From b6cf59b3e117cc310c7a7be15d7f26415a5f40e0 Mon Sep 17 00:00:00 2001 From: Bruce Riley Date: Mon, 23 Sep 2024 21:57:01 -0500 Subject: [PATCH] EVM: Rework per-chain transceivers --- evm/src/NttManager/ManagerBase.sol | 101 ++- .../NttManager/NttManagerNoRateLimiting.sol | 6 +- .../NttManagerWithPerChainTransceivers.sol | 150 ++++ evm/src/NttManager/TransceiverRegistry.sol | 51 -- evm/src/interfaces/IManagerBase.sol | 36 +- evm/src/interfaces/INttManager.sol | 3 - evm/test/IntegrationPerChainThresholds.t.sol | 736 ------------------ evm/test/IntegrationWithoutRateLimiting.t.sol | 11 +- evm/test/NttManagerNoRateLimiting.t.sol | 6 +- ...ivers.t.sol => PerChainTransceivers.t.sol} | 128 ++- evm/test/PerChainTransceiversDemo.t.sol | 430 ++++++++++ evm/test/mocks/MockNttManager.sol | 21 + 12 files changed, 771 insertions(+), 908 deletions(-) create mode 100644 evm/src/NttManager/NttManagerWithPerChainTransceivers.sol delete mode 100644 evm/test/IntegrationPerChainThresholds.t.sol rename evm/test/{IntegrationPerChainTransceivers.t.sol => PerChainTransceivers.t.sol} (85%) create mode 100755 evm/test/PerChainTransceiversDemo.t.sol diff --git a/evm/src/NttManager/ManagerBase.sol b/evm/src/NttManager/ManagerBase.sol index 39522fe70..a269660b3 100644 --- a/evm/src/NttManager/ManagerBase.sol +++ b/evm/src/NttManager/ManagerBase.sol @@ -64,24 +64,11 @@ abstract contract ManagerBase is bytes32 private constant MESSAGE_SEQUENCE_SLOT = bytes32(uint256(keccak256("ntt.messageSequence")) - 1); - bytes32 private constant THRESHOLD_SLOT = bytes32(uint256(keccak256("ntt.threshold")) - 1); + bytes32 internal constant THRESHOLD_SLOT = bytes32(uint256(keccak256("ntt.threshold")) - 1); // =============== Storage Getters/Setters ============================================== - function _getThresholdStorage() private pure returns (_Threshold storage $) { - uint256 slot = uint256(THRESHOLD_SLOT); - assembly ("memory-safe") { - $.slot := slot - } - } - - function _getThresholdStoragePerChain() - private - pure - returns (mapping(uint16 => _Threshold) storage $) - { - // TODO: this is safe (reusing the storage slot, because the mapping - // doesn't write into the slot itself) buy maybe we shouldn't? + function _getThresholdStorage() internal pure returns (_Threshold storage $) { uint256 slot = uint256(THRESHOLD_SLOT); assembly ("memory-safe") { $.slot := slot @@ -135,7 +122,7 @@ abstract contract ManagerBase is uint256 totalPriceQuote = 0; for (uint256 i = 0; i < numEnabledTransceivers; i++) { address transceiverAddr = enabledTransceivers[i]; - if (!_isTransceiverEnabledForChain(transceiverAddr, recipientChain)) { + if (!_isSendTransceiverEnabledForChain(transceiverAddr, recipientChain)) { continue; } uint8 registeredTransceiverIndex = transceiverInfos[transceiverAddr].index; @@ -217,7 +204,7 @@ abstract contract ManagerBase is // call into transceiver contracts to send the message for (uint256 i = 0; i < numEnabledTransceivers; i++) { address transceiverAddr = enabledTransceivers[i]; - if (!_isTransceiverEnabledForChain(transceiverAddr, recipientChain)) { + if (!_isSendTransceiverEnabledForChain(transceiverAddr, recipientChain)) { continue; } @@ -304,14 +291,11 @@ abstract contract ManagerBase is return _getThresholdStorage().num; } - function getThreshold( - uint16 forChainId - ) public view returns (uint8) { - uint8 threshold = _getThresholdStoragePerChain()[forChainId].num; - if (threshold == 0) { - return _getThresholdStorage().num; - } - return threshold; + /// @inheritdoc IManagerBase + function getPerChainThreshold( + uint16 // forChainId + ) public view virtual returns (uint8) { + return _getThresholdStorage().num; } /// @inheritdoc IManagerBase @@ -319,7 +303,7 @@ abstract contract ManagerBase is bytes32 digest ) public view returns (bool) { uint16 sourceChainId = _getMessageAttestationsStorage()[digest].sourceChainId; - uint8 threshold = getThreshold(sourceChainId); + uint8 threshold = getPerChainThreshold(sourceChainId); return messageAttestations(digest) >= threshold && threshold > 0; } @@ -379,15 +363,6 @@ abstract contract ManagerBase is ITransceiver(_registeredTransceivers[i]).transferTransceiverOwnership(newOwner); } } - - /// @inheritdoc IManagerBase - function enableTransceiverForChain( - address transceiver, - uint16 forChainId - ) external onlyOwner { - _enableTransceiverForChain(transceiver, forChainId); - emit TransceiverEnabledForChain(transceiver, forChainId); - } /// @inheritdoc IManagerBase function setTransceiver( @@ -436,6 +411,22 @@ abstract contract ManagerBase is _checkThresholdInvariants(); } + /// @inheritdoc IManagerBase + function enableSendTransceiverForChain( + address, // transceiver, + uint16 // forChainId + ) external virtual onlyOwner { + revert NotImplemented(); + } + + /// @inheritdoc IManagerBase + function enableRecvTransceiverForChain( + address, // transceiver, + uint16 // forChainId + ) external virtual onlyOwner { + revert NotImplemented(); + } + /// @inheritdoc IManagerBase function setThreshold( uint8 threshold @@ -452,20 +443,13 @@ abstract contract ManagerBase is emit ThresholdChanged(oldThreshold, threshold); } - - /// @inheritdoc IManagerBase - function setThresholdPerChain(uint16 forChainId, uint8 threshold) external onlyOwner { - if (threshold == 0) { - revert ZeroThreshold(); - } - - mapping(uint16 => _Threshold) storage _threshold = _getThresholdStoragePerChain(); - uint8 oldThreshold = _threshold[forChainId].num; - _threshold[forChainId].num = threshold; - _checkThresholdInvariants(_threshold[forChainId].num); - - emit PerChainThresholdChanged(forChainId, oldThreshold, threshold); + /// @inheritdoc IManagerBase + function setPerChainThreshold( + uint16, // forChainId, + uint8 // threshold + ) external virtual onlyOwner { + revert NotImplemented(); } // =============== Internal ============================================================== @@ -488,7 +472,7 @@ abstract contract ManagerBase is ) internal view returns (uint64) { uint64 enabledTransceiverBitmap = _getEnabledTransceiversBitmap(); uint16 sourceChainId = _getMessageAttestationsStorage()[digest].sourceChainId; - uint64 enabledTransceiversForChain = _getEnabledTransceiversBitmapForChain(sourceChainId); + uint64 enabledTransceiversForChain = _getEnabledRecvTransceiversForChain(sourceChainId); return _getMessageAttestationsStorage()[digest].attestedTransceivers & enabledTransceiverBitmap & enabledTransceiversForChain; } @@ -521,6 +505,19 @@ abstract contract ManagerBase is _getMessageSequenceStorage().num++; } + function _isSendTransceiverEnabledForChain( + address, // transceiver, + uint16 // chainId + ) internal view virtual returns (bool) { + return true; + } + + function _getEnabledRecvTransceiversForChain( + uint16 // forChainId + ) internal view virtual returns (uint64 bitmap) { + return type(uint64).max; + } + /// ============== Invariants ============================================= /// @dev When we add new immutables, this function should be updated @@ -541,8 +538,10 @@ abstract contract ManagerBase is function _checkThresholdInvariants() internal view { _checkThresholdInvariants(_getThresholdStorage().num); } - - function _checkThresholdInvariants(uint8 threshold) internal pure { + + function _checkThresholdInvariants( + uint8 threshold + ) internal pure { _NumTransceivers memory numTransceivers = _getNumTransceiversStorage(); // invariant: threshold <= enabledTransceivers.length diff --git a/evm/src/NttManager/NttManagerNoRateLimiting.sol b/evm/src/NttManager/NttManagerNoRateLimiting.sol index 14e9fc973..aa75beba9 100644 --- a/evm/src/NttManager/NttManagerNoRateLimiting.sol +++ b/evm/src/NttManager/NttManagerNoRateLimiting.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache 2 pragma solidity >=0.8.8 <0.9.0; -import "./NttManager.sol"; +import "./NttManagerWithPerChainTransceivers.sol"; /// @title NttManagerNoRateLimiting /// @author Wormhole Project Contributors. @@ -10,12 +10,12 @@ import "./NttManager.sol"; /// free up code space. /// /// @dev All of the developer notes from `NttManager` apply here. -contract NttManagerNoRateLimiting is NttManager { +contract NttManagerNoRateLimiting is NttManagerWithPerChainTransceivers { constructor( address _token, Mode _mode, uint16 _chainId - ) NttManager(_token, _mode, _chainId, 0, true) {} + ) NttManagerWithPerChainTransceivers(_token, _mode, _chainId, 0, true) {} // ==================== Override RateLimiter functions ========================= diff --git a/evm/src/NttManager/NttManagerWithPerChainTransceivers.sol b/evm/src/NttManager/NttManagerWithPerChainTransceivers.sol new file mode 100644 index 000000000..9d66948c1 --- /dev/null +++ b/evm/src/NttManager/NttManagerWithPerChainTransceivers.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity >=0.8.8 <0.9.0; + +import "./NttManager.sol"; + +/// @title NttManagerNoRateLimiting +/// @author Wormhole Project Contributors. +/// @notice The NttManagerNoRateLimiting abstract contract is an implementation of +/// NttManager that allows configuring different transceivers and thresholds +/// for each chain. Note that you can configure a different set of send and +/// receive transceivers for each chain, and if you don't specifically enable +/// any transceivers for a chain, then all transceivers will be used for it. +/// +/// @dev All of the developer notes from `NttManager` apply here. +abstract contract NttManagerWithPerChainTransceivers is NttManager { + constructor( + address _token, + Mode _mode, + uint16 _chainId, + uint64 _rateLimitDuration, + bool _skipRateLimiting + ) NttManager(_token, _mode, _chainId, _rateLimitDuration, _skipRateLimiting) {} + + bytes32 private constant SEND_TRANSCEIVER_BITMAP_SLOT = + bytes32(uint256(keccak256("nttpct.sendTransceiverBitmap")) - 1); + + bytes32 private constant RECV_TRANSCEIVER_BITMAP_SLOT = + bytes32(uint256(keccak256("nttpct.recvTransceiverBitmap")) - 1); + + // ==================== Override / implementation of transceiver stuff ========================= + + /// @inheritdoc IManagerBase + function enableSendTransceiverForChain( + address transceiver, + uint16 forChainId + ) external override(ManagerBase, IManagerBase) onlyOwner { + _enableTranceiverForChain(transceiver, forChainId, SEND_TRANSCEIVER_BITMAP_SLOT); + } + + /// @inheritdoc IManagerBase + function enableRecvTransceiverForChain( + address transceiver, + uint16 forChainId + ) external override(ManagerBase, IManagerBase) onlyOwner { + _enableTranceiverForChain(transceiver, forChainId, RECV_TRANSCEIVER_BITMAP_SLOT); + } + + function _enableTranceiverForChain( + address transceiver, + uint16 forChainId, + bytes32 tag + ) internal onlyOwner { + if (transceiver == address(0)) { + revert InvalidTransceiverZeroAddress(); + } + + mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage(); + if (!transceiverInfos[transceiver].registered) { + revert NonRegisteredTransceiver(transceiver); + } + + uint8 index = _getTransceiverInfosStorage()[transceiver].index; + mapping(uint16 => _EnabledTransceiverBitmap) storage _bitmaps = + _getPerChainTransceiverBitmapStorage(tag); + _bitmaps[forChainId].bitmap |= uint64(1 << index); + + emit TransceiverEnabledForChain(transceiver, forChainId); + } + + function _isSendTransceiverEnabledForChain( + address transceiver, + uint16 forChainId + ) internal view override returns (bool) { + uint64 bitmap = + _getPerChainTransceiverBitmapStorage(SEND_TRANSCEIVER_BITMAP_SLOT)[forChainId].bitmap; + if (bitmap == 0) { + // NOTE: this makes it backwards compatible -- if the bitmap is not + // set, it's assumed the corridor uses all transceivers. + bitmap = type(uint64).max; + } + uint8 index = _getTransceiverInfosStorage()[transceiver].index; + return (bitmap & uint64(1 << index)) != 0; + } + + function _getEnabledRecvTransceiversForChain( + uint16 forChainId + ) internal view override returns (uint64 bitmap) { + bitmap = + _getPerChainTransceiverBitmapStorage(RECV_TRANSCEIVER_BITMAP_SLOT)[forChainId].bitmap; + if (bitmap == 0) { + // NOTE: this makes it backwards compatible -- if the bitmap is not + // set, it's assumed the corridor uses all transceivers. + bitmap = type(uint64).max; + } + } + + function _getPerChainTransceiverBitmapStorage( + bytes32 tag + ) internal pure returns (mapping(uint16 => _EnabledTransceiverBitmap) storage $) { + // TODO: this is safe (reusing the storage slot, because the mapping + // doesn't write into the slot itself) buy maybe we shouldn't? + uint256 slot = uint256(tag); + assembly ("memory-safe") { + $.slot := slot + } + } + + // ==================== Override / implementation of threshold stuff ========================= + + /// @inheritdoc IManagerBase + function setPerChainThreshold( + uint16 forChainId, + uint8 threshold + ) external override(ManagerBase, IManagerBase) onlyOwner { + if (threshold == 0) { + revert ZeroThreshold(); + } + + mapping(uint16 => _Threshold) storage _threshold = _getThresholdStoragePerChain(); + uint8 oldThreshold = _threshold[forChainId].num; + + _threshold[forChainId].num = threshold; + _checkThresholdInvariants(_threshold[forChainId].num); + + emit PerChainThresholdChanged(forChainId, oldThreshold, threshold); + } + + function getPerChainThreshold( + uint16 forChainId + ) public view override(ManagerBase, IManagerBase) returns (uint8) { + uint8 threshold = _getThresholdStoragePerChain()[forChainId].num; + if (threshold == 0) { + return _getThresholdStorage().num; + } + return threshold; + } + + function _getThresholdStoragePerChain() + private + pure + returns (mapping(uint16 => _Threshold) storage $) + { + // TODO: this is safe (reusing the storage slot, because the mapping + // doesn't write into the slot itself) buy maybe we shouldn't? + uint256 slot = uint256(THRESHOLD_SLOT); + assembly ("memory-safe") { + $.slot := slot + } + } +} diff --git a/evm/src/NttManager/TransceiverRegistry.sol b/evm/src/NttManager/TransceiverRegistry.sol index 7aa02f13e..d95f39b73 100644 --- a/evm/src/NttManager/TransceiverRegistry.sol +++ b/evm/src/NttManager/TransceiverRegistry.sol @@ -122,19 +122,6 @@ abstract contract TransceiverRegistry { } } - function _getPerChainTransceiverBitmapStorage() - private - pure - returns (mapping(uint16 => _EnabledTransceiverBitmap) storage $) - { - // TODO: this is safe (reusing the storage slot, because the mapping - // doesn't write into the slot itself) buy maybe we shouldn't? - uint256 slot = uint256(TRANSCEIVER_BITMAP_SLOT); - assembly ("memory-safe") { - $.slot := slot - } - } - function _getRegisteredTransceiversStorage() internal pure returns (address[] storage $) { uint256 slot = uint256(REGISTERED_TRANSCEIVERS_SLOT); assembly ("memory-safe") { @@ -151,33 +138,6 @@ abstract contract TransceiverRegistry { // =============== Storage Getters/Setters ======================================== - function _isTransceiverEnabledForChain( - address transceiver, - uint16 chainId - ) internal view returns (bool) { - uint64 bitmap = _getEnabledTransceiversBitmapForChain(chainId); - uint8 index = _getTransceiverInfosStorage()[transceiver].index; - return (bitmap & uint64(1 << index)) != 0; - } - - function _enableTransceiverForChain( - address transceiver, - uint16 chainId - ) internal { - if (transceiver == address(0)) { - revert InvalidTransceiverZeroAddress(); - } - - mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage(); - if (!transceiverInfos[transceiver].registered) { - revert NonRegisteredTransceiver(transceiver); - } - - uint8 index = _getTransceiverInfosStorage()[transceiver].index; - mapping(uint16 => _EnabledTransceiverBitmap)storage _bitmaps = _getPerChainTransceiverBitmapStorage(); - _bitmaps[chainId].bitmap |= uint64(1 << index); - } - function _setTransceiver( address transceiver ) internal returns (uint8 index) { @@ -274,17 +234,6 @@ abstract contract TransceiverRegistry { return _getTransceiverBitmapStorage().bitmap; } - function _getEnabledTransceiversBitmapForChain( - uint16 forChainId - ) internal view virtual returns (uint64 bitmap) { - bitmap = _getPerChainTransceiverBitmapStorage()[forChainId].bitmap; - if (bitmap == 0) { - // NOTE: this makes it backwards compatible -- if the bitmap is not - // set, it's assumed the corridor uses all transceivers. - return type(uint64).max; - } - } - /// @notice Returns the Transceiver contracts that have been enabled via governance. function getTransceivers() external pure returns (address[] memory result) { result = _getEnabledTransceiversStorage(); diff --git a/evm/src/interfaces/IManagerBase.sol b/evm/src/interfaces/IManagerBase.sol index 8b255e77e..708b2380c 100644 --- a/evm/src/interfaces/IManagerBase.sol +++ b/evm/src/interfaces/IManagerBase.sol @@ -48,7 +48,7 @@ interface IManagerBase { /// @param oldThreshold The old threshold. /// @param threshold The new threshold. event ThresholdChanged(uint8 oldThreshold, uint8 threshold); - + /// @notice Emmitted when the per-chain threshold required transceivers is changed. /// @dev Topic0 /// 0x2a855b929b9a53c6fb5b5ed248b27e502b709c088e036a5aa17620c8fc5085a9. @@ -64,7 +64,7 @@ interface IManagerBase { /// @param transceiversNum The current number of transceivers. /// @param threshold The current threshold of transceivers. event TransceiverAdded(address transceiver, uint256 transceiversNum, uint8 threshold); - + /// @notice Emitted when a transceiver is enabled for a chain. /// @dev Topic0 /// 0xf05962b5774c658e85ed80c91a75af9d66d2af2253dda480f90bce78aff5eda5. @@ -127,6 +127,9 @@ interface IManagerBase { /// @param chainId The target Wormhole chain id error PeerNotRegistered(uint16 chainId); + /// @notice Feature is not implemented. + error NotImplemented(); + /// @notice Fetch the delivery price for a given recipient chain transfer. /// @param recipientChain The Wormhole chain ID of the transfer destination. /// @param transceiverInstructions The transceiver specific instructions for quoting and sending @@ -143,16 +146,13 @@ interface IManagerBase { function setThreshold( uint8 threshold ) external; - + /// @notice Sets the per-chain threshold for the number of attestations required for a message /// to be considered valid. Note that if a threshold is not specified for a chain, the default applies. /// @param chainId The chain for which the threshold applies. /// @param threshold The new threshold. /// @dev This method can only be executed by the `owner`. - function setThresholdPerChain( - uint16 chainId, - uint8 threshold - ) external; + function setPerChainThreshold(uint16 chainId, uint8 threshold) external; /// @notice Sets the transceiver for the given chain. /// @param transceiver The address of the transceiver. @@ -167,15 +167,18 @@ interface IManagerBase { function removeTransceiver( address transceiver ) external; - - /// @notice Enables the transceiver for the given chain. + + /// @notice Enables the transceiver for sending on the given chain. /// @param transceiver The address of the transceiver. /// @param chainId The chain for which the threshold applies. /// @dev This method can only be executed by the `owner`. - function enableTransceiverForChain( - address transceiver, - uint16 chainId - ) external; + function enableSendTransceiverForChain(address transceiver, uint16 chainId) external; + + /// @notice Enables the transceiver for receiving on the given chain. + /// @param transceiver The address of the transceiver. + /// @param chainId The chain for which the threshold applies. + /// @dev This method can only be executed by the `owner`. + function enableRecvTransceiverForChain(address transceiver, uint16 chainId) external; /// @notice Checks if a message has been approved. The message should have at least /// the minimum threshold of attestations from distinct endpoints. @@ -214,6 +217,13 @@ interface IManagerBase { /// it to be considered valid and acted upon. function getThreshold() external view returns (uint8); + /// @notice Returns the number of Transceivers that must attest to a msgId for + /// it to be considered valid and acted upon. + /// @param chainId The chain for which the threshold applies. + function getPerChainThreshold( + uint16 chainId + ) external view returns (uint8); + /// @notice Returns a boolean indicating if the transceiver has attested to the message. /// @param digest The digest of the message. /// @param index The index of the transceiver diff --git a/evm/src/interfaces/INttManager.sol b/evm/src/interfaces/INttManager.sol index aeb5ea883..c17de81a3 100644 --- a/evm/src/interfaces/INttManager.sol +++ b/evm/src/interfaces/INttManager.sol @@ -135,9 +135,6 @@ interface INttManager is IManagerBase { /// @dev Selector 0x20371f2a. error InvalidPeerSameChainId(); - /// @notice Feature is not implemented. - error NotImplemented(); - /// @notice Transfer a given amount to a recipient on a given chain. This function is called /// by the user to send the token cross-chain. This function will either lock or burn the /// sender's tokens. Finally, this function will call into registered `Endpoint` contracts diff --git a/evm/test/IntegrationPerChainThresholds.t.sol b/evm/test/IntegrationPerChainThresholds.t.sol deleted file mode 100644 index 8f1ce529e..000000000 --- a/evm/test/IntegrationPerChainThresholds.t.sol +++ /dev/null @@ -1,736 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity >=0.8.8 <0.9.0; - -import "forge-std/Test.sol"; -import "forge-std/console.sol"; - -import "../src/NttManager/NttManager.sol"; -import "../src/Transceiver/Transceiver.sol"; -import "../src/interfaces/INttManager.sol"; -import "../src/interfaces/IRateLimiter.sol"; -import "../src/interfaces/ITransceiver.sol"; -import "../src/interfaces/IManagerBase.sol"; -import "../src/interfaces/IRateLimiterEvents.sol"; -import {Utils} from "./libraries/Utils.sol"; -import {DummyToken, DummyTokenMintAndBurn} from "./NttManager.t.sol"; -import "../src/interfaces/IWormholeTransceiver.sol"; -import {WormholeTransceiver} from "../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; -import "../src/libraries/TransceiverStructs.sol"; -import "./mocks/MockNttManager.sol"; -import "./mocks/MockTransceivers.sol"; - -import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; -import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import "wormhole-solidity-sdk/interfaces/IWormhole.sol"; -import "wormhole-solidity-sdk/testing/helpers/WormholeSimulator.sol"; -import "wormhole-solidity-sdk/Utils.sol"; -//import "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; - -contract TestPerChainTransceivers is Test, IRateLimiterEvents { - NttManager nttManagerChain1; - NttManager nttManagerChain2; - NttManager nttManagerChain3; - - using TrimmedAmountLib for uint256; - using TrimmedAmountLib for TrimmedAmount; - - uint16 constant chainId1 = 7; - uint16 constant chainId2 = 100; - uint16 constant chainId3 = 101; - uint8 constant FAST_CONSISTENCY_LEVEL = 200; - uint256 constant GAS_LIMIT = 500000; - - uint16 constant SENDING_CHAIN_ID = 1; - uint256 constant DEVNET_GUARDIAN_PK = - 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; - WormholeSimulator guardian; - uint256 initialBlockTimestamp; - - WormholeTransceiver wormholeTransceiverChain1; - WormholeTransceiver secondWormholeTransceiverChain1; - WormholeTransceiver wormholeTransceiverChain2; - WormholeTransceiver secondWormholeTransceiverChain2; - WormholeTransceiver wormholeTransceiverChain3; - WormholeTransceiver secondWormholeTransceiverChain3; - address userA = address(0x123); - address userB = address(0x456); - address userC = address(0x789); - address userD = address(0xABC); - - address relayer = address(0x28D8F1Be96f97C1387e94A53e00eCcFb4E75175a); - IWormhole wormhole = IWormhole(0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78); - - // This function sets up the following config: - // - A manager on each of three chains. - // - Two transceivers on each chain, all interconnected as peers. - // - On chain one, it sets a default threshold of one and a per-chain threshold of two for chain three. - // - On chain three, it sets a default threshold of one and a per-chain threshold of two for chain one. - - function setUp() public { - string memory url = "https://ethereum-sepolia-rpc.publicnode.com"; - vm.createSelectFork(url); - initialBlockTimestamp = vm.getBlockTimestamp(); - - guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK); - - vm.chainId(chainId1); - DummyToken t1 = new DummyToken(); - NttManager implementation = new MockNttManagerContract( - address(t1), IManagerBase.Mode.LOCKING, chainId1, 1 days, false - ); - - nttManagerChain1 = - MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); - nttManagerChain1.initialize(); - - // Create the first transceiver, from chain 1 to chain 2. - WormholeTransceiver wormholeTransceiverChain1Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain1 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain1Implementation), "")) - ); - - // Only the deployer should be able to initialize - vm.prank(userA); - vm.expectRevert( - abi.encodeWithSelector(ITransceiver.UnexpectedDeployer.selector, address(this), userA) - ); - wormholeTransceiverChain1.initialize(); - - // Actually initialize properly now - wormholeTransceiverChain1.initialize(); - - // Create the second transceiver for chain 1. - WormholeTransceiver secondWormholeTransceiverChain1Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - secondWormholeTransceiverChain1 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(secondWormholeTransceiverChain1Implementation), "")) - ); - - secondWormholeTransceiverChain1.initialize(); - - // Chain 2 setup - vm.chainId(chainId2); - DummyToken t2 = new DummyTokenMintAndBurn(); - NttManager implementationChain2 = new MockNttManagerContract( - address(t2), IManagerBase.Mode.BURNING, chainId2, 1 days, false - ); - - nttManagerChain2 = - MockNttManagerContract(address(new ERC1967Proxy(address(implementationChain2), ""))); - nttManagerChain2.initialize(); - - WormholeTransceiver wormholeTransceiverChain2Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain2), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain2 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain2Implementation), "")) - ); - wormholeTransceiverChain2.initialize(); - - // Register peer contracts for the nttManager and transceiver. Transceivers and nttManager each have the concept of peers here. - nttManagerChain1.setPeer( - chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 9, type(uint64).max - ); - nttManagerChain2.setPeer( - chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 7, type(uint64).max - ); - - // Create the second transceiver for chain 2. - WormholeTransceiver secondWormholeTransceiverChain2Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain2), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - secondWormholeTransceiverChain2 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(secondWormholeTransceiverChain2Implementation), "")) - ); - - secondWormholeTransceiverChain2.initialize(); - - // Set peers for the transceivers - wormholeTransceiverChain1.setWormholePeer( - chainId2, bytes32(uint256(uint160(address(wormholeTransceiverChain2)))) - ); - wormholeTransceiverChain2.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) - ); - secondWormholeTransceiverChain1.setWormholePeer( - chainId2, bytes32(uint256(uint160(address(secondWormholeTransceiverChain2)))) - ); - secondWormholeTransceiverChain2.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(secondWormholeTransceiverChain1)))) - ); - - // Chain 3 setup - vm.chainId(chainId3); - DummyToken t3 = new DummyTokenMintAndBurn(); - NttManager implementationChain3 = new MockNttManagerContract( - address(t3), IManagerBase.Mode.BURNING, chainId3, 1 days, false - ); - - nttManagerChain3 = - MockNttManagerContract(address(new ERC1967Proxy(address(implementationChain3), ""))); - nttManagerChain3.initialize(); - - WormholeTransceiver wormholeTransceiverChain3Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain3), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain3 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain3Implementation), "")) - ); - wormholeTransceiverChain3.initialize(); - - // Register peer contracts for the nttManager and transceiver. Transceivers and nttManager each have the concept of peers here. - nttManagerChain1.setPeer( - chainId3, bytes32(uint256(uint160(address(nttManagerChain3)))), 9, type(uint64).max - ); - nttManagerChain3.setPeer( - chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 7, type(uint64).max - ); - - // Create the second transceiver, from chain 3 to chain 1. - WormholeTransceiver secondWormholeTransceiverChain3Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain3), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - secondWormholeTransceiverChain3 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(secondWormholeTransceiverChain3Implementation), "")) - ); - - // Actually initialize properly now - secondWormholeTransceiverChain3.initialize(); - - // Set peers for the transceivers - wormholeTransceiverChain1.setWormholePeer( - chainId3, bytes32(uint256(uint160(address(wormholeTransceiverChain3)))) - ); - wormholeTransceiverChain3.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) - ); - wormholeTransceiverChain3.setWormholePeer( - chainId2, bytes32(uint256(uint160(address(wormholeTransceiverChain2)))) - ); - wormholeTransceiverChain2.setWormholePeer( - chainId3, bytes32(uint256(uint160(address(wormholeTransceiverChain3)))) - ); - secondWormholeTransceiverChain1.setWormholePeer( - chainId3, bytes32(uint256(uint160(address(secondWormholeTransceiverChain3)))) - ); - secondWormholeTransceiverChain3.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(secondWormholeTransceiverChain1)))) - ); - secondWormholeTransceiverChain2.setWormholePeer( - chainId3, bytes32(uint256(uint160(address(secondWormholeTransceiverChain3)))) - ); - secondWormholeTransceiverChain3.setWormholePeer( - chainId2, bytes32(uint256(uint160(address(secondWormholeTransceiverChain2)))) - ); - } - - // This test does a transfer between chain one and chain two where there are two transceivers. - // Since chain two uses the default threshold of one, posting a VAA from only one transceiver completes the transfer. - function test_defaultThresholds() public { - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); - nttManagerChain1.setTransceiver(address(secondWormholeTransceiverChain1)); - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2)); - nttManagerChain2.setTransceiver(address(secondWormholeTransceiverChain2)); - nttManagerChain3.setTransceiver(address(wormholeTransceiverChain3)); - nttManagerChain3.setTransceiver(address(secondWormholeTransceiverChain3)); - - nttManagerChain1.setThreshold(1); - nttManagerChain2.setThreshold(1); - nttManagerChain3.setThreshold(1); - - // On chain 1, set the threshold to chain 3 to be different. - nttManagerChain1.setThresholdPerChain(chainId3, 2); - nttManagerChain3.setThresholdPerChain(chainId1, 2); - - require(nttManagerChain1.getThreshold() == 1, "Default threshold is wrong"); - require(nttManagerChain1.getThreshold(chainId3) == 2, "Threshold for chain 3 is wrong"); - - vm.chainId(chainId1); - - // Setting up the transfer - DummyToken token1 = DummyToken(nttManagerChain1.token()); - DummyToken token2 = DummyTokenMintAndBurn(nttManagerChain2.token()); - - uint8 decimals = token1.decimals(); - uint256 sendingAmount = 5 * 10 ** decimals; - token1.mintDummy(address(userA), 5 * 10 ** decimals); - - // Transfer tokens from chain one to chain two through standard means (not relayer) - vm.startPrank(userA); - token1.approve(address(nttManagerChain1), sendingAmount); - vm.recordLogs(); - { - uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceBefore = token1.balanceOf(address(userA)); - nttManagerChain1.transfer(sendingAmount, chainId2, bytes32(uint256(uint160(userB)))); - - // Balance check on funds going in and out working as expected - uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceAfter = token1.balanceOf(address(userB)); - require( - nttManagerBalanceBefore + sendingAmount == nttManagerBalanceAfter, - "Should be locking the tokens" - ); - require( - userBalanceBefore - sendingAmount == userBalanceAfter, - "User should have sent tokens" - ); - } - - vm.stopPrank(); - - // Get and sign the log to go down the other pipes. There should be two messages since we have two transceivers. - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - require(2 == entries.length, "Unexpected number of log entries 1"); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } - - // Chain2 verification and checks - vm.chainId(chainId2); - - { - uint256 supplyBefore = token2.totalSupply(); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); - uint256 supplyAfter = token2.totalSupply(); - - require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); - require(token2.balanceOf(userB) == sendingAmount, "User didn't receive tokens"); - require( - token2.balanceOf(address(nttManagerChain2)) == 0, "NttManager has unintended funds" - ); - } - - // Go back the other way from a THIRD user - vm.prank(userB); - token2.transfer(userC, sendingAmount); - - vm.startPrank(userC); - token2.approve(address(nttManagerChain2), sendingAmount); - vm.recordLogs(); - - // Supply checks on the transfer - { - uint256 supplyBefore = token2.totalSupply(); - nttManagerChain2.transfer( - sendingAmount, - chainId1, - toWormholeFormat(userD), - toWormholeFormat(userC), - false, - encodeTransceiverInstruction(true) - ); - - uint256 supplyAfter = token2.totalSupply(); - - require(sendingAmount - supplyBefore == supplyAfter, "Supplies don't match"); - require(token2.balanceOf(userB) == 0, "OG user receive tokens"); - require(token2.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require( - token2.balanceOf(address(nttManagerChain2)) == 0, - "NttManager didn't receive unintended funds" - ); - } - - // Get and sign the log to go down the other pipe. Thank you to whoever wrote this code in the past! - entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - require(2 == entries.length, "Unexpected number of log entries 2"); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); - } - - vm.chainId(chainId1); - - // Receive the first message. Since the threshold is set to one, the transfer should complete. - { - uint256 supplyBefore = token1.totalSupply(); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); - uint256 supplyAfter = token1.totalSupply(); - - require(supplyBefore == supplyAfter, "Supplies don't match between operations"); - require(token1.balanceOf(userB) == 0, "OG user receive tokens"); - require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require(token1.balanceOf(userD) == sendingAmount, "User received funds"); - } - - // Receive the second message. That should do nothing since we've already processed the transfer. - { - uint256 supplyBefore = token1.totalSupply(); - secondWormholeTransceiverChain1.receiveMessage(encodedVMs[1]); - uint256 supplyAfter = token1.totalSupply(); - - require(supplyBefore == supplyAfter, "Supplies don't match between operations"); - require(token1.balanceOf(userB) == 0, "OG user receive tokens"); - require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require(token1.balanceOf(userD) == sendingAmount, "User received funds"); - } - } - - // This test does a transfer between chain one and chain three where there are two transceivers and the per-chain threshold is two. - // The transfer is not completed until both VAAs are posted. - function test_perChainThreshold() public { - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); - nttManagerChain1.setTransceiver(address(secondWormholeTransceiverChain1)); - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2)); - nttManagerChain2.setTransceiver(address(secondWormholeTransceiverChain2)); - nttManagerChain3.setTransceiver(address(wormholeTransceiverChain3)); - nttManagerChain3.setTransceiver(address(secondWormholeTransceiverChain3)); - - nttManagerChain1.setThreshold(1); - nttManagerChain2.setThreshold(1); - nttManagerChain3.setThreshold(1); - - // On chain 1, set the threshold to chain 3 to be different. - nttManagerChain1.setThresholdPerChain(chainId3, 2); - nttManagerChain3.setThresholdPerChain(chainId1, 2); - - require(nttManagerChain1.getThreshold() == 1, "Default threshold is wrong"); - require(nttManagerChain1.getThreshold(chainId3) == 2, "Threshold for chain 3 is wrong"); - - vm.chainId(chainId1); - - // Setting up the transfer - DummyToken token1 = DummyToken(nttManagerChain1.token()); - DummyToken token3 = DummyTokenMintAndBurn(nttManagerChain3.token()); - - uint8 decimals = token1.decimals(); - uint256 sendingAmount = 5 * 10 ** decimals; - token1.mintDummy(address(userA), 5 * 10 ** decimals); - vm.startPrank(userA); - token1.approve(address(nttManagerChain1), sendingAmount); - - vm.recordLogs(); - - // Send token from chain 1 to chain 3, userB. - { - uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceBefore = token1.balanceOf(address(userA)); - nttManagerChain1.transfer(sendingAmount, chainId3, bytes32(uint256(uint160(userB)))); - - // Balance check on funds going in and out working as expected - uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceAfter = token1.balanceOf(address(userB)); - require( - nttManagerBalanceBefore + sendingAmount == nttManagerBalanceAfter, - "Should be locking the tokens" - ); - require( - userBalanceBefore - sendingAmount == userBalanceAfter, - "User should have sent tokens" - ); - } - - vm.stopPrank(); - - // Get and sign the log to go down the other pipes. There should be two messages since we have two transceivers. - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - require(2 == entries.length, "Unexpected number of log entries 3"); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } - - // Chain3 verification and checks - vm.chainId(chainId3); - - uint256 supplyBefore = token3.totalSupply(); - - // Submit the first message on chain 3. The numbers shouldn't change yet since the threshold is two. - wormholeTransceiverChain3.receiveMessage(encodedVMs[0]); - uint256 supplyAfter = token3.totalSupply(); - - require(supplyBefore == supplyAfter, "Supplies changed early"); - require(token3.balanceOf(userB) == 0, "User receive tokens early"); - require(token3.balanceOf(address(nttManagerChain3)) == 0, "NttManager has unintended funds"); - - // Submit the second message and the transfer should complete. - secondWormholeTransceiverChain3.receiveMessage(encodedVMs[1]); - supplyAfter = token3.totalSupply(); - - require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); - require(token3.balanceOf(userB) == sendingAmount, "User didn't receive tokens"); - require(token3.balanceOf(address(nttManagerChain3)) == 0, "NttManager has unintended funds"); - - // Go back the other way from a THIRD user - vm.prank(userB); - token3.transfer(userC, sendingAmount); - - vm.startPrank(userC); - token3.approve(address(nttManagerChain3), sendingAmount); - vm.recordLogs(); - - // Supply checks on the transfer - supplyBefore = token3.totalSupply(); - nttManagerChain3.transfer( - sendingAmount, - chainId1, - toWormholeFormat(userD), - toWormholeFormat(userC), - false, - encodeTransceiverInstruction(true) - ); - - supplyAfter = token3.totalSupply(); - - require(sendingAmount - supplyBefore == supplyAfter, "Supplies don't match"); - require(token3.balanceOf(userB) == 0, "OG user receive tokens"); - require(token3.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require( - token3.balanceOf(address(nttManagerChain3)) == 0, - "NttManager didn't receive unintended funds" - ); - - // Get and sign the log to go down the other pipe. Thank you to whoever wrote this code in the past! - entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - require(2 == entries.length, "Unexpected number of log entries for response"); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId3); - } - - // Chain1 verification and checks with the receiving of the message - vm.chainId(chainId1); - - // Submit the first message back on chain one. Nothing should happen because our threshold is two. - supplyBefore = token1.totalSupply(); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); - supplyAfter = token1.totalSupply(); - - require(supplyBefore == supplyAfter, "Supplies don't match between operations"); - require(token1.balanceOf(userB) == 0, "OG user receive tokens"); - require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require(token1.balanceOf(userD) == 0, "User received funds before they should"); - - // Submit the second message back on chain one. This should update the balance. - supplyBefore = token1.totalSupply(); - secondWormholeTransceiverChain1.receiveMessage(encodedVMs[1]); - supplyAfter = token1.totalSupply(); - - require(supplyBefore == supplyAfter, "Supplies don't match between operations"); /////////////////// Is this right?? - require(token1.balanceOf(userB) == 0, "OG user receive tokens"); - require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require(token1.balanceOf(userD) == sendingAmount, "User received funds"); - } - - // This test does a transfer between chain one and chain three where there there is only one transceiver and the per-chain threshold is one. - // The transfer is not completed until both VAAs are posted. - function test_perChainTransceiver() public { - // By default, there are two transceivers and a threshold of two. - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); - nttManagerChain1.setTransceiver(address(secondWormholeTransceiverChain1)); - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2)); - nttManagerChain2.setTransceiver(address(secondWormholeTransceiverChain2)); - nttManagerChain3.setTransceiver(address(wormholeTransceiverChain3)); - nttManagerChain3.setTransceiver(address(secondWormholeTransceiverChain3)); - - nttManagerChain1.setThreshold(2); - nttManagerChain2.setThreshold(2); - nttManagerChain3.setThreshold(2); - - // Between chains one and three, there is only a single transceiver with a threshold of one. - nttManagerChain1.enableTransceiverForChain(address(wormholeTransceiverChain1), chainId3); - nttManagerChain3.enableTransceiverForChain(address(wormholeTransceiverChain3), chainId1); - nttManagerChain1.setThresholdPerChain(chainId3, 1); - nttManagerChain3.setThresholdPerChain(chainId1, 1); - - require(nttManagerChain1.getThreshold() == 2, "Default threshold is wrong on chain 1"); - require(nttManagerChain1.getThreshold(chainId3) == 1, "Threshold for chain 3 is wrong on chain 1"); - require(nttManagerChain1.getThreshold() == 2, "Default threshold is wrong on chain 3"); - require(nttManagerChain3.getThreshold(chainId1) == 1, "Threshold for chain 1 is wrong on chain 3"); - - vm.chainId(chainId1); - - // Setting up the transfer - DummyToken token1 = DummyToken(nttManagerChain1.token()); - DummyToken token3 = DummyTokenMintAndBurn(nttManagerChain3.token()); - - uint8 decimals = token1.decimals(); - uint256 sendingAmount = 5 * 10 ** decimals; - token1.mintDummy(address(userA), 5 * 10 ** decimals); - vm.startPrank(userA); - token1.approve(address(nttManagerChain1), sendingAmount); - - vm.recordLogs(); - - // Send token from chain 1 to chain 3, userB. - { - uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceBefore = token1.balanceOf(address(userA)); - nttManagerChain1.transfer(sendingAmount, chainId3, bytes32(uint256(uint160(userB)))); - - // Balance check on funds going in and out working as expected - uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceAfter = token1.balanceOf(address(userB)); - require( - nttManagerBalanceBefore + sendingAmount == nttManagerBalanceAfter, - "Should be locking the tokens" - ); - require( - userBalanceBefore - sendingAmount == userBalanceAfter, - "User should have sent tokens" - ); - } - - vm.stopPrank(); - - // Get and sign the log to go down the other pipes. There should be two messages since we have two transceivers. - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - require(2 == entries.length, "Unexpected number of log entries 3"); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } - - // Chain3 verification and checks - vm.chainId(chainId3); - - uint256 supplyBefore = token3.totalSupply(); -/* - // Submit the first message on chain 3. The numbers shouldn't change yet since the threshold is two. - wormholeTransceiverChain3.receiveMessage(encodedVMs[0]); - uint256 supplyAfter = token3.totalSupply(); - - require(supplyBefore == supplyAfter, "Supplies changed early"); - require(token3.balanceOf(userB) == 0, "User receive tokens early"); - require(token3.balanceOf(address(nttManagerChain3)) == 0, "NttManager has unintended funds"); - - // Submit the second message and the transfer should complete. - secondWormholeTransceiverChain3.receiveMessage(encodedVMs[1]); - supplyAfter = token3.totalSupply(); - - require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); - require(token3.balanceOf(userB) == sendingAmount, "User didn't receive tokens"); - require(token3.balanceOf(address(nttManagerChain3)) == 0, "NttManager has unintended funds"); - - // Go back the other way from a THIRD user - vm.prank(userB); - token3.transfer(userC, sendingAmount); - - vm.startPrank(userC); - token3.approve(address(nttManagerChain3), sendingAmount); - vm.recordLogs(); - - // Supply checks on the transfer - supplyBefore = token3.totalSupply(); - nttManagerChain3.transfer( - sendingAmount, - chainId1, - toWormholeFormat(userD), - toWormholeFormat(userC), - false, - encodeTransceiverInstruction(true) - ); - - supplyAfter = token3.totalSupply(); - - require(sendingAmount - supplyBefore == supplyAfter, "Supplies don't match"); - require(token3.balanceOf(userB) == 0, "OG user receive tokens"); - require(token3.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require( - token3.balanceOf(address(nttManagerChain3)) == 0, - "NttManager didn't receive unintended funds" - ); - - // Get and sign the log to go down the other pipe. Thank you to whoever wrote this code in the past! - entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - require(2 == entries.length, "Unexpected number of log entries for response"); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId3); - } - - // Chain1 verification and checks with the receiving of the message - vm.chainId(chainId1); - - // Submit the first message back on chain one. Nothing should happen because our threshold is two. - supplyBefore = token1.totalSupply(); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); - supplyAfter = token1.totalSupply(); - - require(supplyBefore == supplyAfter, "Supplies don't match between operations"); - require(token1.balanceOf(userB) == 0, "OG user receive tokens"); - require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require(token1.balanceOf(userD) == 0, "User received funds before they should"); - - // Submit the second message back on chain one. This should update the balance. - supplyBefore = token1.totalSupply(); - secondWormholeTransceiverChain1.receiveMessage(encodedVMs[1]); - supplyAfter = token1.totalSupply(); - - require(supplyBefore == supplyAfter, "Supplies don't match between operations"); /////////////////// Is this right?? - require(token1.balanceOf(userB) == 0, "OG user receive tokens"); - require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require(token1.balanceOf(userD) == sendingAmount, "User received funds"); - */ - } - - function encodeTransceiverInstruction(bool relayer_off) public view returns (bytes memory) { - WormholeTransceiver.WormholeTransceiverInstruction memory instruction = - IWormholeTransceiver.WormholeTransceiverInstruction(relayer_off); - bytes memory encodedInstructionWormhole = - wormholeTransceiverChain1.encodeWormholeTransceiverInstruction(instruction); - TransceiverStructs.TransceiverInstruction memory TransceiverInstruction = TransceiverStructs - .TransceiverInstruction({index: 0, payload: encodedInstructionWormhole}); - TransceiverStructs.TransceiverInstruction[] memory TransceiverInstructions = - new TransceiverStructs.TransceiverInstruction[](1); - TransceiverInstructions[0] = TransceiverInstruction; - return TransceiverStructs.encodeTransceiverInstructions(TransceiverInstructions); - } - - // Encode an instruction for each of the relayers - function encodeTransceiverInstructions(bool relayer_off) public view returns (bytes memory) { - WormholeTransceiver.WormholeTransceiverInstruction memory instruction = - IWormholeTransceiver.WormholeTransceiverInstruction(relayer_off); - - bytes memory encodedInstructionWormhole = - wormholeTransceiverChain1.encodeWormholeTransceiverInstruction(instruction); - - TransceiverStructs.TransceiverInstruction memory TransceiverInstruction1 = - TransceiverStructs.TransceiverInstruction({index: 0, payload: encodedInstructionWormhole}); - TransceiverStructs.TransceiverInstruction memory TransceiverInstruction2 = - TransceiverStructs.TransceiverInstruction({index: 1, payload: encodedInstructionWormhole}); - - TransceiverStructs.TransceiverInstruction[] memory TransceiverInstructions = - new TransceiverStructs.TransceiverInstruction[](2); - - TransceiverInstructions[0] = TransceiverInstruction1; - TransceiverInstructions[1] = TransceiverInstruction2; - - return TransceiverStructs.encodeTransceiverInstructions(TransceiverInstructions); - } -} diff --git a/evm/test/IntegrationWithoutRateLimiting.t.sol b/evm/test/IntegrationWithoutRateLimiting.t.sol index 80d8a17b3..0cad6e776 100755 --- a/evm/test/IntegrationWithoutRateLimiting.t.sol +++ b/evm/test/IntegrationWithoutRateLimiting.t.sol @@ -313,19 +313,19 @@ contract TestEndToEndNoRateLimiting is Test { chainId1, bytes32(uint256(uint160(address(nttManagerChain2)))), 9, type(uint64).max ); - vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotImplemented.selector)); nttManagerChain1.getOutboundQueuedTransfer(0); - vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotImplemented.selector)); nttManagerChain1.getInboundQueuedTransfer(bytes32(0)); - vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotImplemented.selector)); nttManagerChain1.completeInboundQueuedTransfer(bytes32(0)); - vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotImplemented.selector)); nttManagerChain1.completeOutboundQueuedTransfer(0); - vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotImplemented.selector)); nttManagerChain1.cancelOutboundQueuedTransfer(0); } @@ -572,6 +572,7 @@ contract TestEndToEndNoRateLimiting is Test { uint256 supplyBefore = token2.totalSupply(); wormholeTransceiverChain2_1.receiveMessage(encodedVMs[0]); + // Playing the VAA to the wrong transceiver should revert. vm.expectRevert( abi.encodeWithSelector( IWormholeTransceiver.InvalidWormholePeer.selector, diff --git a/evm/test/NttManagerNoRateLimiting.t.sol b/evm/test/NttManagerNoRateLimiting.t.sol index 5dae016f2..c58d7c305 100644 --- a/evm/test/NttManagerNoRateLimiting.t.sol +++ b/evm/test/NttManagerNoRateLimiting.t.sol @@ -750,11 +750,11 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { vm.chainId(chainId); // Queued outbound transfers can't be completed, per usual - vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotImplemented.selector)); nttManager.completeOutboundQueuedTransfer(sequence); // Queued outbound transfers can't be cancelled, per usual - vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotImplemented.selector)); nttManager.cancelOutboundQueuedTransfer(sequence); // Outbound transfers fail when queued @@ -822,7 +822,7 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { vm.warp(vm.getBlockTimestamp() + 1 days); - vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotImplemented.selector)); nttManager.completeInboundQueuedTransfer(hash); } diff --git a/evm/test/IntegrationPerChainTransceivers.t.sol b/evm/test/PerChainTransceivers.t.sol similarity index 85% rename from evm/test/IntegrationPerChainTransceivers.t.sol rename to evm/test/PerChainTransceivers.t.sol index ab8ff8fe7..3a3f05198 100755 --- a/evm/test/IntegrationPerChainTransceivers.t.sol +++ b/evm/test/PerChainTransceivers.t.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.8 <0.9.0; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import "../src/NttManager/NttManager.sol"; +import "../src/NttManager/NttManagerNoRateLimiting.sol"; import "../src/Transceiver/Transceiver.sol"; import "../src/interfaces/INttManager.sol"; import "../src/interfaces/IRateLimiter.sol"; @@ -26,10 +26,10 @@ import "wormhole-solidity-sdk/testing/helpers/WormholeSimulator.sol"; import "wormhole-solidity-sdk/Utils.sol"; //import "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; -contract TestPerChainThresholds is Test, IRateLimiterEvents { - NttManager nttManagerChain1; - NttManager nttManagerChain2; - NttManager nttManagerChain3; +contract TestPerChainTransceivers is Test, IRateLimiterEvents { + MockNttManagerNoRateLimitingContractForTest nttManagerChain1; + NttManagerNoRateLimiting nttManagerChain2; + NttManagerNoRateLimiting nttManagerChain3; using TrimmedAmountLib for uint256; using TrimmedAmountLib for TrimmedAmount; @@ -75,12 +75,13 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { vm.chainId(chainId1); DummyToken t1 = new DummyToken(); - NttManager implementation = new MockNttManagerContract( - address(t1), IManagerBase.Mode.LOCKING, chainId1, 1 days, false + NttManager implementation = new MockNttManagerNoRateLimitingContractForTest( + address(t1), IManagerBase.Mode.LOCKING, chainId1 ); - nttManagerChain1 = - MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); + nttManagerChain1 = MockNttManagerNoRateLimitingContractForTest( + address(new ERC1967Proxy(address(implementation), "")) + ); nttManagerChain1.initialize(); // Create the first transceiver, from chain 1 to chain 2. @@ -107,9 +108,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { wormholeTransceiverChain1.initialize(); nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); - nttManagerChain1.setOutboundLimit(type(uint64).max); - nttManagerChain1.setInboundLimit(type(uint64).max, chainId2); - nttManagerChain1.setInboundLimit(type(uint64).max, chainId3); // Create the second transceiver for chain 1. WormholeTransceiver secondWormholeTransceiverChain1Implementation = new MockWormholeTransceiverContract( @@ -130,12 +128,13 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { // Chain 2 setup vm.chainId(chainId2); DummyToken t2 = new DummyTokenMintAndBurn(); - NttManager implementationChain2 = new MockNttManagerContract( - address(t2), IManagerBase.Mode.BURNING, chainId2, 1 days, false + NttManager implementationChain2 = new MockNttManagerNoRateLimitingContract( + address(t2), IManagerBase.Mode.BURNING, chainId2 ); - nttManagerChain2 = - MockNttManagerContract(address(new ERC1967Proxy(address(implementationChain2), ""))); + nttManagerChain2 = MockNttManagerNoRateLimitingContract( + address(new ERC1967Proxy(address(implementationChain2), "")) + ); nttManagerChain2.initialize(); WormholeTransceiver wormholeTransceiverChain2Implementation = new MockWormholeTransceiverContract( @@ -152,8 +151,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { wormholeTransceiverChain2.initialize(); nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2)); - nttManagerChain2.setOutboundLimit(type(uint64).max); - nttManagerChain2.setInboundLimit(type(uint64).max, chainId1); // Register peer contracts for the nttManager and transceiver. Transceivers and nttManager each have the concept of peers here. nttManagerChain1.setPeer( @@ -196,12 +193,13 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { // Chain 3 setup vm.chainId(chainId3); DummyToken t3 = new DummyTokenMintAndBurn(); - NttManager implementationChain3 = new MockNttManagerContract( - address(t3), IManagerBase.Mode.BURNING, chainId3, 1 days, false + NttManager implementationChain3 = new MockNttManagerNoRateLimitingContract( + address(t3), IManagerBase.Mode.BURNING, chainId3 ); - nttManagerChain3 = - MockNttManagerContract(address(new ERC1967Proxy(address(implementationChain3), ""))); + nttManagerChain3 = MockNttManagerNoRateLimitingContract( + address(new ERC1967Proxy(address(implementationChain3), "")) + ); nttManagerChain3.initialize(); WormholeTransceiver wormholeTransceiverChain3Implementation = new MockWormholeTransceiverContract( @@ -218,8 +216,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { wormholeTransceiverChain3.initialize(); nttManagerChain3.setTransceiver(address(wormholeTransceiverChain3)); - nttManagerChain3.setOutboundLimit(type(uint64).max); - nttManagerChain3.setInboundLimit(type(uint64).max, chainId1); // Register peer contracts for the nttManager and transceiver. Transceivers and nttManager each have the concept of peers here. nttManagerChain1.setPeer( @@ -246,8 +242,6 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { secondWormholeTransceiverChain3.initialize(); nttManagerChain3.setTransceiver(address(secondWormholeTransceiverChain3)); - nttManagerChain1.setOutboundLimit(type(uint64).max); - nttManagerChain1.setInboundLimit(type(uint64).max, chainId3); // Set peers for the transceivers wormholeTransceiverChain1.setWormholePeer( @@ -284,11 +278,65 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { nttManagerChain3.setThreshold(1); // On chain 1, set the threshold to chain 3 to be different. - nttManagerChain1.setThresholdPerChain(chainId3, 2); - nttManagerChain3.setThresholdPerChain(chainId1, 2); + nttManagerChain1.setPerChainThreshold(chainId3, 2); + nttManagerChain3.setPerChainThreshold(chainId1, 2); require(nttManagerChain1.getThreshold() == 1, "Default threshold is wrong"); - require(nttManagerChain1.getThreshold(chainId3) == 2, "Threshold for chain 3 is wrong"); + require( + nttManagerChain1.getPerChainThreshold(chainId3) == 2, "Threshold for chain 3 is wrong" + ); + + // Since we haven't set the per-chain threshold for chain 2, it should return the default + require( + nttManagerChain1.getPerChainThreshold(chainId2) == 1, "Threshold for chain 2 is wrong" + ); + } + + function test_transceiverSetters() public { + // If we haven't enabled per-chain send transceivers for this chain, the getter should return true. + require( + nttManagerChain1.isSendTransceiverEnabledForChain(address(nttManagerChain3), chainId3), + "Send transceiver should be enabled by default" + ); + + // If we haven't enabled per-chain receive transceivers, the getter should return everything is enabled. + require( + nttManagerChain1.getEnabledRecvTransceiversForChain(chainId3) == type(uint64).max, + "Receive transceiver should be enabled by default" + ); + + nttManagerChain1.enableSendTransceiverForChain(address(wormholeTransceiverChain1), chainId2); + nttManagerChain1.enableRecvTransceiverForChain( + address(secondWormholeTransceiverChain1), chainId3 + ); + + // Once we enable a transceiver for a chain, that is the only one enabled for that chain. + require( + nttManagerChain1.isSendTransceiverEnabledForChain( + address(wormholeTransceiverChain1), chainId2 + ), + "First transceiver should be enabled for sending on chain 2" + ); + require( + !nttManagerChain1.isSendTransceiverEnabledForChain( + address(secondWormholeTransceiverChain1), chainId2 + ), + "Second transceiver should not be enabled for sending on chain 2" + ); + require( + nttManagerChain1.getEnabledRecvTransceiversForChain(chainId3) == 0x2, + "Only second transceiver should be enabled for receiving on chain 3" + ); + + // And for chains we didn't touch, the defaults should still be in place. + require( + nttManagerChain1.isSendTransceiverEnabledForChain(address(nttManagerChain3), chainId3), + "Send transceiver should be enabled by default for untouched chain" + ); + require( + nttManagerChain1.getEnabledRecvTransceiversForChain(chainId2) == type(uint64).max, + "Receive transceiver should be enabled by default for untouched chain" + ); } // This test does a transfer between chain one and chain two. @@ -390,9 +438,9 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); } + // Chain1 verification and checks with the receiving of the message vm.chainId(chainId1); - // Receive the first message. Since the threshold is set to one, the transfer should complete. { uint256 supplyBefore = token1.totalSupply(); wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); @@ -404,17 +452,7 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { require(token1.balanceOf(userD) == sendingAmount, "User received funds"); } - // Receive the second message. That should do nothing since we've already processed the transfer. - { - uint256 supplyBefore = token1.totalSupply(); - secondWormholeTransceiverChain1.receiveMessage(encodedVMs[1]); - uint256 supplyAfter = token1.totalSupply(); - - require(supplyBefore == supplyAfter, "Supplies don't match between operations"); - require(token1.balanceOf(userB) == 0, "OG user receive tokens"); - require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens"); - require(token1.balanceOf(userD) == sendingAmount, "User received funds"); - } + //////////////////////// receive message 1. } // This test does a transfer between chain one and chain three. @@ -545,7 +583,9 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { require(token1.balanceOf(userD) == sendingAmount, "User received funds"); } - function encodeTransceiverInstruction(bool relayer_off) public view returns (bytes memory) { + function encodeTransceiverInstruction( + bool relayer_off + ) public view returns (bytes memory) { WormholeTransceiver.WormholeTransceiverInstruction memory instruction = IWormholeTransceiver.WormholeTransceiverInstruction(relayer_off); bytes memory encodedInstructionWormhole = @@ -559,7 +599,9 @@ contract TestPerChainThresholds is Test, IRateLimiterEvents { } // Encode an instruction for each of the relayers - function encodeTransceiverInstructions(bool relayer_off) public view returns (bytes memory) { + function encodeTransceiverInstructions( + bool relayer_off + ) public view returns (bytes memory) { WormholeTransceiver.WormholeTransceiverInstruction memory instruction = IWormholeTransceiver.WormholeTransceiverInstruction(relayer_off); diff --git a/evm/test/PerChainTransceiversDemo.t.sol b/evm/test/PerChainTransceiversDemo.t.sol new file mode 100755 index 000000000..eb7f575b5 --- /dev/null +++ b/evm/test/PerChainTransceiversDemo.t.sol @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity >=0.8.8 <0.9.0; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/NttManager/NttManagerNoRateLimiting.sol"; +import "../src/Transceiver/Transceiver.sol"; +import "../src/interfaces/INttManager.sol"; +import "../src/interfaces/IRateLimiter.sol"; +import "../src/interfaces/ITransceiver.sol"; +import "../src/interfaces/IManagerBase.sol"; +import "../src/interfaces/IRateLimiterEvents.sol"; +import {Utils} from "./libraries/Utils.sol"; +import {DummyToken, DummyTokenMintAndBurn} from "./NttManager.t.sol"; +import "../src/interfaces/IWormholeTransceiver.sol"; +import {WormholeTransceiver} from "../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; +import "../src/libraries/TransceiverStructs.sol"; +import "./mocks/MockNttManager.sol"; +import "./mocks/MockTransceivers.sol"; + +import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "wormhole-solidity-sdk/interfaces/IWormhole.sol"; +import "wormhole-solidity-sdk/testing/helpers/WormholeSimulator.sol"; +import "wormhole-solidity-sdk/Utils.sol"; +//import "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; + +// This contract demonstrates how per-chain transceivers could be used based on the following configuration. +// +// Chain1 ↔ Chain2 +// +// 1. Chain1 → Chain2 +// a. Send (on Chain1): via both transceivers +// b. Receive (on Chain2): Require 2-of-2, both transceivers +// 2. Chain2 → Chain1 +// a. Send (on Chain2): via first transceiver only +// b. Receive (on Chain1): require first transceiver only (1-of-?) +// +// For this test, we will use the mock wormhole transceiver for both, but will configure separate ones +// with the appropriate thresholds. We will then do a transfer. + +contract TestPerChainTransceiversDemo is Test, IRateLimiterEvents { + MockNttManagerNoRateLimitingContractForTest nttManagerChain1; + MockNttManagerNoRateLimitingContractForTest nttManagerChain2; + + using TrimmedAmountLib for uint256; + using TrimmedAmountLib for TrimmedAmount; + + uint16 constant chainId1 = 101; + uint16 constant chainId2 = 102; + uint8 constant FAST_CONSISTENCY_LEVEL = 200; + uint256 constant GAS_LIMIT = 500000; + + uint16 constant SENDING_CHAIN_ID = 2; + uint256 constant DEVNET_GUARDIAN_PK = + 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; + WormholeSimulator guardian; + uint256 initialBlockTimestamp; + + WormholeTransceiver chainOneFirstTransceiver; + WormholeTransceiver chainOneSecondTransceiver; + WormholeTransceiver chainTwoFirstTransceiver; + WormholeTransceiver chainTwoSecondTransceiver; + + address userA = address(0x123); + address userB = address(0x456); + address userC = address(0x789); + address userD = address(0xABC); + + address relayer = address(0x28D8F1Be96f97C1387e94A53e00eCcFb4E75175a); + IWormhole wormhole = IWormhole(0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78); + + function setUp() public { + string memory url = "https://ethereum-sepolia-rpc.publicnode.com"; + vm.createSelectFork(url); + initialBlockTimestamp = vm.getBlockTimestamp(); + + guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK); + + // Set up the manager on chain one. ////////////////////////////////////////////////////////////////// + vm.chainId(chainId1); + DummyToken t1 = new DummyToken(); + NttManager implementation = new MockNttManagerNoRateLimitingContractForTest( + address(t1), IManagerBase.Mode.LOCKING, chainId1 + ); + + nttManagerChain1 = MockNttManagerNoRateLimitingContractForTest( + address(new ERC1967Proxy(address(implementation), "")) + ); + nttManagerChain1.initialize(); + + // Create the first transceiver on chain one. + WormholeTransceiver chainOneFirstTransceiverImplementation = new MockWormholeTransceiverContract( + address(nttManagerChain1), + address(wormhole), + address(relayer), + address(0x0), + FAST_CONSISTENCY_LEVEL, + GAS_LIMIT + ); + chainOneFirstTransceiver = MockWormholeTransceiverContract( + address(new ERC1967Proxy(address(chainOneFirstTransceiverImplementation), "")) + ); + + chainOneFirstTransceiver.initialize(); + nttManagerChain1.setTransceiver(address(chainOneFirstTransceiver)); + + // Create the second transceiver for chain one. + WormholeTransceiver chainOneSecondTransceiverImplementation = new MockWormholeTransceiverContract( + address(nttManagerChain1), + address(wormhole), + address(relayer), + address(0x0), + FAST_CONSISTENCY_LEVEL, + GAS_LIMIT + ); + chainOneSecondTransceiver = MockWormholeTransceiverContract( + address(new ERC1967Proxy(address(chainOneSecondTransceiverImplementation), "")) + ); + + chainOneSecondTransceiver.initialize(); + nttManagerChain1.setTransceiver(address(chainOneSecondTransceiver)); + + // Set up the manager on chain two. ////////////////////////////////////////////////////////////////// + vm.chainId(chainId2); + DummyToken t2 = new DummyTokenMintAndBurn(); + NttManager implementationChain2 = new MockNttManagerNoRateLimitingContractForTest( + address(t2), IManagerBase.Mode.BURNING, chainId2 + ); + + nttManagerChain2 = MockNttManagerNoRateLimitingContractForTest( + address(new ERC1967Proxy(address(implementationChain2), "")) + ); + nttManagerChain2.initialize(); + + // Create the first transceiver on chain two. + WormholeTransceiver chainTwoFirstTransceiverImplementation = new MockWormholeTransceiverContract( + address(nttManagerChain2), + address(wormhole), + address(relayer), + address(0x0), + FAST_CONSISTENCY_LEVEL, + GAS_LIMIT + ); + chainTwoFirstTransceiver = MockWormholeTransceiverContract( + address(new ERC1967Proxy(address(chainTwoFirstTransceiverImplementation), "")) + ); + chainTwoFirstTransceiver.initialize(); + + nttManagerChain2.setTransceiver(address(chainTwoFirstTransceiver)); + + // Create the second transceiver on chain two. + WormholeTransceiver chainTwoSecondTransceiverImplementation = new MockWormholeTransceiverContract( + address(nttManagerChain2), + address(wormhole), + address(relayer), + address(0x0), + FAST_CONSISTENCY_LEVEL, + GAS_LIMIT + ); + chainTwoSecondTransceiver = MockWormholeTransceiverContract( + address(new ERC1967Proxy(address(chainTwoSecondTransceiverImplementation), "")) + ); + + chainTwoSecondTransceiver.initialize(); + nttManagerChain2.setTransceiver(address(chainTwoSecondTransceiver)); + + // Register the two NTT manager peers. ////////////////////////////////////////////////////////////////// + nttManagerChain1.setPeer( + chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 9, type(uint64).max + ); + nttManagerChain2.setPeer( + chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 7, type(uint64).max + ); + + // Register the transceiver peers. ////////////////////////////////////////////////////////////////// + chainOneFirstTransceiver.setWormholePeer( + chainId2, bytes32(uint256(uint160(address(chainTwoFirstTransceiver)))) + ); + chainTwoFirstTransceiver.setWormholePeer( + chainId1, bytes32(uint256(uint160(address(chainOneFirstTransceiver)))) + ); + chainOneSecondTransceiver.setWormholePeer( + chainId2, bytes32(uint256(uint160(address(chainTwoSecondTransceiver)))) + ); + chainTwoSecondTransceiver.setWormholePeer( + chainId1, bytes32(uint256(uint160(address(chainOneSecondTransceiver)))) + ); + + // Set the default thresholds. ////////////////////////////////////////////////////////////////////// + nttManagerChain1.setThreshold(2); + nttManagerChain2.setThreshold(2); + + // Set up our per-chain transceivers and thresholds. //////////////////////////////////////////////// + + // Set up chain one. + // 1.a: + nttManagerChain1.enableSendTransceiverForChain(address(chainOneFirstTransceiver), chainId2); + nttManagerChain1.enableSendTransceiverForChain(address(chainOneSecondTransceiver), chainId2); + // 2.b: + nttManagerChain1.enableRecvTransceiverForChain(address(chainOneFirstTransceiver), chainId2); + nttManagerChain1.setPerChainThreshold(chainId2, 1); + + // Set up chain two. + // 2.a: + nttManagerChain2.enableSendTransceiverForChain(address(chainTwoFirstTransceiver), chainId1); + // 1.b: + nttManagerChain2.enableRecvTransceiverForChain(address(chainTwoFirstTransceiver), chainId1); + nttManagerChain2.enableRecvTransceiverForChain(address(chainTwoSecondTransceiver), chainId1); + nttManagerChain2.setPerChainThreshold(chainId1, 2); + } + + function test_verifyConfig() public view { + // Verify config of chain one. ////////////////////////////////////////////////////////// + require( + nttManagerChain1.isSendTransceiverEnabledForChain( + address(chainOneFirstTransceiver), chainId2 + ), + "On chain 1, first transceiver should be enabled for sending to chain 2" + ); + require( + nttManagerChain1.isSendTransceiverEnabledForChain( + address(chainOneFirstTransceiver), chainId2 + ), + "On chain 1, second transceiver should be enabled for sending to chain 2" + ); + require( + nttManagerChain1.getEnabledRecvTransceiversForChain(chainId2) == 0x1, + "On chain 1, only first transceiver should be enabled for receiving from chain 2" + ); + require(nttManagerChain1.getThreshold() == 2, "On chain 1, the default threshold is wrong"); + require( + nttManagerChain1.getPerChainThreshold(chainId2) == 1, + "On chain 1, threshold for chain 2 is wrong" + ); + + // Verify config of chain two. ////////////////////////////////////////////////////////// + require( + nttManagerChain2.isSendTransceiverEnabledForChain( + address(chainTwoFirstTransceiver), chainId1 + ), + "On chain 2, first transceiver should be enabled for sending to chain 1" + ); + require( + !nttManagerChain2.isSendTransceiverEnabledForChain( + address(chainTwoSecondTransceiver), chainId1 + ), + "On chain 2, second transceiver should be not enabled for sending to chain 1" + ); + require( + nttManagerChain2.getEnabledRecvTransceiversForChain(chainId1) == 0x3, + "On chain 2, both transceivers should be enabled for receiving from chain 1" + ); + require(nttManagerChain2.getThreshold() == 2, "On chain 2, the default threshold is wrong"); + require( + nttManagerChain2.getPerChainThreshold(chainId1) == 2, + "On chain 2, threshold for chain 1 is wrong" + ); + } + + function test_transfer() public { + vm.chainId(chainId1); + + // Setting up the transfer + DummyToken token1 = DummyToken(nttManagerChain1.token()); + DummyToken token2 = DummyTokenMintAndBurn(nttManagerChain2.token()); + + uint8 decimals = token1.decimals(); + uint256 sendingAmount = 5 * 10 ** decimals; + token1.mintDummy(address(userA), 5 * 10 ** decimals); + + // Transfer tokens from chain one to chain two through standard means (not relayer) + vm.startPrank(userA); + token1.approve(address(nttManagerChain1), sendingAmount); + vm.recordLogs(); + { + uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); + uint256 userBalanceBefore = token1.balanceOf(address(userA)); + nttManagerChain1.transfer(sendingAmount, chainId2, bytes32(uint256(uint160(userB)))); + + // Balance check on funds going in and out working as expected + uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1)); + uint256 userBalanceAfter = token1.balanceOf(address(userB)); + require( + nttManagerBalanceBefore + sendingAmount == nttManagerBalanceAfter, + "Should be locking the tokens" + ); + require( + userBalanceBefore - sendingAmount == userBalanceAfter, + "User should have sent tokens" + ); + } + + vm.stopPrank(); + + // Get the logs. There should be two messages going from chain 1 to chain 2. + Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); + require(2 == entries.length, "Unexpected number of log entries 1"); + bytes[] memory encodedVMs = new bytes[](entries.length); + for (uint256 i = 0; i < encodedVMs.length; i++) { + encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); + } + + // Chain2 verification and checks + vm.chainId(chainId2); + + { + uint256 supplyBefore = token2.totalSupply(); + + // Receiving the first VAA on the first transceiver shouldn't do anything. + chainTwoFirstTransceiver.receiveMessage(encodedVMs[0]); + uint256 supplyAfter = token2.totalSupply(); + require(supplyBefore == supplyAfter, "It looks like the transfer happened too soon"); + + // Receiving the first VAA on the second transceiver should revert. + vm.expectRevert( + abi.encodeWithSelector( + IWormholeTransceiver.InvalidWormholePeer.selector, + chainId1, + chainOneFirstTransceiver + ) + ); + chainTwoSecondTransceiver.receiveMessage(encodedVMs[0]); + + // Receiving the second VAA on the second one should complete the transfer. + chainTwoSecondTransceiver.receiveMessage(encodedVMs[1]); + supplyAfter = token2.totalSupply(); + + require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); + require(token2.balanceOf(userB) == sendingAmount, "User didn't receive tokens"); + require( + token2.balanceOf(address(nttManagerChain2)) == 0, "NttManager has unintended funds" + ); + } + + // Go back the other way from a THIRD user + vm.prank(userB); + token2.transfer(userC, sendingAmount); + + vm.startPrank(userC); + token2.approve(address(nttManagerChain2), sendingAmount); + vm.recordLogs(); + + // Supply checks on the transfer + { + uint256 supplyBefore = token2.totalSupply(); + nttManagerChain2.transfer( + sendingAmount, + chainId1, + toWormholeFormat(userD), + toWormholeFormat(userC), + false, + encodeTransceiverInstruction(true) + ); + + uint256 supplyAfter = token2.totalSupply(); + + require(sendingAmount - supplyBefore == supplyAfter, "Supplies don't match"); + require(token2.balanceOf(userB) == 0, "OG user receive tokens"); + require(token2.balanceOf(userC) == 0, "Sending user didn't receive tokens"); + require( + token2.balanceOf(address(nttManagerChain2)) == 0, + "NttManager didn't receive unintended funds" + ); + } + + // Get the logs. There should only be one message going from chain 2 to chain 1. + entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); + require(1 == entries.length, "Unexpected number of log entries 2"); + encodedVMs = new bytes[](entries.length); + for (uint256 i = 0; i < encodedVMs.length; i++) { + encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); + } + + // Chain1 verification and checks with the receiving of the message + vm.chainId(chainId1); + + { + // Receiving a single VAA should do the trick. + uint256 supplyBefore = token1.totalSupply(); + chainOneFirstTransceiver.receiveMessage(encodedVMs[0]); + uint256 supplyAfter = token1.totalSupply(); + + require(supplyBefore == supplyAfter, "Supplies don't match between operations"); + require(token1.balanceOf(userB) == 0, "OG user receive tokens"); + require(token1.balanceOf(userC) == 0, "Sending user didn't receive tokens"); + require(token1.balanceOf(userD) == sendingAmount, "User received funds"); + } + } + + function encodeTransceiverInstruction( + bool relayer_off + ) public view returns (bytes memory) { + WormholeTransceiver.WormholeTransceiverInstruction memory instruction = + IWormholeTransceiver.WormholeTransceiverInstruction(relayer_off); + bytes memory encodedInstructionWormhole = + chainOneFirstTransceiver.encodeWormholeTransceiverInstruction(instruction); + TransceiverStructs.TransceiverInstruction memory TransceiverInstruction = TransceiverStructs + .TransceiverInstruction({index: 0, payload: encodedInstructionWormhole}); + TransceiverStructs.TransceiverInstruction[] memory TransceiverInstructions = + new TransceiverStructs.TransceiverInstruction[](1); + TransceiverInstructions[0] = TransceiverInstruction; + return TransceiverStructs.encodeTransceiverInstructions(TransceiverInstructions); + } + + // Encode an instruction for each of the relayers + function encodeTransceiverInstructions( + bool relayer_off + ) public view returns (bytes memory) { + WormholeTransceiver.WormholeTransceiverInstruction memory instruction = + IWormholeTransceiver.WormholeTransceiverInstruction(relayer_off); + + bytes memory encodedInstructionWormhole = + chainOneFirstTransceiver.encodeWormholeTransceiverInstruction(instruction); + + TransceiverStructs.TransceiverInstruction memory TransceiverInstruction1 = + TransceiverStructs.TransceiverInstruction({index: 0, payload: encodedInstructionWormhole}); + TransceiverStructs.TransceiverInstruction memory TransceiverInstruction2 = + TransceiverStructs.TransceiverInstruction({index: 1, payload: encodedInstructionWormhole}); + + TransceiverStructs.TransceiverInstruction[] memory TransceiverInstructions = + new TransceiverStructs.TransceiverInstruction[](2); + + TransceiverInstructions[0] = TransceiverInstruction1; + TransceiverInstructions[1] = TransceiverInstruction2; + + return TransceiverStructs.encodeTransceiverInstructions(TransceiverInstructions); + } +} diff --git a/evm/test/mocks/MockNttManager.sol b/evm/test/mocks/MockNttManager.sol index 6c3fd09fe..8586db36f 100644 --- a/evm/test/mocks/MockNttManager.sol +++ b/evm/test/mocks/MockNttManager.sol @@ -111,3 +111,24 @@ contract MockNttManagerStorageLayoutChange is NttManager { c = address(0x3); } } + +contract MockNttManagerNoRateLimitingContractForTest is NttManagerNoRateLimiting { + constructor( + address token, + Mode mode, + uint16 chainId + ) NttManagerNoRateLimiting(token, mode, chainId) {} + + function isSendTransceiverEnabledForChain( + address transceiver, + uint16 forChainId + ) external view returns (bool) { + return _isSendTransceiverEnabledForChain(transceiver, forChainId); + } + + function getEnabledRecvTransceiversForChain( + uint16 forChainId + ) external view returns (uint64 bitmap) { + return _getEnabledRecvTransceiversForChain(forChainId); + } +}