diff --git a/evm/src/NttManager/ManagerBase.sol b/evm/src/NttManager/ManagerBase.sol index 127b88f03..46ed6b5b6 100644 --- a/evm/src/NttManager/ManagerBase.sol +++ b/evm/src/NttManager/ManagerBase.sol @@ -15,6 +15,7 @@ import "../interfaces/ITransceiver.sol"; import "../interfaces/IManagerBase.sol"; import "./TransceiverRegistry.sol"; +import "forge-std/console.sol"; // TODO - remove this - MAX abstract contract ManagerBase is IManagerBase, @@ -42,6 +43,27 @@ abstract contract ManagerBase is deployer = msg.sender; } + modifier inboundNotPaused() { + if (_getUnilateralPauseStorage().inbound) { + revert InboundPaused(); + } + _; + } + + modifier outboundNotPaused() { + if (_getUnilateralPauseStorage().outbound) { + revert OutboundPaused(); + } + _; + } + + modifier outboundWhenPaused() { + if (_getUnilateralPauseStorage().outbound == false) { + revert NotPausedForUpdate(); + } + _; + } + function _migrate() internal virtual override { _checkThresholdInvariants(); _checkTransceiversInvariants(); @@ -57,8 +79,18 @@ abstract contract ManagerBase is bytes32 private constant THRESHOLD_SLOT = bytes32(uint256(keccak256("ntt.threshold")) - 1); + bytes32 private constant UNILATERAL_PAUSE_SLOT = + bytes32(uint256(keccak256("ntt.unilateral_pause")) - 1); + // =============== Storage Getters/Setters ============================================== + function _getUnilateralPauseStorage() internal pure returns (UnilateralPause storage $) { + uint256 slot = uint256(UNILATERAL_PAUSE_SLOT); + assembly ("memory-safe") { + $.slot := slot + } + } + function _getThresholdStorage() private pure returns (_Threshold storage $) { uint256 slot = uint256(THRESHOLD_SLOT); assembly ("memory-safe") { @@ -273,6 +305,13 @@ abstract contract ManagerBase is return _getThresholdStorage().num; } + /// @inheritdoc IManagerBase + function getUnilateralPause() public view returns (UnilateralPause memory) { + UnilateralPause storage u = _getUnilateralPauseStorage(); + + return UnilateralPause({inbound: u.inbound, outbound: u.outbound}); + } + /// @inheritdoc IManagerBase function isMessageApproved(bytes32 digest) public view returns (bool) { uint8 threshold = getThreshold(); @@ -316,6 +355,14 @@ abstract contract ManagerBase is _unpause(); } + function setInboundPauseStatus(bool status) external onlyOwnerOrPauser { + _getUnilateralPauseStorage().inbound = status; + } + + function setOutboundPauseStatus(bool status) external onlyOwnerOrPauser { + _getUnilateralPauseStorage().outbound = status; + } + /// @notice Transfer ownership of the Manager contract and all Transceiver contracts to a new owner. function transferOwnership(address newOwner) public override onlyOwner { super.transferOwnership(newOwner); @@ -356,7 +403,7 @@ abstract contract ManagerBase is } /// @inheritdoc IManagerBase - function removeTransceiver(address transceiver) external onlyOwner { + function removeTransceiver(address transceiver) external onlyOwner outboundWhenPaused { _removeTransceiver(transceiver); _Threshold storage _threshold = _getThresholdStorage(); @@ -372,7 +419,7 @@ abstract contract ManagerBase is } /// @inheritdoc IManagerBase - function setThreshold(uint8 threshold) external onlyOwner { + function setThreshold(uint8 threshold) external onlyOwner outboundWhenPaused { if (threshold == 0) { revert ZeroThreshold(); } diff --git a/evm/src/NttManager/NttManager.sol b/evm/src/NttManager/NttManager.sol index 54e575e16..c0485eb05 100644 --- a/evm/src/NttManager/NttManager.sol +++ b/evm/src/NttManager/NttManager.sol @@ -64,6 +64,8 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { __PausedOwnable_init(msg.sender, msg.sender); __ReentrancyGuard_init(); _setOutboundLimit(TrimmedAmountLib.max(tokenDecimals())); + _getUnilateralPauseStorage().inbound = true; + _getUnilateralPauseStorage().outbound = true; } function _initialize() internal virtual override { @@ -104,7 +106,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { bytes32 peerContract, uint8 decimals, uint256 inboundLimit - ) public onlyOwner { + ) public onlyOwner outboundWhenPaused { if (peerChainId == 0) { revert InvalidPeerChainIdZero(); } @@ -158,7 +160,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { uint256 amount, uint16 recipientChain, bytes32 recipient - ) external payable nonReentrant whenNotPaused returns (uint64) { + ) external payable nonReentrant whenNotPaused outboundNotPaused returns (uint64) { return _transferEntryPoint(amount, recipientChain, recipient, recipient, false, new bytes(1)); } @@ -171,7 +173,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { bytes32 refundAddress, bool shouldQueue, bytes memory transceiverInstructions - ) external payable nonReentrant whenNotPaused returns (uint64) { + ) external payable nonReentrant whenNotPaused outboundNotPaused returns (uint64) { return _transferEntryPoint( amount, recipientChain, recipient, refundAddress, shouldQueue, transceiverInstructions ); @@ -182,7 +184,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { uint16 sourceChainId, bytes32 sourceNttManagerAddress, TransceiverStructs.NttManagerMessage memory payload - ) external onlyTransceiver whenNotPaused { + ) external onlyTransceiver whenNotPaused inboundNotPaused { _verifyPeer(sourceChainId, sourceNttManagerAddress); // Compute manager message digest and record transceiver attestation. @@ -198,7 +200,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { uint16 sourceChainId, bytes32 sourceNttManagerAddress, TransceiverStructs.NttManagerMessage memory message - ) public whenNotPaused { + ) public whenNotPaused inboundNotPaused { (bytes32 digest, bool alreadyExecuted) = _isMessageExecuted(sourceChainId, sourceNttManagerAddress, message); @@ -241,7 +243,12 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { } /// @inheritdoc INttManager - function completeInboundQueuedTransfer(bytes32 digest) external nonReentrant whenNotPaused { + function completeInboundQueuedTransfer(bytes32 digest) + external + nonReentrant + whenNotPaused + inboundNotPaused + { // find the message in the queue InboundQueuedTransfer memory queuedTransfer = getInboundQueuedTransfer(digest); if (queuedTransfer.txTimestamp == 0) { @@ -266,6 +273,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { payable nonReentrant whenNotPaused + outboundNotPaused returns (uint64) { // find the message in the queue diff --git a/evm/src/interfaces/IManagerBase.sol b/evm/src/interfaces/IManagerBase.sol index 66f4e13a7..33b89155c 100644 --- a/evm/src/interfaces/IManagerBase.sol +++ b/evm/src/interfaces/IManagerBase.sol @@ -31,6 +31,12 @@ interface IManagerBase { uint8 num; } + /// @dev Structure for storing pause information for inbound and outbound transfers + struct UnilateralPause { + bool inbound; + bool outbound; + } + /// @notice Emitted when a message has been attested to. /// @dev Topic0 /// 0x35a2101eaac94b493e0dfca061f9a7f087913fde8678e7cde0aca9897edba0e5. @@ -109,6 +115,18 @@ interface IManagerBase { /// @param chainId The target chain id error PeerNotRegistered(uint16 chainId); + /// @notice Error when the receiving message on inbound call when inbound calls are paused + /// @dev Selector 0xab6e758b + error InboundPaused(); + + /// @notice Error when sending a message when outbound transfers are paused + /// @dev Selector 0xe741d03c + error OutboundPaused(); + + /// @notice Error when trying to update sensitive values when the outbound is not paused + /// @dev Select 0x37291df0 + error NotPausedForUpdate(); + /// @notice Fetch the delivery price for a given recipient chain transfer. /// @param recipientChain The chain ID of the transfer destination. /// @param transceiverInstructions The transceiver specific instructions for quoting and sending @@ -165,6 +183,10 @@ interface IManagerBase { /// it to be considered valid and acted upon. function getThreshold() external view returns (uint8); + /// @notice Returns the inbound and outbound pause information for the manager. + /// This is seperate to the general pause feature + function getUnilateralPause() external view returns (UnilateralPause memory); + /// @notice Returns a boolean indicating if the transceiver has attested to the message. function transceiverAttestedToMessage( bytes32 digest, diff --git a/evm/test/IntegrationManual.t.sol b/evm/test/IntegrationManual.t.sol index 56d762996..8bc77e512 100644 --- a/evm/test/IntegrationManual.t.sol +++ b/evm/test/IntegrationManual.t.sol @@ -108,6 +108,11 @@ contract TestRelayerEndToEndManual is IntegrationHelpers, IRateLimiterEvents { } function test_relayerTransceiverAuth() public { + nttManagerChain1.setInboundPauseStatus(false); + nttManagerChain1.setOutboundPauseStatus(false); + nttManagerChain2.setInboundPauseStatus(false); + nttManagerChain2.setOutboundPauseStatus(false); + // Set up sensible WH transceiver peers _setTransceiverPeers( [wormholeTransceiverChain1, wormholeTransceiverChain2], @@ -143,7 +148,9 @@ contract TestRelayerEndToEndManual is IntegrationHelpers, IRateLimiterEvents { vm.chainId(chainId2); // Set bad manager peer (0x1) + nttManagerChain2.setOutboundPauseStatus(true); nttManagerChain2.setPeer(chainId1, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManagerChain2.setOutboundPauseStatus(false); vm.startPrank(relayer); @@ -158,7 +165,9 @@ contract TestRelayerEndToEndManual is IntegrationHelpers, IRateLimiterEvents { ); vm.stopPrank(); + nttManagerChain2.setOutboundPauseStatus(true); _setManagerPeer(nttManagerChain2, nttManagerChain1, chainId1, 9, type(uint64).max); + nttManagerChain2.setOutboundPauseStatus(false); // Wrong caller - aka not relayer contract vm.prank(userD); @@ -215,6 +224,20 @@ contract TestRelayerEndToEndManual is IntegrationHelpers, IRateLimiterEvents { } function test_relayerWithInvalidWHTransceiver() public { + require( + nttManagerChain1.getUnilateralPause().inbound == true, + "Inbound pause not true by default" + ); + require( + nttManagerChain1.getUnilateralPause().outbound == true, + "Outbound pause not true by default" + ); + + nttManagerChain1.setInboundPauseStatus(false); + nttManagerChain1.setOutboundPauseStatus(false); + nttManagerChain2.setInboundPauseStatus(false); + nttManagerChain2.setOutboundPauseStatus(false); + // Set up dodgy wormhole transceiver peers wormholeTransceiverChain2.setWormholePeer(chainId1, bytes32(uint256(uint160(address(0x1))))); wormholeTransceiverChain1.setWormholePeer( @@ -239,7 +262,7 @@ contract TestRelayerEndToEndManual is IntegrationHelpers, IRateLimiterEvents { nttManagerChain1.transfer{ value: wormholeTransceiverChain1.quoteDeliveryPrice( chainId2, buildTransceiverInstruction(false) - ) + ) }( sendingAmount, chainId2, diff --git a/evm/test/IntegrationRelayer.t.sol b/evm/test/IntegrationRelayer.t.sol index 34e6b7ae5..1b019059f 100755 --- a/evm/test/IntegrationRelayer.t.sol +++ b/evm/test/IntegrationRelayer.t.sol @@ -98,6 +98,9 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole nttManagerChain1.setOutboundLimit(type(uint64).max); nttManagerChain1.setInboundLimit(type(uint64).max, chainId2); nttManagerChain1.setThreshold(1); + + nttManagerChain1.setInboundPauseStatus(false); + nttManagerChain1.setOutboundPauseStatus(false); } // Setup the chain to relay to of the network @@ -145,8 +148,10 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2Other)); nttManagerChain2.setOutboundLimit(type(uint64).max); nttManagerChain2.setInboundLimit(type(uint64).max, chainId1); - nttManagerChain2.setThreshold(1); + + nttManagerChain2.setInboundPauseStatus(false); + nttManagerChain2.setOutboundPauseStatus(false); } function test_chainToChainReverts() public { @@ -158,9 +163,24 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole wormholeTransceiverChain2.setWormholePeer( chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) ); + + // Ensure that the enforcement for changing the peer is in place. + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotPausedForUpdate.selector)); + nttManagerChain2.setPeer( + chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 9, type(uint64).max + ); + + // Test that the threshold must be changed while in the paused state + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotPausedForUpdate.selector)); + nttManagerChain2.setThreshold(1); + + // Set the peer + nttManagerChain2.setOutboundPauseStatus(true); nttManagerChain2.setPeer( chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 9, type(uint64).max ); + nttManagerChain2.setOutboundPauseStatus(false); + DummyToken token2 = DummyTokenMintAndBurn(nttManagerChain2.token()); wormholeTransceiverChain2.setIsWormholeRelayingEnabled(chainId1, true); wormholeTransceiverChain2.setIsWormholeEvmChain(chainId1, true); @@ -171,9 +191,12 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole wormholeTransceiverChain1.setWormholePeer( chainId2, bytes32(uint256(uint160((address(wormholeTransceiverChain2))))) ); + + nttManagerChain1.setOutboundPauseStatus(true); nttManagerChain1.setPeer( chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 7, type(uint64).max ); + nttManagerChain1.setOutboundPauseStatus(false); // Enable general relaying on the chain to transfer for the funds. wormholeTransceiverChain1.setIsWormholeRelayingEnabled(chainId2, true); @@ -265,6 +288,22 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole instructions ); + // Test whether a random caller can set the inbound or outbound pause status + vm.expectRevert( + abi.encodeWithSelector(PausableUpgradeable.InvalidPauser.selector, userA) + ); + nttManagerChain1.setOutboundPauseStatus(true); + vm.expectRevert( + abi.encodeWithSelector(PausableUpgradeable.InvalidPauser.selector, userA) + ); + nttManagerChain1.setInboundPauseStatus(true); + + // Pause outbound transfers and see if this still succeeds + vm.stopPrank(); + nttManagerChain1.setOutboundPauseStatus(true); + vm.startPrank(userA); + + vm.expectRevert(abi.encodeWithSelector(IManagerBase.OutboundPaused.selector)); // Do the payment with slightly more gas than needed. This should result in a *payback* of 1 wei. nttManagerChain1.transfer{value: priceQuote1 + 1}( sendingAmount, @@ -275,6 +314,19 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole instructions ); + // Unpause then to do the transfer + vm.stopPrank(); + nttManagerChain1.setOutboundPauseStatus(false); + vm.startPrank(userA); + nttManagerChain1.transfer{value: priceQuote1 + 1}( + sendingAmount, + chainId2, + bytes32(uint256(uint160(userB))), + bytes32(uint256(uint160(userB))), + false, + instructions + ); + // Balance check on funds going in and out working as expected uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1)); uint256 userBalanceAfter = token1.balanceOf(address(userB)); @@ -319,18 +371,24 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole wormholeTransceiverChain2.setWormholePeer( chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) ); + + nttManagerChain2.setOutboundPauseStatus(true); nttManagerChain2.setPeer( chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 9, type(uint64).max ); + nttManagerChain2.setOutboundPauseStatus(false); + DummyToken token2 = DummyTokenMintAndBurn(nttManagerChain2.token()); wormholeTransceiverChain2.setIsWormholeRelayingEnabled(chainId1, true); wormholeTransceiverChain2.setIsWormholeEvmChain(chainId1, true); // Register peer contracts for the nttManager and transceiver. Transceivers and nttManager each have the concept of peers here. vm.selectFork(sourceFork); + nttManagerChain1.setOutboundPauseStatus(true); nttManagerChain1.setPeer( chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 7, type(uint64).max ); + nttManagerChain1.setOutboundPauseStatus(false); wormholeTransceiverChain1.setWormholePeer( chainId2, bytes32(uint256(uint160((address(wormholeTransceiverChain2))))) ); @@ -355,7 +413,7 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole nttManagerChain1.transfer{ value: wormholeTransceiverChain1.quoteDeliveryPrice( chainId2, buildTransceiverInstruction(false) - ) + ) }( sendingAmount, chainId2, @@ -409,7 +467,7 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole nttManagerChain2.transfer{ value: wormholeTransceiverChain2.quoteDeliveryPrice( chainId1, buildTransceiverInstruction(false) - ) + ) }( sendingAmount, chainId1, @@ -476,9 +534,12 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole [wormholeTransceiverChain1, wormholeTransceiverChain1Other], [chainId1, chainId1] ); + + nttManagerChain2.setOutboundPauseStatus(true); nttManagerChain2.setPeer( chainId1, toWormholeFormat(address(nttManagerChain1)), 9, type(uint64).max ); + nttManagerChain2.setOutboundPauseStatus(false); // setup token DummyToken token2 = DummyTokenMintAndBurn(nttManagerChain2.token()); @@ -493,10 +554,11 @@ contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, Wormhole [wormholeTransceiverChain2, wormholeTransceiverChain2Other], [chainId2, chainId2] ); + nttManagerChain1.setOutboundPauseStatus(true); nttManagerChain1.setPeer( chainId2, toWormholeFormat(address(nttManagerChain2)), 7, type(uint64).max ); - + nttManagerChain1.setOutboundPauseStatus(false); DummyToken token1 = DummyToken(nttManagerChain1.token()); uint8 decimals = token1.decimals(); diff --git a/evm/test/IntegrationStandalone.t.sol b/evm/test/IntegrationStandalone.t.sol index 3ac5c8c12..6261334b2 100755 --- a/evm/test/IntegrationStandalone.t.sol +++ b/evm/test/IntegrationStandalone.t.sol @@ -146,6 +146,11 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { // Actually set it nttManagerChain1.setThreshold(1); nttManagerChain2.setThreshold(1); + + nttManagerChain1.setInboundPauseStatus(false); + nttManagerChain1.setOutboundPauseStatus(false); + nttManagerChain2.setInboundPauseStatus(false); + nttManagerChain2.setOutboundPauseStatus(false); } function test_chainToChainBase() public { @@ -332,6 +337,13 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); vm.chainId(chainId2); + + // Check if the inbound pauser works effectively + nttManagerChain2.setInboundPauseStatus(true); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.InboundPaused.selector)); + wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + nttManagerChain2.setInboundPauseStatus(false); + { uint256 supplyBefore = token2.totalSupply(); wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); @@ -378,6 +390,14 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { encodeTransceiverInstruction(true) ); + // Check that the outbound pause status checker works on queued transactions + vm.stopPrank(); + nttManagerChain2.setOutboundPauseStatus(true); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.OutboundPaused.selector)); + nttManagerChain2.completeOutboundQueuedTransfer(0); + nttManagerChain2.setOutboundPauseStatus(false); + vm.startPrank(userC); + // Test timing on the queues vm.expectRevert( abi.encodeWithSelector( @@ -446,6 +466,14 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { Utils.fetchQueuedTransferDigestsFromLogs(vm.getRecordedLogs()); vm.warp(vm.getBlockTimestamp() + 100000); + + // Verify that the inbound paused modifier is functional for the inbound queue. + nttManagerChain1.setInboundPauseStatus(true); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.InboundPaused.selector)); + nttManagerChain1.completeInboundQueuedTransfer(queuedDigests[0]); + nttManagerChain1.setInboundPauseStatus(false); + + // Complete the transfer and remove it from the queue. nttManagerChain1.completeInboundQueuedTransfer(queuedDigests[0]); // Double redeem @@ -514,9 +542,17 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2_2)); nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1_2)); + // Ensure that a threshold change comes alongside an upgrade pauser + vm.expectRevert(abi.encodeWithSelector(IManagerBase.NotPausedForUpdate.selector)); + nttManagerChain1.setThreshold(2); + // Change the threshold from the setUp functions 1 to 2. + nttManagerChain1.setOutboundPauseStatus(true); + nttManagerChain2.setOutboundPauseStatus(true); nttManagerChain1.setThreshold(2); nttManagerChain2.setThreshold(2); + nttManagerChain1.setOutboundPauseStatus(false); + nttManagerChain2.setOutboundPauseStatus(false); // Setting up the transfer DummyToken token1 = DummyToken(nttManagerChain1.token()); diff --git a/evm/test/NttManager.t.sol b/evm/test/NttManager.t.sol index 4725d9710..61e9df7f6 100644 --- a/evm/test/NttManager.t.sol +++ b/evm/test/NttManager.t.sol @@ -12,6 +12,7 @@ import "../src/interfaces/IRateLimiterEvents.sol"; import "../src/NttManager/TransceiverRegistry.sol"; import "../src/libraries/PausableUpgradeable.sol"; import "../src/libraries/TransceiverHelpers.sol"; +import "../src/libraries/PausableOwnable.sol"; import {Utils} from "./libraries/Utils.sol"; import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; @@ -70,6 +71,11 @@ contract TestNttManager is Test, IRateLimiterEvents { dummyTransceiver = new DummyTransceiver(address(nttManager)); nttManager.setTransceiver(address(dummyTransceiver)); + + nttManager.setOutboundPauseStatus(false); + nttManager.setInboundPauseStatus(false); + nttManagerOther.setInboundPauseStatus(false); + nttManagerOther.setOutboundPauseStatus(false); } // === pure unit tests @@ -109,9 +115,12 @@ contract TestNttManager is Test, IRateLimiterEvents { uint8 decimals = t.decimals(); + nttManagerZeroRateLimiter.setOutboundPauseStatus(true); nttManagerZeroRateLimiter.setPeer( chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max ); + nttManagerZeroRateLimiter.setOutboundPauseStatus(false); + nttManagerZeroRateLimiter.setInboundPauseStatus(false); t.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -136,6 +145,7 @@ contract TestNttManager is Test, IRateLimiterEvents { // Test incoming transfer completes successfully with rate limit disabled (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerZeroRateLimiter); + nttManagerZeroRateLimiter.setOutboundPauseStatus(true); nttManagerZeroRateLimiter.setThreshold(2); // register nttManager peer @@ -144,6 +154,8 @@ contract TestNttManager is Test, IRateLimiterEvents { TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max ); + nttManagerZeroRateLimiter.setOutboundPauseStatus(false); + TransceiverStructs.NttManagerMessage memory nttManagerMessage; bytes memory transceiverMessage; (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib @@ -286,14 +298,17 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_disableReenableTransceiver() public { DummyTransceiver e = new DummyTransceiver(address(nttManager)); + nttManager.setOutboundPauseStatus(true); nttManager.setTransceiver(address(e)); nttManager.removeTransceiver(address(e)); nttManager.setTransceiver(address(e)); } function test_disableAllTransceiversFails() public { + nttManager.setOutboundPauseStatus(true); vm.expectRevert(abi.encodeWithSelector(IManagerBase.ZeroThreshold.selector)); nttManager.removeTransceiver(address(dummyTransceiver)); + nttManager.setOutboundPauseStatus(false); } function test_multipleTransceivers() public { @@ -337,8 +352,10 @@ contract TestNttManager is Test, IRateLimiterEvents { uint8 decimals = token.decimals(); + newNttManager.setOutboundPauseStatus(true); newNttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); newNttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); + newNttManager.setOutboundPauseStatus(false); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -368,7 +385,10 @@ contract TestNttManager is Test, IRateLimiterEvents { // since we register 1 in the setup DummyTransceiver e = new DummyTransceiver(address(nttManager)); nttManager.setTransceiver(address(e)); + + nttManager.setOutboundPauseStatus(true); nttManager.removeTransceiver(address(e)); + nttManager.setOutboundPauseStatus(false); // We should be able to register 64 transceivers total for (uint256 i = 0; i < 62; ++i) { @@ -390,7 +410,10 @@ contract TestNttManager is Test, IRateLimiterEvents { // since we register 1 in the setup DummyTransceiver e = new DummyTransceiver(address(nttManager)); nttManager.setTransceiver(address(e)); + + nttManager.setOutboundPauseStatus(true); nttManager.removeTransceiver(address(dummyTransceiver)); + nttManager.setOutboundPauseStatus(false); address user_A = address(0x123); address user_B = address(0x456); @@ -399,8 +422,10 @@ contract TestNttManager is Test, IRateLimiterEvents { uint8 decimals = token.decimals(); + nttManager.setOutboundPauseStatus(true); nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); + nttManager.setOutboundPauseStatus(false); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -435,7 +460,10 @@ contract TestNttManager is Test, IRateLimiterEvents { uint8 decimals = token.decimals(); + nttManager.setOutboundPauseStatus(true); nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManager.setOutboundPauseStatus(false); + nttManager.setOutboundLimit(0); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -499,6 +527,8 @@ contract TestNttManager is Test, IRateLimiterEvents { // == threshold function test_cantSetThresholdTooHigh() public { + nttManager.setOutboundPauseStatus(true); + // 1 transceiver set, so can't set threshold to 2 vm.expectRevert(abi.encodeWithSelector(IManagerBase.ThresholdTooHigh.selector, 2, 1)); nttManager.setThreshold(2); @@ -510,26 +540,31 @@ contract TestNttManager is Test, IRateLimiterEvents { nttManager.setTransceiver(address(e1)); nttManager.setTransceiver(address(e2)); + nttManager.setOutboundPauseStatus(true); nttManager.setThreshold(1); nttManager.setThreshold(2); nttManager.setThreshold(1); + nttManager.setOutboundPauseStatus(false); } function test_cantSetThresholdToZero() public { DummyTransceiver e = new DummyTransceiver(address(nttManager)); nttManager.setTransceiver(address(e)); + nttManager.setOutboundPauseStatus(true); vm.expectRevert(abi.encodeWithSelector(IManagerBase.ZeroThreshold.selector)); nttManager.setThreshold(0); } function test_onlyOwnerCanSetThreshold() public { + nttManager.setOutboundPauseStatus(true); + address notOwner = address(0x123); vm.startPrank(notOwner); - vm.expectRevert( abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, notOwner) ); + nttManager.setThreshold(1); } @@ -537,8 +572,8 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_peerRegistrationLimitsCanBeUpdated() public { bytes32 peer = toWormholeFormat(address(nttManager)); + nttManager.setOutboundPauseStatus(true); nttManager.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, 0); - IRateLimiter.RateLimitParams memory params = nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); assertEq(params.limit.getAmount(), 0); @@ -554,6 +589,7 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_onlyEnabledTransceiversCanAttest() public { (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); + nttManagerOther.setOutboundPauseStatus(true); nttManagerOther.removeTransceiver(address(e1)); bytes32 peer = toWormholeFormat(address(nttManager)); nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); @@ -571,7 +607,9 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_onlyPeerNttManagerCanAttest() public { (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); + nttManagerOther.setOutboundPauseStatus(true); nttManagerOther.setThreshold(2); + nttManagerOther.setOutboundPauseStatus(false); bytes32 peer = toWormholeFormat(address(nttManager)); @@ -592,11 +630,13 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_attestSimple() public { (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); + nttManagerOther.setOutboundPauseStatus(true); nttManagerOther.setThreshold(2); // register nttManager peer bytes32 peer = toWormholeFormat(address(nttManager)); nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); + nttManagerOther.setOutboundPauseStatus(false); TransceiverStructs.NttManagerMessage memory nttManagerMessage; bytes memory transceiverMessage; @@ -615,11 +655,13 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_attestTwice() public { (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); + nttManagerOther.setOutboundPauseStatus(true); nttManagerOther.setThreshold(2); // register nttManager peer bytes32 peer = toWormholeFormat(address(nttManager)); nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); + nttManagerOther.setOutboundPauseStatus(false); TransceiverStructs.NttManagerMessage memory nttManagerMessage; bytes memory transceiverMessage; @@ -644,10 +686,13 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_attestDisabled() public { (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); + + nttManagerOther.setOutboundPauseStatus(true); nttManagerOther.setThreshold(2); bytes32 peer = toWormholeFormat(address(nttManager)); nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); + nttManagerOther.setOutboundPauseStatus(false); ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](1); transceivers[0] = e1; @@ -664,6 +709,7 @@ contract TestNttManager is Test, IRateLimiterEvents { transceivers ); + nttManagerOther.setOutboundPauseStatus(true); nttManagerOther.removeTransceiver(address(e1)); bytes32 hash = @@ -683,8 +729,9 @@ contract TestNttManager is Test, IRateLimiterEvents { DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); - + nttManager.setOutboundPauseStatus(true); nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManager.setOutboundPauseStatus(false); nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -725,7 +772,9 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_transferWithAmountAndDecimalsThatCouldOverflow() public { // The source chain has 18 decimals trimmed to 8, and the peer has 6 decimals trimmed to 6 + nttManager.setOutboundPauseStatus(true); nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 6, type(uint64).max); + nttManager.setOutboundPauseStatus(false); address user_A = address(0x123); address user_B = address(0x456); @@ -825,12 +874,14 @@ contract TestNttManager is Test, IRateLimiterEvents { uint8 decimals = token.decimals(); + nttManager.setOutboundPauseStatus(true); nttManager.setPeer( TransceiverHelpersLib.SENDING_CHAIN_ID, toWormholeFormat(address(nttManagerOther)), 9, type(uint64).max ); + nttManager.setOutboundPauseStatus(false); nttManager.setOutboundLimit(0); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -969,7 +1020,11 @@ contract TestNttManager is Test, IRateLimiterEvents { uint256 maxAmount = 5 * 10 ** decimals; token.mintDummy(from, maxAmount); + + nttManager.setOutboundPauseStatus(true); nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManager.setOutboundPauseStatus(false); + nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); nttManager.setInboundLimit( packTrimmedAmount(type(uint64).max, 8).untrim(decimals), @@ -1095,7 +1150,10 @@ contract TestNttManager is Test, IRateLimiterEvents { // register nttManager peer and transceiver bytes32 peer = toWormholeFormat(address(nttManager)); + newNttManager.setOutboundPauseStatus(true); newNttManager.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); + newNttManager.setOutboundPauseStatus(false); + newNttManager.setInboundPauseStatus(false); { DummyTransceiver e = new DummyTransceiver(address(newNttManager)); newNttManager.setTransceiver(address(e)); @@ -1120,7 +1178,10 @@ contract TestNttManager is Test, IRateLimiterEvents { // Check that we can receive a transfer (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(newNttManager); + + newNttManager.setOutboundPauseStatus(true); newNttManager.setThreshold(1); + newNttManager.setOutboundPauseStatus(false); bytes memory transceiverMessage; bytes memory tokenTransferMessage; @@ -1214,7 +1275,9 @@ contract TestNttManager is Test, IRateLimiterEvents { uint8 decimals = token.decimals(); + nttManager.setOutboundPauseStatus(true); nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManager.setOutboundPauseStatus(false); nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -1235,4 +1298,56 @@ contract TestNttManager is Test, IRateLimiterEvents { encodedInstructions ); } + + function test_setUnilateralPauseBase() public { + // Both inbound and outbound pause should default to true on deployment + // IManagerBase.UnilateralPause memory initial_p = nttManager.getUnilateralPause(); + // require(initial_p.inbound == true, "Inital inbound pause value unset"); + // require(initial_p.outbound == true, "Inital outbound pause value unset"); + + nttManager.setInboundPauseStatus(true); + IManagerBase.UnilateralPause memory p_after_update1 = nttManager.getUnilateralPause(); + require(p_after_update1.inbound == true, "Inbound pause value unset after update"); + require(p_after_update1.outbound == false, "Outbound pause value not unset"); + + nttManager.setInboundPauseStatus(false); + IManagerBase.UnilateralPause memory p_after_update2 = nttManager.getUnilateralPause(); + require(p_after_update2.inbound == false, "Inbound pause value did not reset back to false"); + require(p_after_update2.outbound == false, "Outbound pause value not unset"); + + nttManager.setOutboundPauseStatus(true); + IManagerBase.UnilateralPause memory p_after_update3 = nttManager.getUnilateralPause(); + require(p_after_update3.inbound == false, "Inbound pause unexpectedly updated to true"); + require(p_after_update3.outbound == true, "Outbound pause value not set to true"); + + nttManager.setOutboundPauseStatus(false); + IManagerBase.UnilateralPause memory p_after_update4 = nttManager.getUnilateralPause(); + require(p_after_update4.inbound == false, "Inbound pause value changed unexpectedly"); + require( + p_after_update4.outbound == false, "Outbound pause value did not reset back to false" + ); + } + + function test_setUnilateralPauseAuth() public { + IManagerBase.UnilateralPause memory initial_p = nttManager.getUnilateralPause(); + require(initial_p.inbound == false, "Inital inbound pause value unset"); + require(initial_p.outbound == false, "Inital outbound pause value unset"); + + address user_B = address(0x456); + vm.startPrank(user_B); + + // Check if other can + vm.expectRevert(abi.encodeWithSelector(PausableUpgradeable.InvalidPauser.selector, user_B)); + nttManager.setInboundPauseStatus(false); + + vm.expectRevert(abi.encodeWithSelector(PausableUpgradeable.InvalidPauser.selector, user_B)); + nttManager.setOutboundPauseStatus(false); + + vm.stopPrank(); + nttManager.setInboundPauseStatus(true); + nttManager.setOutboundPauseStatus(true); + IManagerBase.UnilateralPause memory p_after_update1 = nttManager.getUnilateralPause(); + require(p_after_update1.inbound == true, "Inbound pause value unset after update"); + require(p_after_update1.outbound == true, "Outbound pause value unset after update"); + } } diff --git a/evm/test/RateLimit.t.sol b/evm/test/RateLimit.t.sol index 159246e20..e5b9ae667 100644 --- a/evm/test/RateLimit.t.sol +++ b/evm/test/RateLimit.t.sol @@ -50,6 +50,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { DummyTransceiver e = new DummyTransceiver(address(nttManager)); nttManager.setTransceiver(address(e)); + + nttManager.setInboundPauseStatus(false); + nttManager.setOutboundPauseStatus(false); } function test_outboundRateLimit_setLimitSimple() public { diff --git a/evm/test/Upgrades.t.sol b/evm/test/Upgrades.t.sol index ceda71e7b..88f3f7fb8 100644 --- a/evm/test/Upgrades.t.sol +++ b/evm/test/Upgrades.t.sol @@ -441,6 +441,10 @@ contract TestUpgrades is Test, IRateLimiterEvents { } function basicFunctionality() public { + nttManagerChain1.setInboundPauseStatus(false); + nttManagerChain1.setOutboundPauseStatus(false); + nttManagerChain2.setInboundPauseStatus(false); + nttManagerChain2.setOutboundPauseStatus(false); vm.chainId(chainId1); // Setting up the transfer @@ -583,6 +587,10 @@ contract TestUpgrades is Test, IRateLimiterEvents { } vm.stopPrank(); + nttManagerChain1.setInboundPauseStatus(true); + nttManagerChain1.setOutboundPauseStatus(true); + nttManagerChain2.setInboundPauseStatus(true); + nttManagerChain2.setOutboundPauseStatus(true); } function encodeTransceiverInstruction(bool relayer_off) public view returns (bytes memory) { diff --git a/evm/test/libraries/TransceiverHelpers.sol b/evm/test/libraries/TransceiverHelpers.sol index 11a3551db..ee88b7623 100644 --- a/evm/test/libraries/TransceiverHelpers.sol +++ b/evm/test/libraries/TransceiverHelpers.sol @@ -19,11 +19,14 @@ library TransceiverHelpersLib { internal returns (DummyTransceiver, DummyTransceiver) { + nttManager.setOutboundPauseStatus(true); DummyTransceiver e1 = new DummyTransceiver(address(nttManager)); DummyTransceiver e2 = new DummyTransceiver(address(nttManager)); nttManager.setTransceiver(address(e1)); nttManager.setTransceiver(address(e2)); nttManager.setThreshold(2); + nttManager.setOutboundPauseStatus(false); + return (e1, e2); } @@ -98,9 +101,14 @@ library TransceiverHelpersLib { ) internal { DummyToken token = DummyToken(nttManager.token()); token.mintDummy(address(recipientNttManager), amount.untrim(token.decimals())); + + nttManager.setOutboundPauseStatus(true); + recipientNttManager.setOutboundPauseStatus(true); NttManagerHelpersLib.setConfigs( inboundLimit, nttManager, recipientNttManager, token.decimals() ); + nttManager.setOutboundPauseStatus(false); + recipientNttManager.setOutboundPauseStatus(false); } function buildTransceiverMessageWithNttManagerPayload(