diff --git a/.gitmodules b/.gitmodules index 80c46c6e3..3aa2bb541 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,9 @@ [submodule "evm/lib/solidity-bytes-utils"] path = evm/lib/solidity-bytes-utils url = https://github.com/GNSPS/solidity-bytes-utils +[submodule "evm/lib/example-messaging-endpoint"] + path = evm/lib/example-messaging-endpoint + url = https://github.com/wormholelabs-xyz/example-messaging-endpoint +[submodule "evm/lib/example-messaging-executor"] + path = evm/lib/example-messaging-executor + url = https://github.com/wormholelabs-xyz/example-messaging-executor diff --git a/evm/echidna/FuzzNttManager.sol b/evm/echidna/FuzzNttManager.sol index d3c90de56..03f507648 100644 --- a/evm/echidna/FuzzNttManager.sol +++ b/evm/echidna/FuzzNttManager.sol @@ -7,7 +7,7 @@ import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "../src/interfaces/IManagerBase.sol"; import "../src/interfaces/INttManager.sol"; import "../src/interfaces/IRateLimiter.sol"; -import "../src/NttManager/TransceiverRegistry.sol"; +import "../src/NttManager/AdapterRegistry.sol"; import "../src/libraries/external/OwnableUpgradeable.sol"; import "../src/libraries/TransceiverStructs.sol"; import "./helpers/FuzzingHelpers.sol"; @@ -20,7 +20,7 @@ contract FuzzNttManager is FuzzingHelpers { uint64[] queuedOutboundTransfersArray; mapping(uint64 => bool) queuedOutboundTransfers; mapping(uint256 => bool) executedQueuedOutboundTransfers; - + // Keep track of transceiver state uint256 numRegisteredTransceivers; uint256 numEnabledTransceivers; @@ -33,7 +33,6 @@ contract FuzzNttManager is FuzzingHelpers { bytes[] orderedInstructions; bytes[] unorderedInstructions; - NttManager nttManager; DummyToken dummyToken; DummyTransceiver dummyTransceiver; @@ -41,24 +40,31 @@ contract FuzzNttManager is FuzzingHelpers { constructor() { _initialManagerSetup(); _generateMultipleTransceiverInstructions(numTransceiverInstructions); - + dummyToken.mintDummy(address(this), type(uint256).max); IERC20(dummyToken).approve(address(nttManager), type(uint256).max); } - function transferShouldSeizeTheRightAmount(uint256 amount, uint256 randomAddress, uint16 recipientChainId) public { + function transferShouldSeizeTheRightAmount( + uint256 amount, + uint256 randomAddress, + uint16 recipientChainId + ) public { INttManager.NttManagerPeer memory peer = nttManager.getPeer(recipientChainId); randomAddress = clampBetween(randomAddress, 1, type(uint256).max); - bytes32 validAddress = bytes32(randomAddress); + bytes32 validAddress = bytes32(randomAddress); uint8 decimals = ERC20(dummyToken).decimals(); - uint8 minDecimals = minUint8(8, minUint8(decimals, peer.tokenDecimals)); + uint8 minDecimals = minUint8(8, minUint8(decimals, peer.tokenDecimals)); - amount = clampBetween(amount, 10 ** (decimals - minDecimals), type(uint64).max * 10 ** (decimals - minUint8(8, decimals))); + amount = clampBetween( + amount, + 10 ** (decimals - minDecimals), + type(uint64).max * 10 ** (decimals - minUint8(8, decimals)) + ); amount = TrimmedAmountLib.scale(amount, decimals, minDecimals); amount = TrimmedAmountLib.scale(amount, minDecimals, decimals); - uint256 nttManagerBalanceBefore = IERC20(dummyToken).balanceOf(address(nttManager)); uint256 thisAddressBalanceBefore = IERC20(dummyToken).balanceOf(address(this)); @@ -67,17 +73,21 @@ contract FuzzNttManager is FuzzingHelpers { uint64 nextMessageSequence = nttManager.nextMessageSequence(); // We always queue to ensure we're always seizing the tokens - try nttManager.transfer(amount, recipientChainId, validAddress, validAddress, true, new bytes(1)) { + try nttManager.transfer( + amount, recipientChainId, validAddress, validAddress, true, new bytes(0) + ) { assert(IERC20(dummyToken).balanceOf(address(this)) == thisAddressBalanceBefore - amount); - assert(IERC20(dummyToken).balanceOf(address(nttManager)) == amount + nttManagerBalanceBefore); + assert( + IERC20(dummyToken).balanceOf(address(nttManager)) + == amount + nttManagerBalanceBefore + ); // This transfer has queued, so let's keep track of it if (amount > currentOutboundCapacity) { queuedOutboundTransfersArray.push(nextMessageSequence); queuedOutboundTransfers[nextMessageSequence] = true; } - } - catch (bytes memory revertData) { + } catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); if (amount == 0) { @@ -85,41 +95,42 @@ contract FuzzNttManager is FuzzingHelpers { errorSelector == selectorToUint(INttManager.ZeroAmount.selector), "NttManager: transfer expected to fail if sending with 0 amount" ); - } - else if (peer.tokenDecimals == 0) { + } else if (peer.tokenDecimals == 0) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidPeerDecimals.selector), "NttManager: transfer expected to fail if sending to a peer with 0 decimals" ); - } - else if (numEnabledTransceivers == 0) { + } else if (numEnabledTransceivers == 0) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.NoEnabledTransceivers.selector), "NttManager: transfer expected to fail if sending when no transceivers are enabled" ); - } - else if (peer.peerAddress == bytes32(0)) { + } else if (peer.peerAddress == bytes32(0)) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.PeerNotRegistered.selector), "NttManager: transfer expected to fail if sending to an unset peer" ); - } - else { - assertWithMsg( - false, - "NttManager: transfer unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: transfer unexpected revert"); } } } - function transferWithQoLEntrypoint(uint256 amount, uint16 recipientChainId, bytes32 recipient) public { + function transferWithQoLEntrypoint( + uint256 amount, + uint16 recipientChainId, + bytes32 recipient + ) public { INttManager.NttManagerPeer memory peer = nttManager.getPeer(recipientChainId); uint8 decimals = ERC20(dummyToken).decimals(); - uint8 minDecimals = minUint8(8, minUint8(decimals, peer.tokenDecimals)); + uint8 minDecimals = minUint8(8, minUint8(decimals, peer.tokenDecimals)); - amount = clampBetween(amount, 10 ** (decimals - minDecimals), type(uint64).max * 10 ** (decimals - minUint8(8, decimals))); + amount = clampBetween( + amount, + 10 ** (decimals - minDecimals), + type(uint64).max * 10 ** (decimals - minUint8(8, decimals)) + ); amount = TrimmedAmountLib.scale(amount, decimals, minDecimals); amount = TrimmedAmountLib.scale(amount, minDecimals, decimals); @@ -135,21 +146,25 @@ contract FuzzNttManager is FuzzingHelpers { ); // Check we increase the inbound rate limit as expected - IRateLimiter.RateLimitParams memory inboundParams = nttManager.getInboundLimitParams(recipientChainId); - assertWithMsg( - nttManager.getCurrentInboundCapacity(recipientChainId) == minUint256(currentInboundCapacity + amount, TrimmedAmountLib.scale(TrimmedAmountLib.getAmount(inboundParams.limit), minUint8(8, decimals), decimals)), + IRateLimiter.RateLimitParams memory inboundParams = + nttManager.getInboundLimitParams(recipientChainId); + assertWithMsg( + nttManager.getCurrentInboundCapacity(recipientChainId) + == minUint256( + currentInboundCapacity + amount, + TrimmedAmountLib.scale( + TrimmedAmountLib.getAmount(inboundParams.limit), + minUint8(8, decimals), + decimals + ) + ), "NttManager: transfer inbound rate limit not increased as expected" ); - } - else { + } else { // We were able to transfer when we shouldn't have been able to - assertWithMsg( - false, - "NttManager: transfer unexpectedly succeeded" - ); + assertWithMsg(false, "NttManager: transfer unexpectedly succeeded"); } - } - catch (bytes memory revertData) { + } catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); if (amount == 0) { @@ -157,53 +172,53 @@ contract FuzzNttManager is FuzzingHelpers { errorSelector == selectorToUint(INttManager.ZeroAmount.selector), "NttManager: transfer expected to fail if sending with 0 amount" ); - } - else if (recipient == bytes32(0)) { + } else if (recipient == bytes32(0)) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidRecipient.selector), "NttManager: transfer expected to fail if sending to 0 address" ); - } - else if (peer.tokenDecimals == 0) { + } else if (peer.tokenDecimals == 0) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidPeerDecimals.selector), "NttManager: transfer expected to fail if sending to a peer with 0 decimals" ); - } - else if (amount > currentOutboundCapacity) { + } else if (amount > currentOutboundCapacity) { assertWithMsg( errorSelector == selectorToUint(IRateLimiter.NotEnoughCapacity.selector), "NttManager: transfer expected to fail if exceeding rate limit" ); - } - else if (numEnabledTransceivers == 0) { + } else if (numEnabledTransceivers == 0) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.NoEnabledTransceivers.selector), "NttManager: transfer expected to fail if sending when no transceivers are enabled" ); - } - else if (peer.peerAddress == bytes32(0)) { + } else if (peer.peerAddress == bytes32(0)) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.PeerNotRegistered.selector), "NttManager: transfer expected to fail if sending to an unset peer" ); - } - else { - assertWithMsg( - false, - "NttManager: transfer unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: transfer unexpected revert"); } } } - function transferShouldQueueProperly(uint256 amount, uint16 recipientChainId, bytes32 recipient, bool shouldQueue) public { + function transferShouldQueueProperly( + uint256 amount, + uint16 recipientChainId, + bytes32 recipient, + bool shouldQueue + ) public { INttManager.NttManagerPeer memory peer = nttManager.getPeer(recipientChainId); - + uint8 decimals = ERC20(dummyToken).decimals(); - uint8 minDecimals = minUint8(8, minUint8(decimals, peer.tokenDecimals)); + uint8 minDecimals = minUint8(8, minUint8(decimals, peer.tokenDecimals)); - amount = clampBetween(amount, 10 ** (decimals - minDecimals), type(uint64).max * 10 ** (decimals - minUint8(8, decimals))); + amount = clampBetween( + amount, + 10 ** (decimals - minDecimals), + type(uint64).max * 10 ** (decimals - minUint8(8, decimals)) + ); amount = TrimmedAmountLib.scale(amount, decimals, minDecimals); amount = TrimmedAmountLib.scale(amount, minDecimals, decimals); @@ -211,20 +226,24 @@ contract FuzzNttManager is FuzzingHelpers { uint256 currentInboundCapacity = nttManager.getCurrentInboundCapacity(recipientChainId); uint64 nextMessageSequence = nttManager.nextMessageSequence(); - try nttManager.transfer(amount, recipientChainId, recipient, recipient, shouldQueue, new bytes(1)) { + try nttManager.transfer( + amount, recipientChainId, recipient, recipient, shouldQueue, new bytes(0) + ) { // If we queued, we should have an item in the queue if ((shouldQueue && amount > currentOutboundCapacity)) { // This transfer has queued, so let's keep track of it queuedOutboundTransfersArray.push(nextMessageSequence); queuedOutboundTransfers[nextMessageSequence] = true; - IRateLimiter.OutboundQueuedTransfer memory queuedTransfer = nttManager.getOutboundQueuedTransfer(nextMessageSequence); + IRateLimiter.OutboundQueuedTransfer memory queuedTransfer = + nttManager.getOutboundQueuedTransfer(nextMessageSequence); assertWithMsg( queuedTransfer.recipient == recipient, "NttManager: transfer queued recipient unexpected" ); assertWithMsg( - TrimmedAmountLib.getAmount(queuedTransfer.amount) == TrimmedAmountLib.scale(amount, decimals, minDecimals), + TrimmedAmountLib.getAmount(queuedTransfer.amount) + == TrimmedAmountLib.scale(amount, decimals, minDecimals), "NttManager: transfer queued amount unexpected" ); assertWithMsg( @@ -257,14 +276,22 @@ contract FuzzNttManager is FuzzingHelpers { ); // Check we increase the inbound rate limit as expected - IRateLimiter.RateLimitParams memory inboundParams = nttManager.getInboundLimitParams(recipientChainId); - assertWithMsg( - nttManager.getCurrentInboundCapacity(recipientChainId) == minUint256(currentInboundCapacity + amount, TrimmedAmountLib.scale(TrimmedAmountLib.getAmount(inboundParams.limit), minUint8(8, decimals), decimals)), + IRateLimiter.RateLimitParams memory inboundParams = + nttManager.getInboundLimitParams(recipientChainId); + assertWithMsg( + nttManager.getCurrentInboundCapacity(recipientChainId) + == minUint256( + currentInboundCapacity + amount, + TrimmedAmountLib.scale( + TrimmedAmountLib.getAmount(inboundParams.limit), + minUint8(8, decimals), + decimals + ) + ), "NttManager: transfer inbound rate limit not increased as expected" ); } - } - catch (bytes memory revertData) { + } catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); if (amount == 0) { @@ -272,47 +299,45 @@ contract FuzzNttManager is FuzzingHelpers { errorSelector == selectorToUint(INttManager.ZeroAmount.selector), "NttManager: transfer expected to fail if sending with 0 amount" ); - } - else if (recipient == bytes32(0)) { + } else if (recipient == bytes32(0)) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidRecipient.selector), "NttManager: transfer expected to fail if sending to 0 address" ); - } - else if (peer.tokenDecimals == 0) { + } else if (peer.tokenDecimals == 0) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidPeerDecimals.selector), "NttManager: transfer expected to fail if sending to a peer with 0 decimals" ); - } - else if (!shouldQueue && amount > currentOutboundCapacity) { + } else if (!shouldQueue && amount > currentOutboundCapacity) { assertWithMsg( errorSelector == selectorToUint(IRateLimiter.NotEnoughCapacity.selector), "NttManager: transfer expected to fail if exceeding rate limit and not queueing" ); - } - else if (numEnabledTransceivers == 0) { + } else if (numEnabledTransceivers == 0) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.NoEnabledTransceivers.selector), "NttManager: transfer expected to fail if sending when no transceivers are enabled" ); - } - else if (peer.peerAddress == bytes32(0)) { + } else if (peer.peerAddress == bytes32(0)) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.PeerNotRegistered.selector), "NttManager: transfer expected to fail if sending to an unset peer" ); - } - else { - assertWithMsg( - false, - "NttManager: transfer unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: transfer unexpected revert"); } } } - function transferWithCustomTransceiverInstructions(uint256 amount, uint16 recipientChainId, bytes32 recipient, bool isOrdered, uint256 instructionIndex, bool shouldQueue) public { + function transferWithCustomTransceiverInstructions( + uint256 amount, + uint16 recipientChainId, + bytes32 recipient, + bool isOrdered, + uint256 instructionIndex, + bool shouldQueue + ) public { INttManager.NttManagerPeer memory peer = nttManager.getPeer(recipientChainId); instructionIndex = clampBetween(instructionIndex, 0, numTransceiverInstructions - 1); @@ -320,30 +345,34 @@ contract FuzzNttManager is FuzzingHelpers { if (isOrdered) { encodedInstructions = orderedInstructions[instructionIndex]; - } - else { + } else { encodedInstructions = unorderedInstructions[instructionIndex]; } uint8 decimals = ERC20(dummyToken).decimals(); - uint8 minDecimals = minUint8(8, minUint8(decimals, peer.tokenDecimals)); + uint8 minDecimals = minUint8(8, minUint8(decimals, peer.tokenDecimals)); - amount = clampBetween(amount, 10 ** (decimals - minDecimals), type(uint64).max * 10 ** (decimals - minUint8(8, decimals))); + amount = clampBetween( + amount, + 10 ** (decimals - minDecimals), + type(uint64).max * 10 ** (decimals - minUint8(8, decimals)) + ); amount = TrimmedAmountLib.scale(amount, decimals, minDecimals); amount = TrimmedAmountLib.scale(amount, minDecimals, decimals); uint256 currentOutboundCapacity = nttManager.getCurrentOutboundCapacity(); uint64 nextMessageSequence = nttManager.nextMessageSequence(); - - try nttManager.transfer(amount, recipientChainId, recipient, recipient, shouldQueue, encodedInstructions) { + + try nttManager.transfer( + amount, recipientChainId, recipient, recipient, shouldQueue, encodedInstructions + ) { // If we queued, we should have an item in the queue if ((shouldQueue && amount > currentOutboundCapacity)) { // This transfer has queued, so let's keep track of it queuedOutboundTransfersArray.push(nextMessageSequence); queuedOutboundTransfers[nextMessageSequence] = true; } - } - catch (bytes memory revertData) { + } catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); if (amount == 0) { @@ -351,40 +380,36 @@ contract FuzzNttManager is FuzzingHelpers { errorSelector == selectorToUint(INttManager.ZeroAmount.selector), "NttManager: transfer expected to fail if sending with 0 amount" ); - } - else if (recipient == bytes32(0)) { + } else if (recipient == bytes32(0)) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidRecipient.selector), "NttManager: transfer expected to fail if sending to 0 address" ); - } - else if (peer.tokenDecimals == 0) { + } else if (peer.tokenDecimals == 0) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidPeerDecimals.selector), "NttManager: transfer expected to fail if sending to a peer with 0 decimals" ); - } - else if (!shouldQueue && amount > currentOutboundCapacity) { + } else if (!shouldQueue && amount > currentOutboundCapacity) { assertWithMsg( errorSelector == selectorToUint(IRateLimiter.NotEnoughCapacity.selector), "NttManager: transfer expected to fail if exceeding rate limit and not queueing" ); - } - else if (numEnabledTransceivers == 0) { + } else if (numEnabledTransceivers == 0) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.NoEnabledTransceivers.selector), "NttManager: transfer expected to fail if sending when no transceivers are enabled" ); - } - else if (peer.peerAddress == bytes32(0)) { + } else if (peer.peerAddress == bytes32(0)) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.PeerNotRegistered.selector), "NttManager: transfer expected to fail if sending to an unset peer" ); - } - else if (errorSelector == selectorToUint(TransceiverStructs.InvalidInstructionIndex.selector)) { - TransceiverStructs.TransceiverInstruction[] memory instructions = - TransceiverStructs.parseTransceiverInstructions(encodedInstructions, numRegisteredTransceivers); + } else if ( + errorSelector == selectorToUint(TransceiverStructs.InvalidInstructionIndex.selector) + ) { + TransceiverStructs.TransceiverInstruction[] memory instructions = TransceiverStructs + .parseTransceiverInstructions(encodedInstructions, numRegisteredTransceivers); for (uint256 i = 0; i < instructions.length; ++i) { if (instructions[i].index < numRegisteredTransceivers) { assertWithMsg( @@ -393,67 +418,76 @@ contract FuzzNttManager is FuzzingHelpers { ); } } - } - else { - assertWithMsg( - false, - "NttManager: transfer unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: transfer unexpected revert"); } } } - function canOnlyPlayQueuedOutboundTransferOnce(uint256 warpTime, uint64 messageSequence, bool pickQueuedSequence) public { + function canOnlyPlayQueuedOutboundTransferOnce( + uint256 warpTime, + uint64 messageSequence, + bool pickQueuedSequence + ) public { if (pickQueuedSequence) { - messageSequence = queuedOutboundTransfersArray[clampBetween(uint256(messageSequence), 0, queuedOutboundTransfersArray.length)]; + messageSequence = queuedOutboundTransfersArray[clampBetween( + uint256(messageSequence), 0, queuedOutboundTransfersArray.length + )]; } - IRateLimiter.OutboundQueuedTransfer memory queuedTransfer = nttManager.getOutboundQueuedTransfer(messageSequence); - + IRateLimiter.OutboundQueuedTransfer memory queuedTransfer = + nttManager.getOutboundQueuedTransfer(messageSequence); + warpTime = clampBetween(warpTime, 0, 365 days); uint64 newTimestamp = uint64(block.timestamp + warpTime); // We can safely cast here as the warp is clamped hevm.warp(newTimestamp); uint256 currentOutboundCapacity = nttManager.getCurrentOutboundCapacity(); - uint256 currentInboundCapacity = nttManager.getCurrentInboundCapacity(queuedTransfer.recipientChain); + uint256 currentInboundCapacity = + nttManager.getCurrentInboundCapacity(queuedTransfer.recipientChain); try nttManager.completeOutboundQueuedTransfer(messageSequence) { // Rate limits stay untouched assertWithMsg( - currentOutboundCapacity == nttManager.getCurrentOutboundCapacity() && - currentInboundCapacity == nttManager.getCurrentInboundCapacity(queuedTransfer.recipientChain), + currentOutboundCapacity == nttManager.getCurrentOutboundCapacity() + && currentInboundCapacity + == nttManager.getCurrentInboundCapacity(queuedTransfer.recipientChain), "NttManager: completeOutboundQueuedTransfer expected not to change rate limit capacity" ); // Set that this message sequence has been played executedQueuedOutboundTransfers[messageSequence] = true; - } - catch (bytes memory revertData) { + } catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); // If the message has been executed before it shouldn't be found again if (executedQueuedOutboundTransfers[messageSequence]) { assertWithMsg( - errorSelector == selectorToUint(IRateLimiter.OutboundQueuedTransferNotFound.selector), + errorSelector + == selectorToUint(IRateLimiter.OutboundQueuedTransferNotFound.selector), "NttManager: completeOutboundQueuedTransfer expected to fail if not found" ); } if (queuedTransfer.txTimestamp == 0) { assertWithMsg( - errorSelector == selectorToUint(IRateLimiter.OutboundQueuedTransferNotFound.selector), + errorSelector + == selectorToUint(IRateLimiter.OutboundQueuedTransferNotFound.selector), "NttManager: completeOutboundQueuedTransfer expected to fail if not found" ); - } - else if (newTimestamp - queuedTransfer.txTimestamp < 1 days) { + } else if (newTimestamp - queuedTransfer.txTimestamp < 1 days) { assertWithMsg( - errorSelector == selectorToUint(IRateLimiter.OutboundQueuedTransferStillQueued.selector), + errorSelector + == selectorToUint(IRateLimiter.OutboundQueuedTransferStillQueued.selector), "NttManager: completeOutboundQueuedTransfer expected to fail if not queued for long enough" ); - } - else if (errorSelector == selectorToUint(TransceiverStructs.InvalidInstructionIndex.selector)) { - TransceiverStructs.TransceiverInstruction[] memory instructions = - TransceiverStructs.parseTransceiverInstructions(queuedTransfer.transceiverInstructions, numRegisteredTransceivers); + } else if ( + errorSelector == selectorToUint(TransceiverStructs.InvalidInstructionIndex.selector) + ) { + TransceiverStructs.TransceiverInstruction[] memory instructions = TransceiverStructs + .parseTransceiverInstructions( + queuedTransfer.transceiverInstructions, numRegisteredTransceivers + ); for (uint256 i = 0; i < instructions.length; ++i) { if (instructions[i].index < numRegisteredTransceivers) { assertWithMsg( @@ -462,36 +496,35 @@ contract FuzzNttManager is FuzzingHelpers { ); } } - } - else if (numEnabledTransceivers == 0) { + } else if (numEnabledTransceivers == 0) { // In this case the sender should be able to cancel their outbound queued transfer try nttManager.cancelOutboundQueuedTransfer(messageSequence) { // Set that this message sequence has been played executedQueuedOutboundTransfers[messageSequence] = true; - } - catch { + } catch { assertWithMsg( - false, - "NttManager: cancelOutboundQueuedTransfer unexpected revert" + false, "NttManager: cancelOutboundQueuedTransfer unexpected revert" ); } + } else { + assertWithMsg(false, "NttManager: completeOutboundQueuedTransfer unexpected revert"); } - else { - assertWithMsg( - false, - "NttManager: completeOutboundQueuedTransfer unexpected revert" - ); - } - } } - function cancelOutboundQueuedTransfer(bool pickQueuedSequence, uint64 messageSequence, address sender) public { + function cancelOutboundQueuedTransfer( + bool pickQueuedSequence, + uint64 messageSequence, + address sender + ) public { if (pickQueuedSequence) { - messageSequence = queuedOutboundTransfersArray[clampBetween(uint256(messageSequence), 0, queuedOutboundTransfersArray.length)]; + messageSequence = queuedOutboundTransfersArray[clampBetween( + uint256(messageSequence), 0, queuedOutboundTransfersArray.length + )]; } - IRateLimiter.OutboundQueuedTransfer memory queuedTransfer = nttManager.getOutboundQueuedTransfer(messageSequence); + IRateLimiter.OutboundQueuedTransfer memory queuedTransfer = + nttManager.getOutboundQueuedTransfer(messageSequence); uint256 nttManagerBalanceBefore = IERC20(dummyToken).balanceOf(address(nttManager)); uint256 senderBalanceBefore = IERC20(dummyToken).balanceOf(address(queuedTransfer.sender)); @@ -500,64 +533,71 @@ contract FuzzNttManager is FuzzingHelpers { hevm.prank(sender); try nttManager.cancelOutboundQueuedTransfer(messageSequence) { - assert(IERC20(dummyToken).balanceOf(address(queuedTransfer.sender)) == senderBalanceBefore + amount); - assert(IERC20(dummyToken).balanceOf(address(nttManager)) == nttManagerBalanceBefore - amount); + assert( + IERC20(dummyToken).balanceOf(address(queuedTransfer.sender)) + == senderBalanceBefore + amount + ); + assert( + IERC20(dummyToken).balanceOf(address(nttManager)) + == nttManagerBalanceBefore - amount + ); // Set that this message sequence has been played since this has the same effect as executing an outbound transfer executedQueuedOutboundTransfers[messageSequence] = true; - } - catch (bytes memory revertData) { + } catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); - if (executedQueuedOutboundTransfers[messageSequence] || !queuedOutboundTransfers[messageSequence]) { + if ( + executedQueuedOutboundTransfers[messageSequence] + || !queuedOutboundTransfers[messageSequence] + ) { assertWithMsg( - errorSelector == selectorToUint(IRateLimiter.OutboundQueuedTransferNotFound.selector), + errorSelector + == selectorToUint(IRateLimiter.OutboundQueuedTransferNotFound.selector), "NttManager: cancelOutboundQueuedTransfer expected to fail if not found or already executed/cancelled" ); - } - else if (sender != queuedTransfer.sender) { + } else if (sender != queuedTransfer.sender) { assertWithMsg( errorSelector == selectorToUint(INttManager.CancellerNotSender.selector), "NttManager: cancelOutboundQueuedTransfer expected to fail if called by a different sender" ); - } - else { - assertWithMsg( - false, - "NttManager: cancelOutboundQueuedTransfer unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: cancelOutboundQueuedTransfer unexpected revert"); } } } - function setPeer(uint16 peerChainId, + function setPeer( + uint16 peerChainId, bytes32 peerContract, uint8 decimals, uint256 inboundLimit, bool clampLimit ) public { uint8 localDecimals = ERC20(dummyToken).decimals(); - if (clampLimit) inboundLimit = clampBetween(inboundLimit, 0, type(uint64).max * 10 ** (localDecimals - minUint8(8, localDecimals))); - - try nttManager.setPeer(peerChainId, peerContract, decimals, inboundLimit) { - + if (clampLimit) { + inboundLimit = clampBetween( + inboundLimit, + 0, + type(uint64).max * 10 ** (localDecimals - minUint8(8, localDecimals)) + ); } + + try nttManager.setPeer(peerChainId, peerContract, decimals, 10000000000, inboundLimit) {} catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); - + if (peerChainId == 0) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidPeerChainIdZero.selector), "NttManager: setPeer expected to fail if setting zero peer chain id" ); - } - else if (peerContract == bytes32(0)) { + } else if (peerContract == bytes32(0)) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidPeerZeroAddress.selector), "NttManager: setPeer expected to fail if setting zero peer contract address" ); - } - else if (decimals == 0) { + } else if (decimals == 0) { assertWithMsg( errorSelector == selectorToUint(INttManager.InvalidPeerDecimals.selector), "NttManager: setPeer expected to fail if setting zero peer decimals" @@ -569,83 +609,76 @@ contract FuzzNttManager is FuzzingHelpers { errorSelector == selectorToUint(INttManager.InvalidPeerSameChainId.selector), "NttManager: setPeer expected to fail if setting for the same chain id" ); - } - else if (!clampLimit) { + } else if (!clampLimit) { bytes32 errorStringHash = extractErrorString(revertData); assertWithMsg( - errorStringHash == keccak256(abi.encodePacked("SafeCast: value doesn't fit in 64 bits")), + errorStringHash + == keccak256(abi.encodePacked("SafeCast: value doesn't fit in 64 bits")), "NttManager: setPeer expected to fail if setting too large an inbound limit" ); - } - else { - assertWithMsg( - false, - "NttManager: setPeer unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: setPeer unexpected revert"); } } } function setOutboundLimit(uint256 limit, bool clampLimit) public { uint8 localDecimals = ERC20(dummyToken).decimals(); - if (clampLimit) limit = clampBetween(limit, 0, type(uint64).max * 10 ** (localDecimals - minUint8(8, localDecimals))); - - try nttManager.setOutboundLimit(limit) { - + if (clampLimit) { + limit = clampBetween( + limit, 0, type(uint64).max * 10 ** (localDecimals - minUint8(8, localDecimals)) + ); } + + try nttManager.setOutboundLimit(limit) {} catch (bytes memory revertData) { if (!clampLimit) { bytes32 errorStringHash = extractErrorString(revertData); assertWithMsg( - errorStringHash == keccak256(abi.encodePacked("SafeCast: value doesn't fit in 64 bits")), + errorStringHash + == keccak256(abi.encodePacked("SafeCast: value doesn't fit in 64 bits")), "NttManager: setOutboundLimit expected to fail if setting too large a limit" ); - } - else { - assertWithMsg( - false, - "NttManager: setOutboundLimit unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: setOutboundLimit unexpected revert"); } } } function setInboundLimit(uint256 limit, uint16 chainId, bool clampLimit) public { uint8 localDecimals = ERC20(dummyToken).decimals(); - if (clampLimit) limit = clampBetween(limit, 0, type(uint64).max * 10 ** (localDecimals - minUint8(8, localDecimals))); - - try nttManager.setInboundLimit(limit, chainId) { - + if (clampLimit) { + limit = clampBetween( + limit, 0, type(uint64).max * 10 ** (localDecimals - minUint8(8, localDecimals)) + ); } + + try nttManager.setInboundLimit(limit, chainId) {} catch (bytes memory revertData) { if (!clampLimit) { bytes32 errorStringHash = extractErrorString(revertData); assertWithMsg( - errorStringHash == keccak256(abi.encodePacked("SafeCast: value doesn't fit in 64 bits")), + errorStringHash + == keccak256(abi.encodePacked("SafeCast: value doesn't fit in 64 bits")), "NttManager: setInboundLimit expected to fail if setting too large a limit" ); - } - else { - assertWithMsg( - false, - "NttManager: setInboundLimit unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: setInboundLimit unexpected revert"); } } } function setTransceiver(bool newTransceiver, uint256 transceiverIndex) public { address transceiver; - + if (newTransceiver) { DummyTransceiver newDummyTransceiver = new DummyTransceiver(address(nttManager)); transceiver = address(newDummyTransceiver); - } - else { + } else { transceiverIndex = clampBetween(transceiverIndex, 0, registeredTransceivers.length - 1); transceiver = registeredTransceivers[transceiverIndex]; } - + try nttManager.setTransceiver(transceiver) { // We only set these if the transceiver wasn't registered before if (!isTransceiverRegistered[transceiver]) { @@ -656,92 +689,79 @@ contract FuzzNttManager is FuzzingHelpers { isTransceiverEnabled[transceiver] = true; numEnabledTransceivers++; - } - catch (bytes memory revertData) { + } catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); if (isTransceiverRegistered[transceiver]) { assertWithMsg( - errorSelector == selectorToUint(TransceiverRegistry.TransceiverAlreadyEnabled.selector), + errorSelector == selectorToUint(AdapterRegistry.AdapterAlreadyEnabled.selector), "NttManager: setTransceiver expected to fail if enabling an already enabled transceiver" ); - } - else if (transceiver == address(0)) { + } else if (transceiver == address(0)) { assertWithMsg( - errorSelector == selectorToUint(TransceiverRegistry.InvalidTransceiverZeroAddress.selector), + errorSelector + == selectorToUint(AdapterRegistry.InvalidTransceiverZeroAddress.selector), "NttManager: setTransceiver expected to fail if registering the 0 address" ); - } - else if (numRegisteredTransceivers >= 64) { + } else if (numRegisteredTransceivers >= 64) { assertWithMsg( - errorSelector == selectorToUint(TransceiverRegistry.TooManyTransceivers.selector), + errorSelector == selectorToUint(AdapterRegistry.TooManyAdapters.selector), "NttManager: setTransceiver expected to fail if registering too many transceivers" ); - } - else { - assertWithMsg( - false, - "NttManager: setTransceiver unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: setTransceiver unexpected revert"); } } } function removeTransceiver(bool registeredTransceiver, uint256 transceiverIndex) public { address transceiver; - + if (registeredTransceiver) { transceiverIndex = clampBetween(transceiverIndex, 0, registeredTransceivers.length - 1); transceiver = registeredTransceivers[transceiverIndex]; - } - else { + } else { transceiver = address(uint160(transceiverIndex)); } try nttManager.removeTransceiver(transceiver) { isTransceiverEnabled[transceiver] = false; numEnabledTransceivers--; - } - catch (bytes memory revertData) { + } catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); if (transceiver == address(0)) { assertWithMsg( - errorSelector == selectorToUint(TransceiverRegistry.InvalidTransceiverZeroAddress.selector), + errorSelector + == selectorToUint(AdapterRegistry.InvalidTransceiverZeroAddress.selector), "NttManager: removeTransceiver expected to fail if removing the 0 address" ); - } - else if (!isTransceiverRegistered[transceiver]) { + } else if (!isTransceiverRegistered[transceiver]) { assertWithMsg( - errorSelector == selectorToUint(TransceiverRegistry.NonRegisteredTransceiver.selector), + errorSelector + == selectorToUint(AdapterRegistry.NonRegisteredTransceiver.selector), "NttManager: removeTransceiver expected to fail if removing a non-registered transceiver" ); - } - else if (!isTransceiverEnabled[transceiver]) { + } else if (!isTransceiverEnabled[transceiver]) { assertWithMsg( - errorSelector == selectorToUint(TransceiverRegistry.DisabledTransceiver.selector), + errorSelector == selectorToUint(AdapterRegistry.DisabledTransceiver.selector), "NttManager: removeTransceiver expected to fail if removing an already disabled transceiver" ); - } - else if (numEnabledTransceivers == 1) { + } else if (numEnabledTransceivers == 1) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.ZeroThreshold.selector), "NttManager: removeTransceiver expected to fail if trying to remove the last enabled transceiver" ); - } - else { - assertWithMsg( - false, - "NttManager: removeTransceiver unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: removeTransceiver unexpected revert"); } } } - function setThreshold(uint8 threshold) public { - try nttManager.setThreshold(threshold) { - - } + function setThreshold( + uint8 threshold + ) public { + try nttManager.setThreshold(threshold) {} catch (bytes memory revertData) { uint256 errorSelector = extractErrorSelector(revertData); @@ -750,18 +770,13 @@ contract FuzzNttManager is FuzzingHelpers { errorSelector == selectorToUint(IManagerBase.ZeroThreshold.selector), "NttManager: setThreshold expected to fail if setting threshold to 0" ); - } - else if (threshold > numEnabledTransceivers) { + } else if (threshold > numEnabledTransceivers) { assertWithMsg( errorSelector == selectorToUint(IManagerBase.ThresholdTooHigh.selector), "NttManager: setThreshold expected to fail if trying to set threshold above num enabled transceivers" ); - } - else { - assertWithMsg( - false, - "NttManager: setThreshold unexpected revert" - ); + } else { + assertWithMsg(false, "NttManager: setThreshold unexpected revert"); } } } @@ -772,18 +787,23 @@ contract FuzzNttManager is FuzzingHelpers { // Deploy an NTT token dummyToken = new DummyToken(); // Deploy an implementation of the manager - NttManager implementation = new NttManager(address(dummyToken), IManagerBase.Mode.LOCKING, 1, 1 days, false); + NttManager implementation = + new NttManager(address(dummyToken), IManagerBase.Mode.LOCKING, 1, 1 days, false); // Place the manager behind a proxy nttManager = NttManager(address(new ERC1967Proxy(address(implementation), ""))); // Initialize the proxy nttManager.initialize(); } - function _psuedoRandomNumber(uint256 seed) internal returns(uint256) { - return uint(keccak256(abi.encodePacked(block.timestamp, msg.sender, seed))); + function _psuedoRandomNumber( + uint256 seed + ) internal returns (uint256) { + return uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, seed))); } - function _generateMultipleTransceiverInstructions(uint256 num) internal { + function _generateMultipleTransceiverInstructions( + uint256 num + ) internal { for (uint256 i = 0; i < num; ++i) { _generateTransceiverInstructions(true, i); _generateTransceiverInstructions(false, i); @@ -793,8 +813,9 @@ contract FuzzNttManager is FuzzingHelpers { function _generateTransceiverInstructions(bool isOrdered, uint256 seed) internal { bytes memory encodedInstructions; uint256 numInstructions = clampBetween(_psuedoRandomNumber(seed), 0, type(uint8).max); - - TransceiverStructs.TransceiverInstruction[] memory instructions = new TransceiverStructs.TransceiverInstruction[](numInstructions); + + TransceiverStructs.TransceiverInstruction[] memory instructions = + new TransceiverStructs.TransceiverInstruction[](numInstructions); uint256 previousIndex = 0; @@ -808,12 +829,12 @@ contract FuzzNttManager is FuzzingHelpers { if (isOrdered) { newIndex = clampBetween(newIndex, previousIndex + 1, type(uint8).max); - } - else { + } else { newIndex = clampBetween(newIndex, 0, type(uint8).max); } - TransceiverStructs.TransceiverInstruction memory instruction = TransceiverStructs.TransceiverInstruction({ + TransceiverStructs.TransceiverInstruction memory instruction = TransceiverStructs + .TransceiverInstruction({ index: uint8(newIndex), payload: new bytes(newIndex) // We generate an arbitrary length byte array (of zeros for now) }); @@ -827,9 +848,8 @@ contract FuzzNttManager is FuzzingHelpers { if (isOrdered) { orderedInstructions.push(encodedInstructions); - } - else { + } else { unorderedInstructions.push(encodedInstructions); } } -} \ No newline at end of file +} diff --git a/evm/lib/example-messaging-endpoint b/evm/lib/example-messaging-endpoint new file mode 160000 index 000000000..0f853ea03 --- /dev/null +++ b/evm/lib/example-messaging-endpoint @@ -0,0 +1 @@ +Subproject commit 0f853ea0335937d611217f5048d677a4f46249fd diff --git a/evm/lib/example-messaging-executor b/evm/lib/example-messaging-executor new file mode 160000 index 000000000..d6125d67a --- /dev/null +++ b/evm/lib/example-messaging-executor @@ -0,0 +1 @@ +Subproject commit d6125d67ac3f68c65045e954941cf3fd2d78387a diff --git a/evm/script/ConfigureWormholeNtt.s.sol b/evm/script/ConfigureWormholeNtt.s.sol index 083b1e831..282ccb6a3 100644 --- a/evm/script/ConfigureWormholeNtt.s.sol +++ b/evm/script/ConfigureWormholeNtt.s.sol @@ -4,12 +4,12 @@ pragma solidity >=0.8.8 <0.9.0; import {console2} from "forge-std/Script.sol"; import {stdJson} from "forge-std/StdJson.sol"; +import "example-messaging-endpoint/evm/src/interfaces/IAdapter.sol"; + import "../src/interfaces/INttManager.sol"; -import "../src/interfaces/IWormholeTransceiver.sol"; import "../src/interfaces/IOwnableUpgradeable.sol"; import {ParseNttConfig} from "./helpers/ParseNttConfig.sol"; -import {WormholeTransceiver} from "../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; contract ConfigureWormholeNtt is ParseNttConfig { using stdJson for string; @@ -24,42 +24,6 @@ contract ConfigureWormholeNtt is ParseNttConfig { require(params.wormholeChainId != 0, "Invalid chain ID"); } - function configureWormholeTransceiver( - IWormholeTransceiver wormholeTransceiver, - ChainConfig[] memory config, - ConfigParams memory params - ) internal { - for (uint256 i = 0; i < config.length; i++) { - ChainConfig memory targetConfig = config[i]; - if (targetConfig.chainId == params.wormholeChainId) { - continue; - } else { - // Set relayer. - if (targetConfig.isWormholeRelayingEnabled) { - wormholeTransceiver.setIsWormholeRelayingEnabled(targetConfig.chainId, true); - console2.log("Wormhole relaying enabled for chain", targetConfig.chainId); - } else if (targetConfig.isSpecialRelayingEnabled) { - wormholeTransceiver.setIsSpecialRelayingEnabled(targetConfig.chainId, true); - console2.log("Special relaying enabled for chain", targetConfig.chainId); - } - - // Set peer. - wormholeTransceiver.setWormholePeer( - targetConfig.chainId, targetConfig.wormholeTransceiver - ); - console2.log("Wormhole peer set for chain", targetConfig.chainId); - - // Set EVM chain. - if (targetConfig.isEvmChain) { - wormholeTransceiver.setIsWormholeEvmChain(targetConfig.chainId, true); - console2.log("EVM chain set for chain", targetConfig.chainId); - } else { - console2.log("This is not an EVM chain, doing nothing"); - } - } - } - } - function configureNttManager( INttManager nttManager, ChainConfig[] memory config, @@ -75,6 +39,7 @@ contract ConfigureWormholeNtt is ParseNttConfig { targetConfig.chainId, targetConfig.nttManager, targetConfig.decimals, + targetConfig.gasLimit, targetConfig.inboundLimit ); console2.log("Peer set for chain", targetConfig.chainId); @@ -87,13 +52,9 @@ contract ConfigureWormholeNtt is ParseNttConfig { // Sanity check deployment parameters. ConfigParams memory params = _readEnvVariables(); - ( - ChainConfig[] memory config, - INttManager nttManager, - IWormholeTransceiver wormholeTransceiver - ) = _parseAndValidateConfigFile(params.wormholeChainId); + (ChainConfig[] memory config, INttManager nttManager,) = + _parseAndValidateConfigFile(params.wormholeChainId); - configureWormholeTransceiver(wormholeTransceiver, config, params); configureNttManager(nttManager, config, params); vm.stopBroadcast(); diff --git a/evm/script/DeployWormholeNtt.s.sol b/evm/script/DeployWormholeNtt.s.sol index a55e8e52e..adaf7689f 100644 --- a/evm/script/DeployWormholeNtt.s.sol +++ b/evm/script/DeployWormholeNtt.s.sol @@ -4,7 +4,6 @@ pragma solidity >=0.8.8 <0.9.0; import {Script, console} from "forge-std/Script.sol"; import {DeployWormholeNttBase} from "./helpers/DeployWormholeNttBase.sol"; import {INttManager} from "../src/interfaces/INttManager.sol"; -import {IWormholeTransceiver} from "../src/interfaces/IWormholeTransceiver.sol"; import "../src/interfaces/IManagerBase.sol"; import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {NttManager} from "../src/NttManager/NttManager.sol"; @@ -15,7 +14,10 @@ interface IWormhole { contract DeployWormholeNtt is Script, DeployWormholeNttBase { function run( + address endpoint, + address executor, address wormhole, + address transceiver, address token, address wormholeRelayer, address specialRelayer, @@ -28,66 +30,72 @@ contract DeployWormholeNtt is Script, DeployWormholeNttBase { IWormhole wh = IWormhole(wormhole); // sanity check decimals - (bool success, bytes memory queriedDecimals) = - token.staticcall(abi.encodeWithSignature("decimals()")); - - if (success) { - uint8 queriedDecimals = abi.decode(queriedDecimals, (uint8)); - if (queriedDecimals != decimals) { - console.log("Decimals mismatch: ", queriedDecimals, " != ", decimals); - vm.stopBroadcast(); - return; + { + (bool success, bytes memory queriedDecimals) = + token.staticcall(abi.encodeWithSignature("decimals()")); + + if (success) { + uint8 queriedDec = abi.decode(queriedDecimals, (uint8)); + if (queriedDec != decimals) { + console.log("Decimals mismatch: ", queriedDec, " != ", decimals); + vm.stopBroadcast(); + return; + } + } else { + // NOTE: this might not be a critical error. It could just mean that + // the token contract was compiled against a different EVM version than what the forge script is running on. + // In this case, it's the responsibility of the caller to ensure that the provided decimals are correct + // and that the token contract is valid. + // The best way to ensure that is by calling this script with the queried token decimals (which is what the NTT CLI does). + console.log( + "Failed to query token decimals. Proceeding with provided decimals.", decimals + ); + // the NTT manager initialiser calls the token contract to get the + // decimals as well. We're just going to mock that call to return the provided decimals. + // This is a bit of a hack, but in the worst case (i.e. if the token contract is actually broken), the + // NTT manager initialiser will fail anyway. + vm.mockCall( + token, abi.encodeWithSelector(ERC20.decimals.selector), abi.encode(decimals) + ); } - } else { - // NOTE: this might not be a critical error. It could just mean that - // the token contract was compiled against a different EVM version than what the forge script is running on. - // In this case, it's the responsibility of the caller to ensure that the provided decimals are correct - // and that the token contract is valid. - // The best way to ensure that is by calling this script with the queried token decimals (which is what the NTT CLI does). - console.log( - "Failed to query token decimals. Proceeding with provided decimals.", decimals - ); - // the NTT manager initialiser calls the token contract to get the - // decimals as well. We're just going to mock that call to return the provided decimals. - // This is a bit of a hack, but in the worst case (i.e. if the token contract is actually broken), the - // NTT manager initialiser will fail anyway. - vm.mockCall( - token, abi.encodeWithSelector(ERC20.decimals.selector), abi.encode(decimals) - ); } - uint16 chainId = wh.chainId(); - - console.log("Chain ID: ", chainId); - - uint256 scale = - decimals > TRIMMED_DECIMALS ? uint256(10 ** (decimals - TRIMMED_DECIMALS)) : 1; - - DeploymentParams memory params = DeploymentParams({ - token: token, - mode: mode, - wormholeChainId: chainId, - rateLimitDuration: 86400, - shouldSkipRatelimiter: false, - wormholeCoreBridge: wormhole, - wormholeRelayerAddr: wormholeRelayer, - specialRelayerAddr: specialRelayer, - consistencyLevel: 202, - gasLimit: 500000, - // the trimming will trim this number to uint64.max - outboundLimit: uint256(type(uint64).max) * scale - }); + DeploymentParams memory params; + { + uint16 chainId = wh.chainId(); + + console.log("Chain ID: ", chainId); + + uint256 scale = + decimals > TRIMMED_DECIMALS ? uint256(10 ** (decimals - TRIMMED_DECIMALS)) : 1; + + params = DeploymentParams({ + endpointAddr: endpoint, + executorAddr: executor, + token: token, + mode: mode, + wormholeChainId: chainId, + rateLimitDuration: 86400, + shouldSkipRatelimiter: false, + wormholeCoreBridge: wormhole, + wormholeRelayerAddr: wormholeRelayer, + specialRelayerAddr: specialRelayer, + consistencyLevel: 202, + gasLimit: 500000, + // the trimming will trim this number to uint64.max + outboundLimit: uint256(type(uint64).max) * scale + }); + } // Deploy NttManager. - address manager = deployNttManager(params); - - // Deploy Wormhole Transceiver. - address transceiver = deployWormholeTransceiver(params, manager); + { + address manager = deployNttManager(params); - // Configure NttManager. - configureNttManager( - manager, transceiver, params.outboundLimit, params.shouldSkipRatelimiter - ); + // Configure NttManager. + configureNttManager( + manager, transceiver, params.outboundLimit, params.shouldSkipRatelimiter + ); + } vm.stopBroadcast(); } @@ -105,6 +113,8 @@ contract DeployWormholeNtt is Script, DeployWormholeNttBase { bool shouldSkipRatelimiter = rateLimitDuration == 0; NttManager implementation = new NttManager( + address(nttManager.endpoint()), + address(nttManager.executor()), nttManager.token(), nttManager.mode(), nttManager.chainId(), diff --git a/evm/script/UpgradeNttManager.s.sol b/evm/script/UpgradeNttManager.s.sol index 2ae864624..e98204c7c 100644 --- a/evm/script/UpgradeNttManager.s.sol +++ b/evm/script/UpgradeNttManager.s.sol @@ -13,6 +13,8 @@ import {ParseNttConfig} from "./helpers/ParseNttConfig.sol"; contract UpgradeNttManager is ParseNttConfig { struct DeploymentParams { + address endpoint; + address executor; address token; INttManager.Mode mode; uint16 wormholeChainId; @@ -26,6 +28,8 @@ contract UpgradeNttManager is ParseNttConfig { ) internal { // Deploy the Manager Implementation. NttManager implementation = new NttManager( + params.endpoint, + params.executor, params.token, params.mode, params.wormholeChainId, diff --git a/evm/script/UpgradeWormholeTransceiver.s.sol b/evm/script/UpgradeWormholeTransceiver.s.sol deleted file mode 100644 index e6783629a..000000000 --- a/evm/script/UpgradeWormholeTransceiver.s.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity >=0.8.8 <0.9.0; - -import {console2} from "forge-std/Script.sol"; - -import "../src/interfaces/IWormholeTransceiver.sol"; -import "../src/interfaces/ITransceiver.sol"; -import "../src/interfaces/INttManager.sol"; - -import {WormholeTransceiver} from "../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; -import {ERC1967Proxy} from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - -import {ParseNttConfig} from "./helpers/ParseNttConfig.sol"; - -contract UpgradeWormholeTransceiver is ParseNttConfig { - struct DeploymentParams { - uint16 wormholeChainId; - address wormholeCoreBridge; - address wormholeRelayerAddr; - address specialRelayerAddr; - uint8 consistencyLevel; - uint256 gasLimit; - uint256 outboundLimit; - } - - // The minimum gas limit to verify a message on mainnet. If you're worried about saving - // gas on testnet, pick up the phone and start dialing! - uint256 constant MIN_WORMHOLE_GAS_LIMIT = 150000; - - function upgradeWormholeTransceiver( - IWormholeTransceiver wormholeTransceiverProxy, - DeploymentParams memory params, - address nttManager - ) internal { - // Deploy the Wormhole Transceiver. - WormholeTransceiver implementation = new WormholeTransceiver( - nttManager, - params.wormholeCoreBridge, - params.wormholeRelayerAddr, - params.specialRelayerAddr, - params.consistencyLevel, - params.gasLimit - ); - - console2.log("WormholeTransceiver Implementation deployed at: ", address(implementation)); - - // Upgrade the proxy. - ITransceiver(address(wormholeTransceiverProxy)).upgrade(address(implementation)); - } - - function _readEnvVariables() internal view returns (DeploymentParams memory params) { - // Chain ID. - params.wormholeChainId = uint16(vm.envUint("RELEASE_WORMHOLE_CHAIN_ID")); - require(params.wormholeChainId != 0, "Invalid chain ID"); - - // Wormhole Core Bridge address. - params.wormholeCoreBridge = vm.envAddress("RELEASE_CORE_BRIDGE_ADDRESS"); - require(params.wormholeCoreBridge != address(0), "Invalid wormhole core bridge address"); - - // Wormhole relayer, special relayer, consistency level. - params.wormholeRelayerAddr = vm.envAddress("RELEASE_WORMHOLE_RELAYER_ADDRESS"); - params.specialRelayerAddr = vm.envAddress("RELEASE_SPECIAL_RELAYER_ADDRESS"); - params.consistencyLevel = uint8(vm.envUint("RELEASE_CONSISTENCY_LEVEL")); - - params.gasLimit = vm.envUint("RELEASE_GAS_LIMIT"); - require(params.gasLimit >= MIN_WORMHOLE_GAS_LIMIT, "Invalid gas limit"); - - // Outbound rate limiter limit. - params.outboundLimit = vm.envUint("RELEASE_OUTBOUND_LIMIT"); - } - - function run() public { - vm.startBroadcast(); - - // Sanity check deployment parameters. - DeploymentParams memory params = _readEnvVariables(); - (, INttManager nttManager, IWormholeTransceiver wormholeTransceiver) = - _parseAndValidateConfigFile(params.wormholeChainId); - - // Upgrade WormholeTransceiver. - upgradeWormholeTransceiver(wormholeTransceiver, params, address(nttManager)); - - vm.stopBroadcast(); - } -} diff --git a/evm/script/helpers/DeployWormholeNttBase.sol b/evm/script/helpers/DeployWormholeNttBase.sol index 848b3cfda..d08157295 100644 --- a/evm/script/helpers/DeployWormholeNttBase.sol +++ b/evm/script/helpers/DeployWormholeNttBase.sol @@ -5,11 +5,8 @@ import {console2} from "forge-std/Script.sol"; import {ParseNttConfig} from "./ParseNttConfig.sol"; import "../../src/interfaces/IManagerBase.sol"; import "../../src/interfaces/INttManager.sol"; -import "../../src/interfaces/IWormholeTransceiver.sol"; import {NttManager} from "../../src/NttManager/NttManager.sol"; -import {WormholeTransceiver} from - "../../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; import {ERC1967Proxy} from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract DeployWormholeNttBase is ParseNttConfig { @@ -22,6 +19,8 @@ contract DeployWormholeNttBase is ParseNttConfig { address wormholeCoreBridge; address wormholeRelayerAddr; address specialRelayerAddr; + address endpointAddr; + address executorAddr; uint8 consistencyLevel; uint256 gasLimit; uint256 outboundLimit; @@ -36,6 +35,8 @@ contract DeployWormholeNttBase is ParseNttConfig { ) internal returns (address) { // Deploy the Manager Implementation. NttManager implementation = new NttManager( + params.endpointAddr, + params.executorAddr, params.token, params.mode, params.wormholeChainId, @@ -54,30 +55,6 @@ contract DeployWormholeNttBase is ParseNttConfig { return address(nttManagerProxy); } - function deployWormholeTransceiver( - DeploymentParams memory params, - address nttManager - ) public returns (address) { - // Deploy the Wormhole Transceiver. - WormholeTransceiver implementation = new WormholeTransceiver( - nttManager, - params.wormholeCoreBridge, - params.wormholeRelayerAddr, - params.specialRelayerAddr, - params.consistencyLevel, - params.gasLimit - ); - - WormholeTransceiver transceiverProxy = - WormholeTransceiver(address(new ERC1967Proxy(address(implementation), ""))); - - transceiverProxy.initialize(); - - console2.log("WormholeTransceiver:", address(transceiverProxy)); - - return address(transceiverProxy); - } - function configureNttManager( address nttManager, address transceiver, @@ -92,9 +69,10 @@ contract DeployWormholeNttBase is ParseNttConfig { console2.log("Outbound rate limit set on NttManager: ", outboundLimit); } - // Hardcoded to one since these scripts handle Wormhole-only deployments. - INttManager(nttManager).setThreshold(1); - console2.log("Threshold set on NttManager: %d", uint256(1)); + // TODO: Need to enable sending and receiving and set the threshold for the destination chains. + // // Hardcoded to one since these scripts handle Wormhole-only deployments. + // INttManager(nttManager).setThreshold(1); + // console2.log("Threshold set on NttManager: %d", uint256(1)); } function _readEnvVariables() internal view returns (DeploymentParams memory params) { diff --git a/evm/script/helpers/ParseNttConfig.sol b/evm/script/helpers/ParseNttConfig.sol index 78739f10e..177fe2a05 100644 --- a/evm/script/helpers/ParseNttConfig.sol +++ b/evm/script/helpers/ParseNttConfig.sol @@ -5,7 +5,7 @@ import {Script, console2} from "forge-std/Script.sol"; import {stdJson} from "forge-std/StdJson.sol"; import "../../src/interfaces/INttManager.sol"; -import "../../src/interfaces/IWormholeTransceiver.sol"; +import "example-messaging-endpoint/evm/src/interfaces/IAdapter.sol"; contract ParseNttConfig is Script { using stdJson for string; @@ -15,6 +15,7 @@ contract ParseNttConfig is Script { struct ChainConfig { uint16 chainId; uint8 decimals; + uint128 gasLimit; uint256 inboundLimit; bool isEvmChain; bool isSpecialRelayingEnabled; @@ -47,11 +48,7 @@ contract ParseNttConfig is Script { uint16 wormholeChainId ) internal - returns ( - ChainConfig[] memory config, - INttManager nttManager, - IWormholeTransceiver wormholeTransceiver - ) + returns (ChainConfig[] memory config, INttManager nttManager, IAdapter wormholeTransceiver) { string memory root = vm.projectRoot(); string memory path = string.concat(root, "/cfg/WormholeNttConfig.json"); @@ -83,8 +80,7 @@ contract ParseNttConfig is Script { // Set the contract addresses for this chain. if (config[i].chainId == wormholeChainId) { nttManager = INttManager(fromUniversalAddress(config[i].nttManager)); - wormholeTransceiver = - IWormholeTransceiver(fromUniversalAddress(config[i].wormholeTransceiver)); + wormholeTransceiver = IAdapter(fromUniversalAddress(config[i].wormholeTransceiver)); } } } diff --git a/evm/src/NttManager/ManagerBase.sol b/evm/src/NttManager/ManagerBase.sol index 8a336fccc..3e6228df6 100644 --- a/evm/src/NttManager/ManagerBase.sol +++ b/evm/src/NttManager/ManagerBase.sol @@ -4,6 +4,10 @@ pragma solidity >=0.8.8 <0.9.0; import "wormhole-solidity-sdk/Utils.sol"; import "wormhole-solidity-sdk/libraries/BytesParsing.sol"; +import "example-messaging-endpoint/evm/src/interfaces/IEndpointAdmin.sol"; +import "example-messaging-endpoint/evm/src/interfaces/IEndpointIntegrator.sol"; +import "example-messaging-executor/evm/src/interfaces/IExecutor.sol"; + import "../libraries/external/OwnableUpgradeable.sol"; import "../libraries/external/ReentrancyGuardUpgradeable.sol"; import "../libraries/TransceiverStructs.sol"; @@ -11,14 +15,10 @@ import "../libraries/TransceiverHelpers.sol"; import "../libraries/PausableOwnable.sol"; import "../libraries/Implementation.sol"; -import "../interfaces/ITransceiver.sol"; import "../interfaces/IManagerBase.sol"; -import "./TransceiverRegistry.sol"; - abstract contract ManagerBase is IManagerBase, - TransceiverRegistry, PausableOwnable, ReentrancyGuardUpgradeable, Implementation @@ -40,9 +40,17 @@ abstract contract ManagerBase is /// This chain ID is formatted based on standardized chain IDs, e.g. Ethereum mainnet is 1, Sepolia is 11155111, etc. uint256 immutable evmChainId; + // The modular messaging endpoint used for sending and receiving attestations. + IEndpointIntegrator public immutable endpoint; + + // The executor used for publishing message payloads. + IExecutor public immutable executor; + // =============== Setup ================================================================= - constructor(address _token, Mode _mode, uint16 _chainId) { + constructor(address _endpoint, address _executor, address _token, Mode _mode, uint16 _chainId) { + endpoint = IEndpointIntegrator(_endpoint); + executor = IExecutor(_executor); token = _token; mode = _mode; chainId = _chainId; @@ -53,41 +61,40 @@ abstract contract ManagerBase is function _migrate() internal virtual override { _checkThresholdInvariants(); - _checkTransceiversInvariants(); } // =============== Storage ============================================================== - bytes32 private constant MESSAGE_ATTESTATIONS_SLOT = - bytes32(uint256(keccak256("ntt.messageAttestations")) - 1); - bytes32 private constant MESSAGE_SEQUENCE_SLOT = bytes32(uint256(keccak256("ntt.messageSequence")) - 1); bytes32 private constant THRESHOLD_SLOT = bytes32(uint256(keccak256("ntt.threshold")) - 1); + bytes32 private constant RECV_ENABLED_CHAINS_SLOT = + bytes32(uint256(keccak256("ntt.recvEnabledChains")) - 1); + // =============== Storage Getters/Setters ============================================== - function _getThresholdStorage() private pure returns (_Threshold storage $) { + function _getThresholdStorage() + private + pure + returns (mapping(uint16 => _Threshold) storage $) + { uint256 slot = uint256(THRESHOLD_SLOT); assembly ("memory-safe") { $.slot := slot } } - function _getMessageAttestationsStorage() - internal - pure - returns (mapping(bytes32 => AttestationInfo) storage $) - { - uint256 slot = uint256(MESSAGE_ATTESTATIONS_SLOT); + function _getMessageSequenceStorage() internal pure returns (_Sequence storage $) { + uint256 slot = uint256(MESSAGE_SEQUENCE_SLOT); assembly ("memory-safe") { $.slot := slot } } - function _getMessageSequenceStorage() internal pure returns (_Sequence storage $) { - uint256 slot = uint256(MESSAGE_SEQUENCE_SLOT); + function _getChainsEnabledForReceiveStorage() internal pure returns (uint16[] storage $) { + uint256 slot = uint256(RECV_ENABLED_CHAINS_SLOT); assembly ("memory-safe") { $.slot := slot } @@ -99,167 +106,12 @@ abstract contract ManagerBase is function quoteDeliveryPrice( uint16 recipientChain, bytes memory transceiverInstructions - ) public view returns (uint256[] memory, uint256) { - address[] memory enabledTransceivers = _getEnabledTransceiversStorage(); - - TransceiverStructs.TransceiverInstruction[] memory instructions = TransceiverStructs - .parseTransceiverInstructions(transceiverInstructions, enabledTransceivers.length); - - return _quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers); + ) public view returns (uint256) { + return endpoint.quoteDeliveryPrice(recipientChain, transceiverInstructions); } // =============== Internal Logic =========================================================== - function _quoteDeliveryPrice( - uint16 recipientChain, - TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions, - address[] memory enabledTransceivers - ) internal view returns (uint256[] memory, uint256) { - uint256 numEnabledTransceivers = enabledTransceivers.length; - mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage(); - - uint256[] memory priceQuotes = new uint256[](numEnabledTransceivers); - uint256 totalPriceQuote = 0; - for (uint256 i = 0; i < numEnabledTransceivers; i++) { - address transceiverAddr = enabledTransceivers[i]; - uint8 registeredTransceiverIndex = transceiverInfos[transceiverAddr].index; - uint256 transceiverPriceQuote = ITransceiver(transceiverAddr).quoteDeliveryPrice( - recipientChain, transceiverInstructions[registeredTransceiverIndex] - ); - priceQuotes[i] = transceiverPriceQuote; - totalPriceQuote += transceiverPriceQuote; - } - return (priceQuotes, totalPriceQuote); - } - - function _recordTransceiverAttestation( - uint16 sourceChainId, - TransceiverStructs.NttManagerMessage memory payload - ) internal returns (bytes32) { - bytes32 nttManagerMessageHash = - TransceiverStructs.nttManagerMessageDigest(sourceChainId, payload); - - // set the attested flag for this transceiver. - // NOTE: Attestation is idempotent (bitwise or 1), but we revert - // anyway to ensure that the client does not continue to initiate calls - // to receive the same message through the same transceiver. - if ( - transceiverAttestedToMessage( - nttManagerMessageHash, _getTransceiverInfosStorage()[msg.sender].index - ) - ) { - revert TransceiverAlreadyAttestedToMessage(nttManagerMessageHash); - } - _setTransceiverAttestedToMessage(nttManagerMessageHash, msg.sender); - - return nttManagerMessageHash; - } - - function _isMessageExecuted( - uint16 sourceChainId, - bytes32 sourceNttManagerAddress, - TransceiverStructs.NttManagerMessage memory message - ) internal returns (bytes32, bool) { - bytes32 digest = TransceiverStructs.nttManagerMessageDigest(sourceChainId, message); - - if (!isMessageApproved(digest)) { - revert MessageNotApproved(digest); - } - - bool msgAlreadyExecuted = _replayProtect(digest); - if (msgAlreadyExecuted) { - // end execution early to mitigate the possibility of race conditions from transceivers - // attempting to deliver the same message when (threshold < number of transceiver messages) - // notify client (off-chain process) so they don't attempt redundant msg delivery - emit MessageAlreadyExecuted(sourceNttManagerAddress, digest); - return (bytes32(0), msgAlreadyExecuted); - } - - return (digest, msgAlreadyExecuted); - } - - function _sendMessageToTransceivers( - uint16 recipientChain, - bytes32 refundAddress, - bytes32 peerAddress, - uint256[] memory priceQuotes, - TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions, - address[] memory enabledTransceivers, - bytes memory nttManagerMessage - ) internal { - uint256 numEnabledTransceivers = enabledTransceivers.length; - mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage(); - - if (peerAddress == bytes32(0)) { - revert PeerNotRegistered(recipientChain); - } - - // push onto the stack again to avoid stack too deep error - bytes32 refundRecipient = refundAddress; - - // call into transceiver contracts to send the message - for (uint256 i = 0; i < numEnabledTransceivers; i++) { - address transceiverAddr = enabledTransceivers[i]; - - // send it to the recipient nttManager based on the chain - ITransceiver(transceiverAddr).sendMessage{value: priceQuotes[i]}( - recipientChain, - transceiverInstructions[transceiverInfos[transceiverAddr].index], - nttManagerMessage, - peerAddress, - refundRecipient - ); - } - } - - function _prepareForTransfer( - uint16 recipientChain, - bytes memory transceiverInstructions - ) - internal - returns ( - address[] memory, - TransceiverStructs.TransceiverInstruction[] memory, - uint256[] memory, - uint256 - ) - { - // cache enabled transceivers to avoid multiple storage reads - address[] memory enabledTransceivers = _getEnabledTransceiversStorage(); - - TransceiverStructs.TransceiverInstruction[] memory instructions; - - { - uint256 numRegisteredTransceivers = _getRegisteredTransceiversStorage().length; - uint256 numEnabledTransceivers = enabledTransceivers.length; - - if (numEnabledTransceivers == 0) { - revert NoEnabledTransceivers(); - } - - instructions = TransceiverStructs.parseTransceiverInstructions( - transceiverInstructions, numRegisteredTransceivers - ); - } - - (uint256[] memory priceQuotes, uint256 totalPriceQuote) = - _quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers); - { - // check up front that msg.value will cover the delivery price - if (msg.value < totalPriceQuote) { - revert DeliveryPaymentTooLow(totalPriceQuote, msg.value); - } - - // refund user extra excess value from msg.value - uint256 excessValue = msg.value - totalPriceQuote; - if (excessValue > 0) { - _refundToSender(excessValue); - } - } - - return (enabledTransceivers, instructions, priceQuotes, totalPriceQuote); - } - function _refundToSender( uint256 refundAmount ) internal { @@ -280,16 +132,23 @@ abstract contract ManagerBase is } /// @inheritdoc IManagerBase - function getThreshold() public view returns (uint8) { - return _getThresholdStorage().num; + function getThreshold( + uint16 chain + ) public view returns (uint8) { + return _getThresholdStorage()[chain].num; } /// @inheritdoc IManagerBase function isMessageApproved( - bytes32 digest + uint16 srcChain, + UniversalAddress srcAddr, + uint64 sequence, + UniversalAddress dstAddr, + bytes32 payloadHash ) public view returns (bool) { - uint8 threshold = getThreshold(); - return messageAttestations(digest) >= threshold && threshold > 0; + uint8 numAttested = messageAttestations(srcChain, srcAddr, sequence, dstAddr, payloadHash); + uint8 threshold = getThreshold(srcChain); + return (numAttested >= threshold); } /// @inheritdoc IManagerBase @@ -299,22 +158,42 @@ abstract contract ManagerBase is /// @inheritdoc IManagerBase function isMessageExecuted( - bytes32 digest + uint16 srcChain, + UniversalAddress srcAddr, + uint64 sequence, + UniversalAddress dstAddr, + bytes32 payloadHash ) public view returns (bool) { - return _getMessageAttestationsStorage()[digest].executed; + (,,, bool executed) = + endpoint.getMessageStatus(srcChain, srcAddr, sequence, dstAddr, payloadHash); + + return executed; } /// @inheritdoc IManagerBase - function transceiverAttestedToMessage(bytes32 digest, uint8 index) public view returns (bool) { - return - _getMessageAttestationsStorage()[digest].attestedTransceivers & uint64(1 << index) > 0; + function transceiverAttestedToMessage( + uint16 srcChain, + UniversalAddress srcAddr, + uint64 sequence, + UniversalAddress dstAddr, + bytes32 payloadHash, + uint8 index + ) public view returns (bool) { + (, uint128 attested,,) = + endpoint.getMessageStatus(srcChain, srcAddr, sequence, dstAddr, payloadHash); + + return attested & uint64(1 << index) > 0; } /// @inheritdoc IManagerBase function messageAttestations( - bytes32 digest + uint16 srcChain, + UniversalAddress srcAddr, + uint64 sequence, + UniversalAddress dstAddr, + bytes32 payloadHash ) public view returns (uint8 count) { - return countSetBits(_getMessageAttestations(digest)); + (,, count,) = endpoint.getMessageStatus(srcChain, srcAddr, sequence, dstAddr, payloadHash); } // =============== Admin ============================================================== @@ -339,23 +218,28 @@ abstract contract ManagerBase is function transferOwnership( address newOwner ) public override onlyOwner { + // TODO: Just delete this function and let the Ownable one be called directly? super.transferOwnership(newOwner); - // loop through all the registered transceivers and set the new owner of each transceiver to the newOwner - address[] storage _registeredTransceivers = _getRegisteredTransceiversStorage(); - _checkRegisteredTransceiversInvariants(); - - for (uint256 i = 0; i < _registeredTransceivers.length; i++) { - ITransceiver(_registeredTransceivers[i]).transferTransceiverOwnership(newOwner); - } } /// @inheritdoc IManagerBase function setTransceiver( address transceiver ) external onlyOwner { - _setTransceiver(transceiver); + uint8 index = IEndpointAdmin(address(endpoint)).addAdapter(address(this), transceiver); + emit TransceiverAdded(transceiver, index); + } + + /// @inheritdoc IManagerBase + function enableSendTransceiver(uint16 chain, address transceiver) external { + IEndpointAdmin(address(endpoint)).enableSendAdapter(address(this), chain, transceiver); + } + + /// @inheritdoc IManagerBase + function enableRecvTransceiver(uint16 chain, address transceiver) external { + IEndpointAdmin(address(endpoint)).enableRecvAdapter(address(this), chain, transceiver); - _Threshold storage _threshold = _getThresholdStorage(); + _Threshold storage _threshold = _getThresholdStorage()[chain]; // We do not automatically increase the threshold here. // Automatically increasing the threshold can result in a scenario // where in-flight messages can't be redeemed. @@ -371,97 +255,95 @@ abstract contract ManagerBase is // However if the threshold is 0 (the initial case) we do increment to 1. if (_threshold.num == 0) { _threshold.num = 1; + _addChainEnabledForReceive(chain); } - emit TransceiverAdded(transceiver, _getNumTransceiversStorage().enabled, _threshold.num); + _checkThresholdInvariantsForChain(chain); + } - _checkThresholdInvariants(); + /// @inheritdoc IManagerBase + function disableSendTransceiver(uint16 chain, address transceiver) external { + IEndpointAdmin(address(endpoint)).disableSendAdapter(address(this), chain, transceiver); } /// @inheritdoc IManagerBase - function removeTransceiver( - address transceiver - ) external onlyOwner { - _removeTransceiver(transceiver); + function disableRecvTransceiver(uint16 chain, address transceiver) external { + IEndpointAdmin(address(endpoint)).disableRecvAdapter(address(this), chain, transceiver); - _Threshold storage _threshold = _getThresholdStorage(); - uint8 numEnabledTransceivers = _getNumTransceiversStorage().enabled; + _Threshold storage _threshold = _getThresholdStorage()[chain]; + uint8 numEnabled = IEndpointAdmin(address(endpoint)).getNumEnabledRecvAdaptersForChain( + address(this), chain + ); - if (numEnabledTransceivers < _threshold.num) { - _threshold.num = numEnabledTransceivers; + // TODO: Should we do this or just let _checkThresholdInvariantsForChain revert and make them reduce the threshold before disabling the chain? + if (_threshold.num > numEnabled) { + uint8 oldThreshold = _threshold.num; + _threshold.num = numEnabled; + emit ThresholdChanged(chainId, oldThreshold, numEnabled); } - emit TransceiverRemoved(transceiver, _threshold.num); + if (numEnabled == 0) { + _removeChainEnabledForReceive(chain); + } - _checkThresholdInvariants(); + _checkThresholdInvariantsForChain(chain); } /// @inheritdoc IManagerBase - function setThreshold( - uint8 threshold - ) external onlyOwner { + function setThreshold(uint16 chain, uint8 threshold) external onlyOwner { if (threshold == 0) { revert ZeroThreshold(); } - _Threshold storage _threshold = _getThresholdStorage(); + _Threshold storage _threshold = _getThresholdStorage()[chain]; uint8 oldThreshold = _threshold.num; _threshold.num = threshold; - _checkThresholdInvariants(); - - emit ThresholdChanged(oldThreshold, threshold); + _addChainEnabledForReceive(chain); + _checkThresholdInvariantsForChain(chain); + emit ThresholdChanged(chainId, oldThreshold, threshold); } // =============== Internal ============================================================== - function _setTransceiverAttestedToMessage(bytes32 digest, uint8 index) internal { - _getMessageAttestationsStorage()[digest].attestedTransceivers |= uint64(1 << index); - } - - function _setTransceiverAttestedToMessage(bytes32 digest, address transceiver) internal { - _setTransceiverAttestedToMessage(digest, _getTransceiverInfosStorage()[transceiver].index); - - emit MessageAttestedTo( - digest, transceiver, _getTransceiverInfosStorage()[transceiver].index - ); - } - - /// @dev Returns the bitmap of attestations from enabled transceivers for a given message. - function _getMessageAttestations( - bytes32 digest - ) internal view returns (uint64) { - uint64 enabledTransceiverBitmap = _getEnabledTransceiversBitmap(); - return - _getMessageAttestationsStorage()[digest].attestedTransceivers & enabledTransceiverBitmap; - } - - function _getEnabledTransceiverAttestedToMessage( - bytes32 digest, - uint8 index - ) internal view returns (bool) { - return _getMessageAttestations(digest) & uint64(1 << index) != 0; + function _useMessageSequence() internal returns (uint64 currentSequence) { + currentSequence = _getMessageSequenceStorage().num; + _getMessageSequenceStorage().num++; } - // @dev Mark a message as executed. - // This function will retuns `true` if the message has already been executed. - function _replayProtect( - bytes32 digest - ) internal returns (bool) { - // check if this message has already been executed - if (isMessageExecuted(digest)) { - return true; + /// @dev It's not an error if the chain is not in the list. + function _removeChainEnabledForReceive( + uint16 chain + ) internal { + uint16[] storage chains = _getChainsEnabledForReceiveStorage(); + uint256 len = chains.length; + for (uint256 idx = 0; (idx < len);) { + if (chains[idx] == chain) { + chains[idx] = chains[len - 1]; + chains.pop(); + return; + } + unchecked { + ++idx; + } } - - // mark this message as executed - _getMessageAttestationsStorage()[digest].executed = true; - - return false; } - function _useMessageSequence() internal returns (uint64 currentSequence) { - currentSequence = _getMessageSequenceStorage().num; - _getMessageSequenceStorage().num++; + /// @dev It's not an error if the chain is already in the list. + function _addChainEnabledForReceive( + uint16 chain + ) internal { + uint16[] storage chains = _getChainsEnabledForReceiveStorage(); + uint256 len = chains.length; + for (uint256 idx = 0; (idx < len);) { + if (chains[idx] == chain) { + return; + } + unchecked { + ++idx; + } + } + chains.push(chain); } /// ============== Invariants ============================================= @@ -471,26 +353,35 @@ abstract contract ManagerBase is assert(this.token() == token); assert(this.mode() == mode); assert(this.chainId() == chainId); + assert(this.endpoint() == endpoint); } - function _checkRegisteredTransceiversInvariants() internal view { - if (_getRegisteredTransceiversStorage().length != _getNumTransceiversStorage().registered) { - revert RetrievedIncorrectRegisteredTransceivers( - _getRegisteredTransceiversStorage().length, _getNumTransceiversStorage().registered - ); + function _checkThresholdInvariants() internal view { + uint16[] storage chains = _getChainsEnabledForReceiveStorage(); + uint256 len = chains.length; + for (uint256 idx = 0; (idx < len);) { + _checkThresholdInvariantsForChain(chains[idx]); + unchecked { + ++idx; + } } } - function _checkThresholdInvariants() internal view { - uint8 threshold = _getThresholdStorage().num; - _NumTransceivers memory numTransceivers = _getNumTransceiversStorage(); + /// @dev This can be called directly when we are only manipulating a single chain. Otherwise use _checkThresholdInvariants. + function _checkThresholdInvariantsForChain( + uint16 chain + ) internal view { + uint8 threshold = _getThresholdStorage()[chain].num; + uint8 numEnabled = IEndpointAdmin(address(endpoint)).getNumEnabledRecvAdaptersForChain( + address(this), chain + ); // invariant: threshold <= enabledTransceivers.length - if (threshold > numTransceivers.enabled) { - revert ThresholdTooHigh(threshold, numTransceivers.enabled); + if (threshold > numEnabled) { + revert ThresholdTooHigh(threshold, numEnabled); } - if (numTransceivers.registered > 0) { + if (numEnabled > 0) { if (threshold == 0) { revert ZeroThreshold(); } diff --git a/evm/src/NttManager/NttManager.sol b/evm/src/NttManager/NttManager.sol index 7ac021b91..17ee9bb17 100644 --- a/evm/src/NttManager/NttManager.sol +++ b/evm/src/NttManager/NttManager.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache 2 pragma solidity >=0.8.8 <0.9.0; +import "example-messaging-executor/evm/src/libraries/ExecutorMessages.sol"; +import "example-messaging-executor/evm/src/libraries/RelayInstructions.sol"; import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol"; @@ -11,7 +13,6 @@ import "../libraries/RateLimiter.sol"; import "../interfaces/INttManager.sol"; import "../interfaces/INttToken.sol"; -import "../interfaces/ITransceiver.sol"; import {ManagerBase} from "./ManagerBase.sol"; @@ -46,12 +47,17 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { // =============== Setup ================================================================= constructor( + address _endpoint, + address _executor, address _token, Mode _mode, uint16 _chainId, uint64 _rateLimitDuration, bool _skipRateLimiting - ) RateLimiter(_rateLimitDuration, _skipRateLimiting) ManagerBase(_token, _mode, _chainId) {} + ) + RateLimiter(_rateLimitDuration, _skipRateLimiting) + ManagerBase(_endpoint, _executor, _token, _mode, _chainId) + {} function __NttManager_init() internal onlyInitializing { // check if the owner is the deployer of this contract @@ -64,12 +70,14 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { __PausedOwnable_init(msg.sender, msg.sender); __ReentrancyGuard_init(); _setOutboundLimit(TrimmedAmountLib.max(tokenDecimals())); + + // Register the proxy as the integrator and the admin. + endpoint.register(address(this)); } function _initialize() internal virtual override { __NttManager_init(); _checkThresholdInvariants(); - _checkTransceiversInvariants(); } // =============== Storage ============================================================== @@ -105,6 +113,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { uint16 peerChainId, bytes32 peerContract, uint8 decimals, + uint128 gasLimit, uint256 inboundLimit ) public onlyOwner { if (peerChainId == 0) { @@ -116,6 +125,9 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { if (decimals == 0) { revert InvalidPeerDecimals(); } + if (gasLimit == 0) { + revert InvalidGasLimitZero(peerChainId); + } if (peerChainId == chainId) { revert InvalidPeerSameChainId(); } @@ -124,6 +136,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { _getPeersStorage()[peerChainId].peerAddress = peerContract; _getPeersStorage()[peerChainId].tokenDecimals = decimals; + _getPeersStorage()[peerChainId].gasLimit = gasLimit; uint8 toDecimals = tokenDecimals(); _setInboundLimit(inboundLimit.trim(toDecimals, toDecimals), peerChainId); @@ -133,6 +146,17 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { ); } + /// @inheritdoc INttManager + function setGasLimit(uint16 peerChainId, uint128 gasLimit) external onlyOwner { + if (gasLimit == 0) { + revert InvalidGasLimitZero(peerChainId); + } + if (_getPeersStorage()[peerChainId].peerAddress == bytes32(0)) { + revert InvalidPeerZeroAddress(); + } + _getPeersStorage()[peerChainId].gasLimit = gasLimit; + } + /// @inheritdoc INttManager function setOutboundLimit( uint256 limit @@ -161,10 +185,21 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { function transfer( uint256 amount, uint16 recipientChain, - bytes32 recipient + bytes32 recipient, + bytes calldata executorQuote, + bytes calldata relayInstructions, + bytes calldata transceiverInstructions ) external payable nonReentrant whenNotPaused returns (uint64) { - return - _transferEntryPoint(amount, recipientChain, recipient, recipient, false, new bytes(1)); + return _transferEntryPoint( + amount, + recipientChain, + recipient, + recipient, + false, + executorQuote, + relayInstructions, + transceiverInstructions + ); } /// @inheritdoc INttManager @@ -174,43 +209,60 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { bytes32 recipient, bytes32 refundAddress, bool shouldQueue, - bytes memory transceiverInstructions + bytes calldata executorQuote, + bytes calldata relayInstructions, + bytes calldata transceiverInstructions ) external payable nonReentrant whenNotPaused returns (uint64) { return _transferEntryPoint( - amount, recipientChain, recipient, refundAddress, shouldQueue, transceiverInstructions + amount, + recipientChain, + recipient, + refundAddress, + shouldQueue, + executorQuote, + relayInstructions, + transceiverInstructions ); } /// @inheritdoc INttManager - function attestationReceived( + function executeMsg( uint16 sourceChainId, - bytes32 sourceNttManagerAddress, - TransceiverStructs.NttManagerMessage memory payload - ) external onlyTransceiver whenNotPaused { - _verifyPeer(sourceChainId, sourceNttManagerAddress); + UniversalAddress sourceNttManagerAddress, + uint64 epSeq, + bytes memory payload + ) public whenNotPaused { + // We should only except messages from a peer. + bytes32 peerAddress = _getPeersStorage()[sourceChainId].peerAddress; + if (sourceNttManagerAddress != UniversalAddressLibrary.fromBytes32(peerAddress)) { + revert InvalidPeer( + sourceChainId, UniversalAddressLibrary.toBytes32(sourceNttManagerAddress) + ); + } - // Compute manager message digest and record transceiver attestation. - bytes32 nttManagerMessageHash = _recordTransceiverAttestation(sourceChainId, payload); + // The endpoint uses the payload hash, not the actual payload. + bytes32 payloadHash = keccak256(payload); - if (isMessageApproved(nttManagerMessageHash)) { - executeMsg(sourceChainId, sourceNttManagerAddress, payload); - } - } + // The endpoint does replay protection and verifies that there has been at least one attestation. + (,, uint8 numAttested) = + endpoint.recvMessage(sourceChainId, sourceNttManagerAddress, epSeq, payloadHash); - /// @inheritdoc INttManager - function executeMsg( - uint16 sourceChainId, - bytes32 sourceNttManagerAddress, - TransceiverStructs.NttManagerMessage memory message - ) public whenNotPaused { - (bytes32 digest, bool alreadyExecuted) = - _isMessageExecuted(sourceChainId, sourceNttManagerAddress, message); + uint8 threshold = getThreshold(sourceChainId); - if (alreadyExecuted) { - return; + if (numAttested < threshold) { + revert ThresholdNotMet(threshold, numAttested); } - _handleMsg(sourceChainId, sourceNttManagerAddress, message, digest); + TransceiverStructs.NttManagerMessage memory message = + TransceiverStructs.parseNttManagerMessage(payload); + + bytes32 digest = TransceiverStructs.nttManagerMessageDigest(sourceChainId, message); + _handleMsg( + sourceChainId, + UniversalAddressLibrary.toBytes32(sourceNttManagerAddress), + message, + digest + ); } /// @dev Override this function to handle custom NttManager payloads. @@ -346,6 +398,8 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { queuedTransfer.recipient, queuedTransfer.refundAddress, queuedTransfer.sender, + queuedTransfer.executorQuote, + queuedTransfer.relayInstructions, queuedTransfer.transceiverInstructions ); } @@ -382,6 +436,8 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { bytes32 recipient, bytes32 refundAddress, bool shouldQueue, + bytes memory executorQuote, + bytes memory relayInstructions, bytes memory transceiverInstructions ) internal returns (uint64) { if (amount == 0) { @@ -446,6 +502,8 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { recipient, refundAddress, shouldQueue, + executorQuote, + relayInstructions, transceiverInstructions, trimmedAmount, sequence @@ -462,6 +520,8 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { recipient, refundAddress, msg.sender, + executorQuote, + relayInstructions, transceiverInstructions ); } @@ -472,6 +532,8 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { bytes32 recipient, bytes32 refundAddress, bool shouldQueue, + bytes memory executorQuote, + bytes memory relayInstructions, bytes memory transceiverInstructions, TrimmedAmount trimmedAmount, uint64 sequence @@ -500,13 +562,15 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { recipient, refundAddress, msg.sender, + executorQuote, + relayInstructions, transceiverInstructions ); // refund price quote back to sender _refundToSender(msg.value); - // return that the transfer has been enqued + // return that the transfer has been enqueued return true; } @@ -525,66 +589,130 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { bytes32 recipient, bytes32 refundAddress, address sender, + bytes memory executorQuote, + bytes memory relayInstructions, bytes memory transceiverInstructions ) internal returns (uint64 msgSequence) { // verify chain has not forked checkFork(evmChainId); - ( - address[] memory enabledTransceivers, - TransceiverStructs.TransceiverInstruction[] memory instructions, - uint256[] memory priceQuotes, - uint256 totalPriceQuote - ) = _prepareForTransfer(recipientChain, transceiverInstructions); + // Compute the quote price and refund user excess value from msg.value + uint256 epTotalPriceQuote = quoteAndRefund(recipientChain, transceiverInstructions); - // push it on the stack again to avoid a stack too deep error - uint64 seq = sequence; - - TransceiverStructs.NativeTokenTransfer memory ntt = _prepareNativeTokenTransfer( - amount, recipient, recipientChain, seq, sender, refundAddress + return _transfer( + _TransferArgs({ + sequence: sequence, + amount: amount, + recipientChain: recipientChain, + recipient: recipient, + refundAddress: refundAddress, + sender: sender, + executorQuote: executorQuote, + relayInstructions: relayInstructions, + transceiverInstructions: transceiverInstructions, + epTotalPriceQuote: epTotalPriceQuote + }) ); + } - // construct the NttManagerMessage payload - bytes memory encodedNttManagerPayload = TransceiverStructs.encodeNttManagerMessage( - TransceiverStructs.NttManagerMessage( - bytes32(uint256(seq)), - toWormholeFormat(sender), - TransceiverStructs.encodeNativeTokenTransfer(ntt) - ) - ); + /// @dev Used to get around "stack too deep. + struct _TransferArgs { + uint64 sequence; + TrimmedAmount amount; + uint16 recipientChain; + bytes32 recipient; + bytes32 refundAddress; + address sender; + bytes executorQuote; + bytes relayInstructions; + bytes transceiverInstructions; + uint256 epTotalPriceQuote; + } - // push onto the stack again to avoid stack too deep error - uint16 destinationChain = recipientChain; + function _transfer( + _TransferArgs memory args + ) internal returns (uint64 msgSequence) { + NttManagerPeer storage peerData = _getPeersStorage()[args.recipientChain]; + bytes memory encodedNttManagerPayload = buildEncodedPayload(args); // send the message - _sendMessageToTransceivers( - recipientChain, - refundAddress, - _getPeersStorage()[destinationChain].peerAddress, - priceQuotes, - instructions, - enabledTransceivers, - encodedNttManagerPayload + bytes32 payloadHash = keccak256(encodedNttManagerPayload); + uint64 epSeqNo = endpoint.sendMessage{value: args.epTotalPriceQuote}( + args.recipientChain, + UniversalAddressLibrary.fromBytes32(peerData.peerAddress), + payloadHash, + UniversalAddressLibrary.toAddress( + UniversalAddressLibrary.fromBytes32(args.refundAddress) + ), + args.transceiverInstructions ); - // push it on the stack again to avoid a stack too deep error - TrimmedAmount amt = amount; - emit TransferSent( - recipient, - refundAddress, - amt.untrim(tokenDecimals()), - totalPriceQuote, - destinationChain, - seq + args.recipient, + args.refundAddress, + args.amount.untrim(tokenDecimals()), + args.epTotalPriceQuote, + args.recipientChain, + args.sequence, + payloadHash ); - emit TransferSent( - TransceiverStructs._nttManagerMessageDigest(chainId, encodedNttManagerPayload) + uint128 gasLimit = peerData.gasLimit; + if (gasLimit == 0) { + revert InvalidGasLimitZero(args.recipientChain); + } + + bytes memory relayInstructions = RelayInstructions.encodeGas(gasLimit, 0); + if (args.relayInstructions.length != 0) { + relayInstructions = abi.encodePacked(relayInstructions, args.relayInstructions); + } + + executor.requestExecution( + args.recipientChain, + peerData.peerAddress, + UniversalAddressLibrary.fromBytes32(args.refundAddress).toAddress(), + args.executorQuote, + ExecutorMessages.makeMMRequest( + chainId, address(this), epSeqNo, encodedNttManagerPayload + ), + relayInstructions ); // return the sequence number - return seq; + return args.sequence; + } + + function quoteAndRefund( + uint16 recipientChain, + bytes memory transceiverInstructions + ) internal returns (uint256 epTotalPriceQuote) { + epTotalPriceQuote = quoteDeliveryPrice(recipientChain, transceiverInstructions); + uint256 excessValue = msg.value - epTotalPriceQuote; + if (excessValue > 0) { + _refundToSender(excessValue); + } + } + + function buildEncodedPayload( + _TransferArgs memory args + ) internal returns (bytes memory encodedNttManagerPayload) { + TransceiverStructs.NativeTokenTransfer memory ntt = _prepareNativeTokenTransfer( + args.amount, + args.recipient, + args.recipientChain, + args.sequence, + args.sender, + args.refundAddress + ); + + // construct the NttManagerMessage payload + encodedNttManagerPayload = TransceiverStructs.encodeNttManagerMessage( + TransceiverStructs.NttManagerMessage( + bytes32(uint256(args.sequence)), + toWormholeFormat(args.sender), + TransceiverStructs.encodeNativeTokenTransfer(ntt) + ) + ); } /// @dev Override this function to provide an additional payload on the NativeTokenTransfer diff --git a/evm/src/NttManager/NttManagerNoRateLimiting.sol b/evm/src/NttManager/NttManagerNoRateLimiting.sol index 82358c5e8..1e9e8aae0 100644 --- a/evm/src/NttManager/NttManagerNoRateLimiting.sol +++ b/evm/src/NttManager/NttManagerNoRateLimiting.sol @@ -12,10 +12,12 @@ import "./NttManager.sol"; /// @dev All of the developer notes from `NttManager` apply here. contract NttManagerNoRateLimiting is NttManager { constructor( + address _endpoint, + address _executor, address _token, Mode _mode, uint16 _chainId - ) NttManager(_token, _mode, _chainId, 0, true) {} + ) NttManager(_endpoint, _executor, _token, _mode, _chainId, 0, true) {} // ==================== Override RateLimiter functions ========================= @@ -95,6 +97,8 @@ contract NttManagerNoRateLimiting is NttManager { bytes32, // recipient bytes32, // refundAddress bool, // shouldQueue + bytes memory, // executorQuote + bytes memory, // relayInstructions bytes memory, // transceiverInstructions TrimmedAmount, // trimmedAmount uint64 // sequence diff --git a/evm/src/NttManager/TransceiverRegistry.sol b/evm/src/NttManager/TransceiverRegistry.sol deleted file mode 100644 index d95f39b73..000000000 --- a/evm/src/NttManager/TransceiverRegistry.sol +++ /dev/null @@ -1,320 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity >=0.8.8 <0.9.0; - -/// @title TransceiverRegistry -/// @author Wormhole Project Contributors. -/// @notice This contract is responsible for handling the registration of Transceivers. -/// @dev This contract checks that a few critical invariants hold when transceivers are added or removed, -/// including: -/// 1. If a transceiver is not registered, it should be enabled. -/// 2. The value set in the bitmap of trannsceivers -/// should directly correspond to the whether the transceiver is enabled -abstract contract TransceiverRegistry { - constructor() { - _checkTransceiversInvariants(); - } - - /// @dev Information about registered transceivers. - struct TransceiverInfo { - // whether this transceiver is registered - bool registered; - // whether this transceiver is enabled - bool enabled; - uint8 index; - } - - /// @dev Bitmap encoding the enabled transceivers. - /// invariant: forall (i: uint8), enabledTransceiverBitmap & i == 1 <=> transceiverInfos[i].enabled - struct _EnabledTransceiverBitmap { - uint64 bitmap; - } - - /// @dev Total number of registered transceivers. This number can only increase. - /// invariant: numRegisteredTransceivers <= MAX_TRANSCEIVERS - /// invariant: forall (i: uint8), - /// i < numRegisteredTransceivers <=> exists (a: address), transceiverInfos[a].index == i - struct _NumTransceivers { - uint8 registered; - uint8 enabled; - } - - uint8 constant MAX_TRANSCEIVERS = 64; - - /// @notice Error when the caller is not the transceiver. - /// @dev Selector 0xa0ae911d. - /// @param caller The address of the caller. - error CallerNotTransceiver(address caller); - - /// @notice Error when the transceiver is the zero address. - /// @dev Selector 0x2f44bd77. - error InvalidTransceiverZeroAddress(); - - /// @notice Error when the transceiver is disabled. - /// @dev Selector 0x1f61ba44. - error DisabledTransceiver(address transceiver); - - /// @notice Error when the number of registered transceivers - /// exceeeds (MAX_TRANSCEIVERS = 64). - /// @dev Selector 0x891684c3. - error TooManyTransceivers(); - - /// @notice Error when attempting to remove a transceiver - /// that is not registered. - /// @dev Selector 0xd583f470. - /// @param transceiver The address of the transceiver. - error NonRegisteredTransceiver(address transceiver); - - /// @notice Error when attempting to enable a transceiver that is already enabled. - /// @dev Selector 0x8d68f84d. - /// @param transceiver The address of the transceiver. - error TransceiverAlreadyEnabled(address transceiver); - - modifier onlyTransceiver() { - if (!_getTransceiverInfosStorage()[msg.sender].enabled) { - revert CallerNotTransceiver(msg.sender); - } - _; - } - - // =============== Storage =============================================== - - bytes32 private constant TRANSCEIVER_INFOS_SLOT = - bytes32(uint256(keccak256("ntt.transceiverInfos")) - 1); - - bytes32 private constant TRANSCEIVER_BITMAP_SLOT = - bytes32(uint256(keccak256("ntt.transceiverBitmap")) - 1); - - bytes32 private constant ENABLED_TRANSCEIVERS_SLOT = - bytes32(uint256(keccak256("ntt.enabledTransceivers")) - 1); - - bytes32 private constant REGISTERED_TRANSCEIVERS_SLOT = - bytes32(uint256(keccak256("ntt.registeredTransceivers")) - 1); - - bytes32 private constant NUM_REGISTERED_TRANSCEIVERS_SLOT = - bytes32(uint256(keccak256("ntt.numRegisteredTransceivers")) - 1); - - function _getTransceiverInfosStorage() - internal - pure - returns (mapping(address => TransceiverInfo) storage $) - { - uint256 slot = uint256(TRANSCEIVER_INFOS_SLOT); - assembly ("memory-safe") { - $.slot := slot - } - } - - function _getEnabledTransceiversStorage() internal pure returns (address[] storage $) { - uint256 slot = uint256(ENABLED_TRANSCEIVERS_SLOT); - assembly ("memory-safe") { - $.slot := slot - } - } - - function _getTransceiverBitmapStorage() - private - pure - returns (_EnabledTransceiverBitmap storage $) - { - 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") { - $.slot := slot - } - } - - function _getNumTransceiversStorage() internal pure returns (_NumTransceivers storage $) { - uint256 slot = uint256(NUM_REGISTERED_TRANSCEIVERS_SLOT); - assembly ("memory-safe") { - $.slot := slot - } - } - - // =============== Storage Getters/Setters ======================================== - - function _setTransceiver( - address transceiver - ) internal returns (uint8 index) { - mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage(); - _EnabledTransceiverBitmap storage _enabledTransceiverBitmap = _getTransceiverBitmapStorage(); - address[] storage _enabledTransceivers = _getEnabledTransceiversStorage(); - - _NumTransceivers storage _numTransceivers = _getNumTransceiversStorage(); - - if (transceiver == address(0)) { - revert InvalidTransceiverZeroAddress(); - } - - if (transceiverInfos[transceiver].registered) { - transceiverInfos[transceiver].enabled = true; - } else { - if (_numTransceivers.registered >= MAX_TRANSCEIVERS) { - revert TooManyTransceivers(); - } - - transceiverInfos[transceiver] = TransceiverInfo({ - registered: true, - enabled: true, - index: _numTransceivers.registered - }); - _numTransceivers.registered++; - _getRegisteredTransceiversStorage().push(transceiver); - } - - _enabledTransceivers.push(transceiver); - _numTransceivers.enabled++; - - uint64 updatedEnabledTransceiverBitmap = - _enabledTransceiverBitmap.bitmap | uint64(1 << transceiverInfos[transceiver].index); - // ensure that this actually changed the bitmap - if (updatedEnabledTransceiverBitmap == _enabledTransceiverBitmap.bitmap) { - revert TransceiverAlreadyEnabled(transceiver); - } - _enabledTransceiverBitmap.bitmap = updatedEnabledTransceiverBitmap; - - _checkTransceiversInvariants(); - - return transceiverInfos[transceiver].index; - } - - function _removeTransceiver( - address transceiver - ) internal { - mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage(); - _EnabledTransceiverBitmap storage _enabledTransceiverBitmap = _getTransceiverBitmapStorage(); - address[] storage _enabledTransceivers = _getEnabledTransceiversStorage(); - - if (transceiver == address(0)) { - revert InvalidTransceiverZeroAddress(); - } - - if (!transceiverInfos[transceiver].registered) { - revert NonRegisteredTransceiver(transceiver); - } - - if (!transceiverInfos[transceiver].enabled) { - revert DisabledTransceiver(transceiver); - } - - transceiverInfos[transceiver].enabled = false; - _getNumTransceiversStorage().enabled--; - - uint64 updatedEnabledTransceiverBitmap = - _enabledTransceiverBitmap.bitmap & uint64(~(1 << transceiverInfos[transceiver].index)); - // ensure that this actually changed the bitmap - assert(updatedEnabledTransceiverBitmap < _enabledTransceiverBitmap.bitmap); - _enabledTransceiverBitmap.bitmap = updatedEnabledTransceiverBitmap; - - bool removed = false; - - uint256 numEnabledTransceivers = _enabledTransceivers.length; - for (uint256 i = 0; i < numEnabledTransceivers; i++) { - if (_enabledTransceivers[i] == transceiver) { - _enabledTransceivers[i] = _enabledTransceivers[numEnabledTransceivers - 1]; - _enabledTransceivers.pop(); - removed = true; - break; - } - } - assert(removed); - - _checkTransceiversInvariants(); - // we call the invariant check on the transceiver here as well, since - // the above check only iterates through the enabled transceivers. - _checkTransceiverInvariants(transceiver); - } - - function _getEnabledTransceiversBitmap() internal view virtual returns (uint64 bitmap) { - return _getTransceiverBitmapStorage().bitmap; - } - - /// @notice Returns the Transceiver contracts that have been enabled via governance. - function getTransceivers() external pure returns (address[] memory result) { - result = _getEnabledTransceiversStorage(); - } - - /// @notice Returns the info for all enabled transceivers - function getTransceiverInfo() external view returns (TransceiverInfo[] memory) { - address[] memory enabledTransceivers = _getEnabledTransceiversStorage(); - uint256 numEnabledTransceivers = enabledTransceivers.length; - TransceiverInfo[] memory result = new TransceiverInfo[](numEnabledTransceivers); - - for (uint256 i = 0; i < numEnabledTransceivers; ++i) { - result[i] = _getTransceiverInfosStorage()[enabledTransceivers[i]]; - } - - return result; - } - - // ============== Invariants ============================================= - - /// @dev Check that the transceiver nttManager is in a valid state. - /// Checking these invariants is somewhat costly, but we only need to do it - /// when modifying the transceivers, which happens infrequently. - function _checkTransceiversInvariants() internal view { - _NumTransceivers storage _numTransceivers = _getNumTransceiversStorage(); - address[] storage _enabledTransceivers = _getEnabledTransceiversStorage(); - - uint256 numTransceiversEnabled = _numTransceivers.enabled; - assert(numTransceiversEnabled == _enabledTransceivers.length); - - for (uint256 i = 0; i < numTransceiversEnabled; i++) { - _checkTransceiverInvariants(_enabledTransceivers[i]); - } - - // invariant: each transceiver is only enabled once - for (uint256 i = 0; i < numTransceiversEnabled; i++) { - for (uint256 j = i + 1; j < numTransceiversEnabled; j++) { - assert(_enabledTransceivers[i] != _enabledTransceivers[j]); - } - } - - // invariant: numRegisteredTransceivers <= MAX_TRANSCEIVERS - assert(_numTransceivers.registered <= MAX_TRANSCEIVERS); - } - - // @dev Check that the transceiver is in a valid state. - function _checkTransceiverInvariants( - address transceiver - ) private view { - mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage(); - _EnabledTransceiverBitmap storage _enabledTransceiverBitmap = _getTransceiverBitmapStorage(); - _NumTransceivers storage _numTransceivers = _getNumTransceiversStorage(); - address[] storage _enabledTransceivers = _getEnabledTransceiversStorage(); - - TransceiverInfo memory transceiverInfo = transceiverInfos[transceiver]; - - // if an transceiver is not registered, it should not be enabled - assert( - transceiverInfo.registered || (!transceiverInfo.enabled && transceiverInfo.index == 0) - ); - - bool transceiverInEnabledBitmap = - (_enabledTransceiverBitmap.bitmap & uint64(1 << transceiverInfo.index)) != 0; - bool transceiverEnabled = transceiverInfo.enabled; - - bool transceiverInEnabledTransceivers = false; - - for (uint256 i = 0; i < _numTransceivers.enabled; i++) { - if (_enabledTransceivers[i] == transceiver) { - transceiverInEnabledTransceivers = true; - break; - } - } - - // invariant: transceiverInfos[transceiver].enabled - // <=> enabledTransceiverBitmap & (1 << transceiverInfos[transceiver].index) != 0 - assert(transceiverInEnabledBitmap == transceiverEnabled); - - // invariant: transceiverInfos[transceiver].enabled <=> transceiver in _enabledTransceivers - assert(transceiverInEnabledTransceivers == transceiverEnabled); - - assert(transceiverInfo.index < _numTransceivers.registered); - } -} diff --git a/evm/src/Transceiver/Transceiver.sol b/evm/src/Transceiver/Transceiver.sol deleted file mode 100644 index d0dfa2f7f..000000000 --- a/evm/src/Transceiver/Transceiver.sol +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity >=0.8.8 <0.9.0; - -import "wormhole-solidity-sdk/Utils.sol"; - -import "../libraries/TransceiverStructs.sol"; -import "../libraries/PausableOwnable.sol"; -import "../libraries/external/ReentrancyGuardUpgradeable.sol"; -import "../libraries/Implementation.sol"; - -import "../interfaces/INttManager.sol"; -import "../interfaces/ITransceiver.sol"; - -/// @title Transceiver -/// @author Wormhole Project Contributors. -/// @notice This contract is a base contract for Transceivers. -/// @dev The Transceiver provides basic functionality for transmitting / receiving NTT messages. -/// The contract supports pausing via an admin or owner and is upgradable. -/// -/// @dev The interface for receiving messages is not enforced by this contract. -/// Instead, inheriting contracts should implement their own receiving logic, -/// based on the verification model and serde logic associated with message handling. -abstract contract Transceiver is - ITransceiver, - PausableOwnable, - ReentrancyGuardUpgradeable, - Implementation -{ - /// @dev updating bridgeNttManager requires a new Transceiver deployment. - /// Projects should implement their own governance to remove the old Transceiver - /// contract address and then add the new one. - address public immutable nttManager; - address public immutable nttManagerToken; - address immutable deployer; - - constructor( - address _nttManager - ) { - nttManager = _nttManager; - nttManagerToken = INttManager(nttManager).token(); - deployer = msg.sender; - } - - /// =============== MODIFIERS =============================================== - - modifier onlyNttManager() { - if (msg.sender != nttManager) { - revert CallerNotNttManager(msg.sender); - } - _; - } - - /// =============== ADMIN =============================================== - - function _initialize() internal virtual override { - // check if the owner is the deployer of this contract - if (msg.sender != deployer) { - revert UnexpectedDeployer(deployer, msg.sender); - } - - __ReentrancyGuard_init(); - // owner of the transceiver is set to the owner of the nttManager - __PausedOwnable_init(msg.sender, getNttManagerOwner()); - } - - /// @dev transfer the ownership of the transceiver to a new address - /// the nttManager should be able to update transceiver ownership. - function transferTransceiverOwnership( - address newOwner - ) external onlyNttManager { - _transferOwnership(newOwner); - } - - function upgrade( - address newImplementation - ) external onlyOwner { - _upgrade(newImplementation); - } - - function _migrate() internal virtual override {} - - // @define This method checks that the the referecnes to the nttManager and its corresponding function - // are correct When new immutable variables are added, this function should be updated. - function _checkImmutables() internal view virtual override { - assert(this.nttManager() == nttManager); - assert(this.nttManagerToken() == nttManagerToken); - } - - /// =============== GETTERS & SETTERS =============================================== - - function getNttManagerOwner() public view returns (address) { - return IOwnableUpgradeable(nttManager).owner(); - } - - function getNttManagerToken() public view virtual returns (address) { - return nttManagerToken; - } - - function getTransceiverType() external view virtual returns (string memory); - - /// =============== TRANSCEIVING LOGIC =============================================== - - /// @inheritdoc ITransceiver - function quoteDeliveryPrice( - uint16 targetChain, - TransceiverStructs.TransceiverInstruction memory instruction - ) external view returns (uint256) { - return _quoteDeliveryPrice(targetChain, instruction); - } - - /// @inheritdoc ITransceiver - function sendMessage( - uint16 recipientChain, - TransceiverStructs.TransceiverInstruction memory instruction, - bytes memory nttManagerMessage, - bytes32 recipientNttManagerAddress, - bytes32 refundAddress - ) external payable nonReentrant onlyNttManager { - _sendMessage( - recipientChain, - msg.value, - msg.sender, - recipientNttManagerAddress, - refundAddress, - instruction, - nttManagerMessage - ); - } - - /// ============================= INTERNAL ========================================= - - function _sendMessage( - uint16 recipientChain, - uint256 deliveryPayment, - address caller, - bytes32 recipientNttManagerAddress, - bytes32 refundAddress, - TransceiverStructs.TransceiverInstruction memory transceiverInstruction, - bytes memory nttManagerMessage - ) internal virtual; - - // @define This method is called by the BridgeNttManager contract to send a cross-chain message. - // @reverts if: - // - `recipientNttManagerAddress` does not match the address of this manager contract - function _deliverToNttManager( - uint16 sourceChainId, - bytes32 sourceNttManagerAddress, - bytes32 recipientNttManagerAddress, - TransceiverStructs.NttManagerMessage memory payload - ) internal virtual { - if (recipientNttManagerAddress != toWormholeFormat(nttManager)) { - revert UnexpectedRecipientNttManagerAddress( - toWormholeFormat(nttManager), recipientNttManagerAddress - ); - } - INttManager(nttManager).attestationReceived(sourceChainId, sourceNttManagerAddress, payload); - } - - function _quoteDeliveryPrice( - uint16 targetChain, - TransceiverStructs.TransceiverInstruction memory transceiverInstruction - ) internal view virtual returns (uint256); -} diff --git a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol deleted file mode 100644 index 2cf3550c5..000000000 --- a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol +++ /dev/null @@ -1,284 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity >=0.8.8 <0.9.0; - -import "wormhole-solidity-sdk/WormholeRelayerSDK.sol"; -import "wormhole-solidity-sdk/libraries/BytesParsing.sol"; -import "wormhole-solidity-sdk/interfaces/IWormhole.sol"; - -import "../../libraries/TransceiverHelpers.sol"; -import "../../libraries/TransceiverStructs.sol"; - -import "../../interfaces/IWormholeTransceiver.sol"; -import "../../interfaces/ISpecialRelayer.sol"; -import "../../interfaces/INttManager.sol"; - -import "./WormholeTransceiverState.sol"; - -/// @title WormholeTransceiver -/// @author Wormhole Project Contributors. -/// @notice Transceiver implementation for Wormhole. -/// -/// @dev This contract is responsible for sending and receiving NTT messages -/// that are authenticated through Wormhole Core. -/// -/// @dev Messages can be delivered either via standard relaying or special relaying, or -/// manually via the core layer. -/// -/// @dev Once a message is received, it is delivered to its corresponding -/// NttManager contract. -contract WormholeTransceiver is - IWormholeTransceiver, - IWormholeReceiver, - WormholeTransceiverState -{ - using BytesParsing for bytes; - - string public constant WORMHOLE_TRANSCEIVER_VERSION = "1.1.0"; - - constructor( - address nttManager, - address wormholeCoreBridge, - address wormholeRelayerAddr, - address specialRelayerAddr, - uint8 _consistencyLevel, - uint256 _gasLimit - ) - WormholeTransceiverState( - nttManager, - wormholeCoreBridge, - wormholeRelayerAddr, - specialRelayerAddr, - _consistencyLevel, - _gasLimit - ) - {} - - // ==================== External Interface =============================================== - - function getTransceiverType() external pure override returns (string memory) { - return "wormhole"; - } - - /// @inheritdoc IWormholeTransceiver - function receiveMessage( - bytes memory encodedMessage - ) external { - uint16 sourceChainId; - bytes memory payload; - (sourceChainId, payload) = _verifyMessage(encodedMessage); - - // parse the encoded Transceiver payload - TransceiverStructs.TransceiverMessage memory parsedTransceiverMessage; - TransceiverStructs.NttManagerMessage memory parsedNttManagerMessage; - (parsedTransceiverMessage, parsedNttManagerMessage) = TransceiverStructs - .parseTransceiverAndNttManagerMessage(WH_TRANSCEIVER_PAYLOAD_PREFIX, payload); - - _deliverToNttManager( - sourceChainId, - parsedTransceiverMessage.sourceNttManagerAddress, - parsedTransceiverMessage.recipientNttManagerAddress, - parsedNttManagerMessage - ); - } - - /// @inheritdoc IWormholeReceiver - function receiveWormholeMessages( - bytes memory payload, - bytes[] memory additionalMessages, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 deliveryHash - ) external payable onlyRelayer { - if (getWormholePeer(sourceChain) != sourceAddress) { - revert InvalidWormholePeer(sourceChain, sourceAddress); - } - - // VAA replay protection: - // - Note that this VAA is for the AR delivery, not for the raw message emitted by the source - // - chain Transceiver contract. The VAAs received by this entrypoint are different than the - // - VAA received by the receiveMessage entrypoint. - if (isVAAConsumed(deliveryHash)) { - revert TransferAlreadyCompleted(deliveryHash); - } - _setVAAConsumed(deliveryHash); - - // We don't honor additional messages in this handler. - if (additionalMessages.length > 0) { - revert UnexpectedAdditionalMessages(); - } - - // emit `ReceivedRelayedMessage` event - emit ReceivedRelayedMessage(deliveryHash, sourceChain, sourceAddress); - - // parse the encoded Transceiver payload - TransceiverStructs.TransceiverMessage memory parsedTransceiverMessage; - TransceiverStructs.NttManagerMessage memory parsedNttManagerMessage; - (parsedTransceiverMessage, parsedNttManagerMessage) = TransceiverStructs - .parseTransceiverAndNttManagerMessage(WH_TRANSCEIVER_PAYLOAD_PREFIX, payload); - - _deliverToNttManager( - sourceChain, - parsedTransceiverMessage.sourceNttManagerAddress, - parsedTransceiverMessage.recipientNttManagerAddress, - parsedNttManagerMessage - ); - } - - /// @inheritdoc IWormholeTransceiver - function parseWormholeTransceiverInstruction( - bytes memory encoded - ) public pure returns (WormholeTransceiverInstruction memory instruction) { - // If the user doesn't pass in any transceiver instructions then the default is false - if (encoded.length == 0) { - instruction.shouldSkipRelayerSend = false; - return instruction; - } - - uint256 offset = 0; - (instruction.shouldSkipRelayerSend, offset) = encoded.asBoolUnchecked(offset); - encoded.checkLength(offset); - } - - /// @inheritdoc IWormholeTransceiver - function encodeWormholeTransceiverInstruction( - WormholeTransceiverInstruction memory instruction - ) public pure returns (bytes memory) { - return abi.encodePacked(instruction.shouldSkipRelayerSend); - } - - // ==================== Internal ======================================================== - - function _quoteDeliveryPrice( - uint16 targetChain, - TransceiverStructs.TransceiverInstruction memory instruction - ) internal view override returns (uint256 nativePriceQuote) { - // Check the special instruction up front to see if we should skip sending via a relayer - WormholeTransceiverInstruction memory weIns = - parseWormholeTransceiverInstruction(instruction.payload); - if (weIns.shouldSkipRelayerSend) { - return wormhole.messageFee(); - } - - if (_checkInvalidRelayingConfig(targetChain)) { - revert InvalidRelayingConfig(targetChain); - } - - if (_shouldRelayViaStandardRelaying(targetChain)) { - (uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, 0, gasLimit); - return cost; - } else if (isSpecialRelayingEnabled(targetChain)) { - uint256 cost = specialRelayer.quoteDeliveryPrice(getNttManagerToken(), targetChain, 0); - // We need to pay both the special relayer cost and the Wormhole message fee independently - return cost + wormhole.messageFee(); - } else { - return wormhole.messageFee(); - } - } - - function _sendMessage( - uint16 recipientChain, - uint256 deliveryPayment, - address caller, - bytes32 recipientNttManagerAddress, - bytes32 refundAddress, - TransceiverStructs.TransceiverInstruction memory instruction, - bytes memory nttManagerMessage - ) internal override { - ( - TransceiverStructs.TransceiverMessage memory transceiverMessage, - bytes memory encodedTransceiverPayload - ) = TransceiverStructs.buildAndEncodeTransceiverMessage( - WH_TRANSCEIVER_PAYLOAD_PREFIX, - toWormholeFormat(caller), - recipientNttManagerAddress, - nttManagerMessage, - new bytes(0) - ); - - WormholeTransceiverInstruction memory weIns = - parseWormholeTransceiverInstruction(instruction.payload); - - if (!weIns.shouldSkipRelayerSend && _shouldRelayViaStandardRelaying(recipientChain)) { - // NOTE: standard relaying supports refunds. The amount to be refunded will be sent - // to a refundAddress specified by the client on the destination chain. - - // push onto the stack again to avoid stack too deep error - bytes32 refundRecipient = refundAddress; - uint16 destinationChain = recipientChain; - - wormholeRelayer.sendToEvm{value: deliveryPayment}( - destinationChain, - fromWormholeFormat(getWormholePeer(destinationChain)), - encodedTransceiverPayload, - 0, // receiverValue - 0, // paymentForExtraReceiverValue, - gasLimit, - destinationChain, - fromWormholeFormat(refundRecipient), - wormholeRelayer.getDefaultDeliveryProvider(), - new VaaKey[](0), - consistencyLevel - ); - - emit RelayingInfo(uint8(RelayingType.Standard), refundAddress, deliveryPayment); - } else if (!weIns.shouldSkipRelayerSend && isSpecialRelayingEnabled(recipientChain)) { - uint256 wormholeFee = wormhole.messageFee(); - uint64 sequence = wormhole.publishMessage{value: wormholeFee}( - 0, encodedTransceiverPayload, consistencyLevel - ); - specialRelayer.requestDelivery{value: deliveryPayment - wormholeFee}( - getNttManagerToken(), recipientChain, 0, sequence - ); - - // NOTE: specialized relaying does not currently support refunds. The zero address - // is used as a placeholder for the refund address until support is added. - emit RelayingInfo(uint8(RelayingType.Special), bytes32(0), deliveryPayment); - } else { - wormhole.publishMessage{value: deliveryPayment}( - 0, encodedTransceiverPayload, consistencyLevel - ); - - // NOTE: manual relaying does not currently support refunds. The zero address - // is used as refundAddress. - emit RelayingInfo(uint8(RelayingType.Manual), bytes32(0), deliveryPayment); - } - - emit SendTransceiverMessage(recipientChain, transceiverMessage); - } - - function _verifyMessage( - bytes memory encodedMessage - ) internal returns (uint16, bytes memory) { - // verify VAA against Wormhole Core Bridge contract - (IWormhole.VM memory vm, bool valid, string memory reason) = - wormhole.parseAndVerifyVM(encodedMessage); - - // ensure that the VAA is valid - if (!valid) { - revert InvalidVaa(reason); - } - - // ensure that the message came from a registered peer contract - if (!_verifyBridgeVM(vm)) { - revert InvalidWormholePeer(vm.emitterChainId, vm.emitterAddress); - } - - // save the VAA hash in storage to protect against replay attacks. - if (isVAAConsumed(vm.hash)) { - revert TransferAlreadyCompleted(vm.hash); - } - _setVAAConsumed(vm.hash); - - // emit `ReceivedMessage` event - emit ReceivedMessage(vm.hash, vm.emitterChainId, vm.emitterAddress, vm.sequence); - - return (vm.emitterChainId, vm.payload); - } - - function _verifyBridgeVM( - IWormhole.VM memory vm - ) internal view returns (bool) { - checkFork(wormholeTransceiver_evmChainId); - return getWormholePeer(vm.emitterChainId) == vm.emitterAddress; - } -} diff --git a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol deleted file mode 100644 index b58db7dc2..000000000 --- a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol +++ /dev/null @@ -1,304 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity >=0.8.8 <0.9.0; - -import "wormhole-solidity-sdk/WormholeRelayerSDK.sol"; -import "wormhole-solidity-sdk/libraries/BytesParsing.sol"; -import "wormhole-solidity-sdk/interfaces/IWormhole.sol"; - -import "../../libraries/TransceiverHelpers.sol"; -import "../../libraries/BooleanFlag.sol"; -import "../../libraries/TransceiverStructs.sol"; - -import "../../interfaces/IWormholeTransceiver.sol"; -import "../../interfaces/IWormholeTransceiverState.sol"; -import "../../interfaces/ISpecialRelayer.sol"; -import "../../interfaces/INttManager.sol"; - -import "../Transceiver.sol"; - -abstract contract WormholeTransceiverState is IWormholeTransceiverState, Transceiver { - using BytesParsing for bytes; - using BooleanFlagLib for bool; - using BooleanFlagLib for BooleanFlag; - - // ==================== Immutables =============================================== - uint8 public immutable consistencyLevel; - IWormhole public immutable wormhole; - IWormholeRelayer public immutable wormholeRelayer; - ISpecialRelayer public immutable specialRelayer; - /// @dev We don't check this in `_checkImmutables` since it's set at construction - /// through `block.chainid`. - uint256 immutable wormholeTransceiver_evmChainId; - /// @dev We purposely avoid checking this in `_checkImmutables` to allow tweaking it - /// without needing to allow modification of security critical immutables. - uint256 public immutable gasLimit; - - // ==================== Constants ================================================ - - /// @dev Prefix for all TransceiverMessage payloads - /// @notice Magic string (constant value set by messaging provider) that idenfies the payload as an transceiver-emitted payload. - /// Note that this is not a security critical field. It's meant to be used by messaging providers to identify which messages are Transceiver-related. - bytes4 constant WH_TRANSCEIVER_PAYLOAD_PREFIX = 0x9945FF10; - - /// @dev Prefix for all Wormhole transceiver initialisation payloads - /// This is bytes4(keccak256("WormholeTransceiverInit")) - bytes4 constant WH_TRANSCEIVER_INIT_PREFIX = 0x9c23bd3b; - - /// @dev Prefix for all Wormhole peer registration payloads - /// This is bytes4(keccak256("WormholePeerRegistration")) - bytes4 constant WH_PEER_REGISTRATION_PREFIX = 0x18fc67c2; - - constructor( - address nttManager, - address wormholeCoreBridge, - address wormholeRelayerAddr, - address specialRelayerAddr, - uint8 _consistencyLevel, - uint256 _gasLimit - ) Transceiver(nttManager) { - wormhole = IWormhole(wormholeCoreBridge); - wormholeRelayer = IWormholeRelayer(wormholeRelayerAddr); - specialRelayer = ISpecialRelayer(specialRelayerAddr); - wormholeTransceiver_evmChainId = block.chainid; - consistencyLevel = _consistencyLevel; - gasLimit = _gasLimit; - } - - enum RelayingType { - Standard, - Special, - Manual - } - - function _initialize() internal override { - super._initialize(); - _initializeTransceiver(); - } - - function _initializeTransceiver() internal { - TransceiverStructs.TransceiverInit memory init = TransceiverStructs.TransceiverInit({ - transceiverIdentifier: WH_TRANSCEIVER_INIT_PREFIX, - nttManagerAddress: toWormholeFormat(nttManager), - nttManagerMode: INttManager(nttManager).getMode(), - tokenAddress: toWormholeFormat(nttManagerToken), - tokenDecimals: INttManager(nttManager).tokenDecimals() - }); - wormhole.publishMessage{value: msg.value}( - 0, TransceiverStructs.encodeTransceiverInit(init), consistencyLevel - ); - } - - function _checkImmutables() internal view override { - super._checkImmutables(); - assert(this.wormhole() == wormhole); - assert(this.wormholeRelayer() == wormholeRelayer); - assert(this.specialRelayer() == specialRelayer); - assert(this.consistencyLevel() == consistencyLevel); - } - - // =============== Storage =============================================== - - bytes32 private constant WORMHOLE_CONSUMED_VAAS_SLOT = - bytes32(uint256(keccak256("whTransceiver.consumedVAAs")) - 1); - - bytes32 private constant WORMHOLE_PEERS_SLOT = - bytes32(uint256(keccak256("whTransceiver.peers")) - 1); - - bytes32 private constant WORMHOLE_RELAYING_ENABLED_CHAINS_SLOT = - bytes32(uint256(keccak256("whTransceiver.relayingEnabledChains")) - 1); - - bytes32 private constant SPECIAL_RELAYING_ENABLED_CHAINS_SLOT = - bytes32(uint256(keccak256("whTransceiver.specialRelayingEnabledChains")) - 1); - - bytes32 private constant WORMHOLE_EVM_CHAIN_IDS = - bytes32(uint256(keccak256("whTransceiver.evmChainIds")) - 1); - - // =============== Storage Setters/Getters ======================================== - - function _getWormholeConsumedVAAsStorage() - internal - pure - returns (mapping(bytes32 => bool) storage $) - { - uint256 slot = uint256(WORMHOLE_CONSUMED_VAAS_SLOT); - assembly ("memory-safe") { - $.slot := slot - } - } - - function _getWormholePeersStorage() - internal - pure - returns (mapping(uint16 => bytes32) storage $) - { - uint256 slot = uint256(WORMHOLE_PEERS_SLOT); - assembly ("memory-safe") { - $.slot := slot - } - } - - function _getWormholeRelayingEnabledChainsStorage() - internal - pure - returns (mapping(uint16 => BooleanFlag) storage $) - { - uint256 slot = uint256(WORMHOLE_RELAYING_ENABLED_CHAINS_SLOT); - assembly ("memory-safe") { - $.slot := slot - } - } - - function _getSpecialRelayingEnabledChainsStorage() - internal - pure - returns (mapping(uint16 => BooleanFlag) storage $) - { - uint256 slot = uint256(SPECIAL_RELAYING_ENABLED_CHAINS_SLOT); - assembly ("memory-safe") { - $.slot := slot - } - } - - function _getWormholeEvmChainIdsStorage() - internal - pure - returns (mapping(uint16 => BooleanFlag) storage $) - { - uint256 slot = uint256(WORMHOLE_EVM_CHAIN_IDS); - assembly ("memory-safe") { - $.slot := slot - } - } - - // =============== Public Getters ====================================================== - - /// @inheritdoc IWormholeTransceiverState - function isVAAConsumed( - bytes32 hash - ) public view returns (bool) { - return _getWormholeConsumedVAAsStorage()[hash]; - } - - /// @inheritdoc IWormholeTransceiverState - function getWormholePeer( - uint16 chainId - ) public view returns (bytes32) { - return _getWormholePeersStorage()[chainId]; - } - - /// @inheritdoc IWormholeTransceiverState - function isWormholeRelayingEnabled( - uint16 chainId - ) public view returns (bool) { - return _getWormholeRelayingEnabledChainsStorage()[chainId].toBool(); - } - - /// @inheritdoc IWormholeTransceiverState - function isSpecialRelayingEnabled( - uint16 chainId - ) public view returns (bool) { - return _getSpecialRelayingEnabledChainsStorage()[chainId].toBool(); - } - - /// @inheritdoc IWormholeTransceiverState - function isWormholeEvmChain( - uint16 chainId - ) public view returns (bool) { - return _getWormholeEvmChainIdsStorage()[chainId].toBool(); - } - - // =============== Admin =============================================================== - - /// @inheritdoc IWormholeTransceiverState - function setWormholePeer(uint16 peerChainId, bytes32 peerContract) external payable onlyOwner { - if (peerChainId == 0) { - revert InvalidWormholeChainIdZero(); - } - if (peerContract == bytes32(0)) { - revert InvalidWormholePeerZeroAddress(); - } - - bytes32 oldPeerContract = _getWormholePeersStorage()[peerChainId]; - - // We don't want to allow updating a peer since this adds complexity in the accountant - // If the owner makes a mistake with peer registration they should deploy a new Wormhole - // transceiver and register this new transceiver with the NttManager - if (oldPeerContract != bytes32(0)) { - revert PeerAlreadySet(peerChainId, oldPeerContract); - } - - _getWormholePeersStorage()[peerChainId] = peerContract; - - // Publish a message for this transceiver registration - TransceiverStructs.TransceiverRegistration memory registration = TransceiverStructs - .TransceiverRegistration({ - transceiverIdentifier: WH_PEER_REGISTRATION_PREFIX, - transceiverChainId: peerChainId, - transceiverAddress: peerContract - }); - wormhole.publishMessage{value: msg.value}( - 0, TransceiverStructs.encodeTransceiverRegistration(registration), consistencyLevel - ); - - emit SetWormholePeer(peerChainId, peerContract); - } - - /// @inheritdoc IWormholeTransceiverState - function setIsWormholeEvmChain(uint16 chainId, bool isEvm) external onlyOwner { - if (chainId == 0) { - revert InvalidWormholeChainIdZero(); - } - _getWormholeEvmChainIdsStorage()[chainId] = isEvm.toWord(); - - emit SetIsWormholeEvmChain(chainId, isEvm); - } - - /// @inheritdoc IWormholeTransceiverState - function setIsWormholeRelayingEnabled(uint16 chainId, bool isEnabled) external onlyOwner { - if (chainId == 0) { - revert InvalidWormholeChainIdZero(); - } - _getWormholeRelayingEnabledChainsStorage()[chainId] = isEnabled.toWord(); - - emit SetIsWormholeRelayingEnabled(chainId, isEnabled); - } - - /// @inheritdoc IWormholeTransceiverState - function setIsSpecialRelayingEnabled(uint16 chainId, bool isEnabled) external onlyOwner { - if (chainId == 0) { - revert InvalidWormholeChainIdZero(); - } - _getSpecialRelayingEnabledChainsStorage()[chainId] = isEnabled.toWord(); - - emit SetIsSpecialRelayingEnabled(chainId, isEnabled); - } - - // ============= Internal =============================================================== - - function _checkInvalidRelayingConfig( - uint16 chainId - ) internal view returns (bool) { - return isWormholeRelayingEnabled(chainId) && !isWormholeEvmChain(chainId); - } - - function _shouldRelayViaStandardRelaying( - uint16 chainId - ) internal view returns (bool) { - return isWormholeRelayingEnabled(chainId) && isWormholeEvmChain(chainId); - } - - function _setVAAConsumed( - bytes32 hash - ) internal { - _getWormholeConsumedVAAsStorage()[hash] = true; - } - - // =============== MODIFIERS =============================================== - - modifier onlyRelayer() { - if (msg.sender != address(wormholeRelayer)) { - revert CallerNotRelayer(msg.sender); - } - _; - } -} diff --git a/evm/src/interfaces/IManagerBase.sol b/evm/src/interfaces/IManagerBase.sol index c4d1d565f..706200397 100644 --- a/evm/src/interfaces/IManagerBase.sol +++ b/evm/src/interfaces/IManagerBase.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache 2 pragma solidity >=0.8.8 <0.9.0; +import "example-messaging-endpoint/evm/src/libraries/UniversalAddress.sol"; + import "../libraries/TransceiverStructs.sol"; interface IManagerBase { @@ -39,27 +41,20 @@ interface IManagerBase { /// @param index The index of the transceiver in the bitmap. event MessageAttestedTo(bytes32 digest, address transceiver, uint8 index); - /// @notice Emmitted when the threshold required transceivers is changed. + /// @notice Emitted when the threshold for a chain is changed. /// @dev Topic0 - /// 0x2a855b929b9a53c6fb5b5ed248b27e502b709c088e036a5aa17620c8fc5085a9. + /// 0x77bc50d64a1fb4b6ef32894bc26fc008f4ee08223914c45472c9d62088c97f38. + /// @param chainId The chain for which the threshold was updated. /// @param oldThreshold The old threshold. /// @param threshold The new threshold. - event ThresholdChanged(uint8 oldThreshold, uint8 threshold); + event ThresholdChanged(uint16 chainId, uint8 oldThreshold, uint8 threshold); /// @notice Emitted when an transceiver is removed from the nttManager. /// @dev Topic0 - /// 0xf05962b5774c658e85ed80c91a75af9d66d2af2253dda480f90bce78aff5eda5. + /// 0x2fb241a51a63da05063ac6be1f963395b281e455e8085bd246a7e8502b8950d5. /// @param transceiver The address of the transceiver. /// @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 an transceiver is removed from the nttManager. - /// @dev Topic0 - /// 0x697a3853515b88013ad432f29f53d406debc9509ed6d9313dcfe115250fcd18f. - /// @param transceiver The address of the transceiver. - /// @param threshold The current threshold of transceivers. - event TransceiverRemoved(address transceiver, uint8 threshold); + event TransceiverAdded(address transceiver, uint256 transceiversNum); /// @notice payment for a transfer is too low. /// @param requiredPayment The required payment. @@ -92,14 +87,6 @@ interface IManagerBase { /// @param msgHash The hash of the message. error MessageNotApproved(bytes32 msgHash); - /// @notice Emitted when a message has already been executed to - /// notify client of against retries. - /// @dev Topic0 - /// 0x4069dff8c9df7e38d2867c0910bd96fd61787695e5380281148c04932d02bef2. - /// @param sourceNttManager The address of the source nttManager. - /// @param msgHash The keccak-256 hash of the message. - event MessageAlreadyExecuted(bytes32 indexed sourceNttManager, bytes32 indexed msgHash); - /// @notice There are no transceivers enabled with the Manager /// @dev Selector 0x69cf632a error NoEnabledTransceivers(); @@ -111,20 +98,19 @@ interface IManagerBase { /// @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 + /// @param transceiverInstructions The encoded adapter instructions to be passed to the endpoint. /// @return - The delivery prices associated with each enabled endpoint and the total price. function quoteDeliveryPrice( uint16 recipientChain, bytes memory transceiverInstructions - ) external view returns (uint256[] memory, uint256); + ) external view returns (uint256); /// @notice Sets the threshold for the number of attestations required for a message /// to be considered valid. + /// @param chain The chain to which the threshold update applies. /// @param threshold The new threshold (number of attestations). /// @dev This method can only be executed by the `owner`. - function setThreshold( - uint8 threshold - ) external; + function setThreshold(uint16 chain, uint8 threshold) external; /// @notice Sets the transceiver for the given chain. /// @param transceiver The address of the transceiver. @@ -133,26 +119,55 @@ interface IManagerBase { address transceiver ) external; - /// @notice Removes the transceiver for the given chain. - /// @param transceiver The address of the transceiver. - /// @dev This method can only be executed by the `owner`. - function removeTransceiver( - address transceiver - ) external; + /// @notice This enables the sending of messages from the given transceiver on the given chain. + /// @param transceiver The address of the Transceiver contract. + /// @param chain The chain ID of the Transceiver contract. + function enableSendTransceiver(uint16 chain, address transceiver) external; + + /// @notice This enables the receiving of messages by the given transceiver on the given chain. + /// @param transceiver The address of the Transceiver contract. + /// @param chain The chain ID of the Transceiver contract. + function enableRecvTransceiver(uint16 chain, address transceiver) external; + + /// @notice This disables the sending of messages from the given transceiver on the given chain. + /// @param transceiver The address of the Transceiver contract. + /// @param chain The chain ID of the Transceiver contract. + function disableSendTransceiver(uint16 chain, address transceiver) external; + + /// @notice This disables the receiving of messages by the given transceiver on the given chain. + /// @param transceiver The address of the Transceiver contract. + /// @param chain The chain ID of the Transceiver contract. + function disableRecvTransceiver(uint16 chain, address transceiver) external; /// @notice Checks if a message has been approved. The message should have at least /// the minimum threshold of attestations from distinct endpoints. - /// @param digest The digest of the message. + /// @param srcChain The Wormhole chain ID of the sender. + /// @param srcAddr The universal address of the message. + /// @param sequence The sequence number of the message. + /// @param dstAddr The destination address of the message. + /// @param payloadHash The keccak256 of payload from the integrator. /// @return - Boolean indicating if message has been approved. function isMessageApproved( - bytes32 digest + uint16 srcChain, + UniversalAddress srcAddr, + uint64 sequence, + UniversalAddress dstAddr, + bytes32 payloadHash ) external view returns (bool); /// @notice Checks if a message has been executed. - /// @param digest The digest of the message. + /// @param srcChain The Wormhole chain ID of the sender. + /// @param srcAddr The universal address of the message. + /// @param sequence The sequence number of the message. + /// @param dstAddr The destination address of the message. + /// @param payloadHash The keccak256 of payload from the integrator. /// @return - Boolean indicating if message has been executed. function isMessageExecuted( - bytes32 digest + uint16 srcChain, + UniversalAddress srcAddr, + uint64 sequence, + UniversalAddress dstAddr, + bytes32 payloadHash ) external view returns (bool); /// @notice Returns the next message sequence. @@ -173,24 +188,43 @@ interface IManagerBase { /// @return mode A uint8 corresponding to the mode function getMode() external view returns (uint8); - /// @notice Returns the number of Transceivers that must attest to a msgId for + /// @notice Returns the threshold for the specified chain. /// it to be considered valid and acted upon. - function getThreshold() external view returns (uint8); + /// @param chain The chain for which the threshold is requested. + function getThreshold( + uint16 chain + ) 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 srcChain The Wormhole chain ID of the sender. + /// @param srcAddr The universal address of the message. + /// @param sequence The sequence number of the message. + /// @param dstAddr The destination address of the message. + /// @param payloadHash The keccak256 of payload from the integrator. /// @param index The index of the transceiver /// @return - Boolean indicating whether the transceiver at index `index` attested to a message digest function transceiverAttestedToMessage( - bytes32 digest, + uint16 srcChain, + UniversalAddress srcAddr, + uint64 sequence, + UniversalAddress dstAddr, + bytes32 payloadHash, uint8 index ) external view returns (bool); /// @notice Returns the number of attestations for a given message. - /// @param digest The digest of the message. + /// @param srcChain The Wormhole chain ID of the sender. + /// @param srcAddr The universal address of the message. + /// @param sequence The sequence number of the message. + /// @param dstAddr The destination address of the message. + /// @param payloadHash The keccak256 of payload from the integrator. /// @return count The number of attestations received for the given message digest function messageAttestations( - bytes32 digest + uint16 srcChain, + UniversalAddress srcAddr, + uint64 sequence, + UniversalAddress dstAddr, + bytes32 payloadHash ) external view returns (uint8 count); /// @notice Returns of the address of the token managed by this contract. diff --git a/evm/src/interfaces/INttManager.sol b/evm/src/interfaces/INttManager.sol index 3ace687fe..57e67f37a 100644 --- a/evm/src/interfaces/INttManager.sol +++ b/evm/src/interfaces/INttManager.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache 2 pragma solidity >=0.8.8 <0.9.0; +import "example-messaging-endpoint/evm/src/libraries/UniversalAddress.sol"; + import "../libraries/TrimmedAmount.sol"; import "../libraries/TransceiverStructs.sol"; @@ -11,11 +13,12 @@ interface INttManager is IManagerBase { struct NttManagerPeer { bytes32 peerAddress; uint8 tokenDecimals; + uint128 gasLimit; } /// @notice Emitted when a message is sent from the nttManager. /// @dev Topic0 - /// 0xe54e51e42099622516fa3b48e9733581c9dbdcb771cafb093f745a0532a35982. + /// 0x75eb8927cc7c4810b30fa2e8011fce37da6da7d18eb82c642c367ae4445c3625. /// @param recipient The recipient of the message. /// @param refundAddress The address on the destination chain to which the /// refund of unused gas will be paid @@ -29,7 +32,8 @@ interface INttManager is IManagerBase { uint256 amount, uint256 fee, uint16 recipientChain, - uint64 msgSequence + uint64 msgSequence, + bytes32 msgHash ); /// @notice Emitted when a message is sent from the nttManager. @@ -141,9 +145,20 @@ interface INttManager is IManagerBase { /// @dev Selector 0x20371f2a. error InvalidPeerSameChainId(); + /// @notice Threshold has not been met for an attestation. + /// @dev Selector 0xf6f12287. + /// @param threshold The required threshold. + /// @param numAttested The number of attestations for this message. + error ThresholdNotMet(uint8 threshold, uint8 numAttested); + /// @notice Feature is not implemented. error NotImplemented(); + /// @notice The gas limit for the specified chain is not set. + /// @dev Selector 0xb30dea62. + /// @param destChain The chain ID for which the gas limit is not set. + error InvalidGasLimitZero(uint16 destChain); + /// @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 @@ -151,11 +166,17 @@ interface INttManager is IManagerBase { /// @param amount The amount to transfer. /// @param recipientChain The Wormhole chain ID for the destination. /// @param recipient The recipient address. + /// @param executorQuote The signed quote to be passed to the executor. + /// @param relayInstructions The relay instructions to be passed to the executor. + /// @param transceiverInstructions The adapter instructions to be passed to the endpoint. /// @return msgId The resulting message ID of the transfer function transfer( uint256 amount, uint16 recipientChain, - bytes32 recipient + bytes32 recipient, + bytes calldata executorQuote, + bytes calldata relayInstructions, + bytes calldata transceiverInstructions ) external payable returns (uint64 msgId); /// @notice Transfer a given amount to a recipient on a given chain. This function is called @@ -166,9 +187,11 @@ interface INttManager is IManagerBase { /// @param amount The amount to transfer. /// @param recipientChain The Wormhole chain ID for the destination. /// @param recipient The recipient address. - /// @param refundAddress The address to which a refund for unussed gas is issued on the recipient chain. + /// @param refundAddress The address to which a refund for unused gas is issued on the recipient chain. /// @param shouldQueue Whether the transfer should be queued if the outbound limit is hit. - /// @param encodedInstructions Additional instructions to be forwarded to the recipient chain. + /// @param executorQuote The signed quote to be passed to the executor. + /// @param relayInstructions The relay instructions to be passed to the executor. + /// @param transceiverInstructions The adapter instructions to be passed to the endpoint. /// @return msgId The resulting message ID of the transfer function transfer( uint256 amount, @@ -176,7 +199,9 @@ interface INttManager is IManagerBase { bytes32 recipient, bytes32 refundAddress, bool shouldQueue, - bytes memory encodedInstructions + bytes calldata executorQuote, + bytes calldata relayInstructions, + bytes calldata transceiverInstructions ) external payable returns (uint64 msgId); /// @notice Complete an outbound transfer that's been queued. @@ -200,31 +225,20 @@ interface INttManager is IManagerBase { bytes32 digest ) external; - /// @notice Called by an Endpoint contract to deliver a verified attestation. - /// @dev This function enforces attestation threshold and replay logic for messages. Once all - /// validations are complete, this function calls `executeMsg` to execute the command specified - /// by the message. - /// @param sourceChainId The Wormhole chain id of the sender. - /// @param sourceNttManagerAddress The address of the sender's NTT Manager contract. - /// @param payload The VAA payload. - function attestationReceived( - uint16 sourceChainId, - bytes32 sourceNttManagerAddress, - TransceiverStructs.NttManagerMessage memory payload - ) external; - /// @notice Called after a message has been sufficiently verified to execute /// the command in the message. This function will decode the payload /// as an NttManagerMessage to extract the sequence, msgType, and other parameters. /// @dev This function is exposed as a fallback for when an `Transceiver` is deregistered /// when a message is in flight. - /// @param sourceChainId The Wormhole chain id of the sender. - /// @param sourceNttManagerAddress The address of the sender's nttManager contract. - /// @param message The message to execute. + /// @param srcChain The Wormhole chain ID of the sender. + /// @param srcAddr The universal address of the peer on the sending chain. + /// @param sequence The sequence number of the message (per integrator). + /// @param payload The message to execute. function executeMsg( - uint16 sourceChainId, - bytes32 sourceNttManagerAddress, - TransceiverStructs.NttManagerMessage memory message + uint16 srcChain, + UniversalAddress srcAddr, + uint64 sequence, + bytes memory payload ) external; /// @notice Returns the number of decimals of the token managed by the NttManager. @@ -242,15 +256,23 @@ interface INttManager is IManagerBase { /// @param peerChainId The Wormhole chain ID of the peer. /// @param peerContract The address of the peer nttManager contract. /// @param decimals The number of decimals of the token on the peer chain. + /// @param gasLimit The gas limit for the peer chain. /// @param inboundLimit The inbound rate limit for the peer chain id. This is formatted in the normal /// token representation. e.g. a limit of 100 for a token with 6 decimals = 100_000_000 function setPeer( uint16 peerChainId, bytes32 peerContract, uint8 decimals, + uint128 gasLimit, uint256 inboundLimit ) external; + /// @notice Sets the gas limit for a given chain. + /// @dev This method can only be executed by the `owner`. + /// @param peerChainId The Wormhole chain ID of the peer. + /// @param limit The new gas limit. + function setGasLimit(uint16 peerChainId, uint128 limit) external; + /// @notice Sets the outbound transfer limit for a given chain. /// @dev This method can only be executed by the `owner`. /// @param limit The new outbound limit. This is formatted in the normal diff --git a/evm/src/interfaces/IRateLimiter.sol b/evm/src/interfaces/IRateLimiter.sol index 05aec9751..c3555945d 100644 --- a/evm/src/interfaces/IRateLimiter.sol +++ b/evm/src/interfaces/IRateLimiter.sol @@ -62,7 +62,8 @@ interface IRateLimiter { /// - txTimestamp: the timestamp of the transfer. /// - recipientChain: the chain of the recipient. /// - sender: the sender of the transfer. - /// - transceiverInstructions: additional instructions to be forwarded to the recipient chain. + /// - relayInstructions: additional instructions to be forwarded to the relayer. + /// - transceiverInstructions: instructions to be passed into the adapters. struct OutboundQueuedTransfer { bytes32 recipient; bytes32 refundAddress; @@ -70,6 +71,8 @@ interface IRateLimiter { uint64 txTimestamp; uint16 recipientChain; address sender; + bytes executorQuote; + bytes relayInstructions; bytes transceiverInstructions; } diff --git a/evm/src/interfaces/ITransceiver.sol b/evm/src/interfaces/ITransceiver.sol deleted file mode 100644 index 9e6ea79ec..000000000 --- a/evm/src/interfaces/ITransceiver.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity >=0.8.8 <0.9.0; - -import "../libraries/TransceiverStructs.sol"; - -interface ITransceiver { - /// @notice The caller is not the deployer. - /// @dev Selector: 0xc68a0e42. - /// @param deployer The address of the deployer. - /// @param caller The address of the caller. - error UnexpectedDeployer(address deployer, address caller); - - /// @notice The caller is not the NttManager. - /// @dev Selector: 0xc5aa6153. - /// @param caller The address of the caller. - error CallerNotNttManager(address caller); - - /// @notice Error when trying renounce transceiver ownership. - /// Ensures the owner of the transceiver is in sync with - /// the owner of the NttManager. - /// @dev Selector: 0x66791dd6. - /// @param currentOwner he current owner of the transceiver. - error CannotRenounceTransceiverOwnership(address currentOwner); - - /// @notice Error when trying to transfer transceiver ownership. - /// @dev Selector: 0x306239eb. - /// @param currentOwner The current owner of the transceiver. - /// @param newOwner The new owner of the transceiver. - error CannotTransferTransceiverOwnership(address currentOwner, address newOwner); - - /// @notice Error when the recipient NttManager address is not the - /// corresponding manager of the transceiver. - /// @dev Selector: 0x73bdd322. - /// @param recipientNttManagerAddress The address of the recipient NttManager. - /// @param expectedRecipientNttManagerAddress The expected address of the recipient NttManager. - error UnexpectedRecipientNttManagerAddress( - bytes32 recipientNttManagerAddress, bytes32 expectedRecipientNttManagerAddress - ); - - /// @notice Returns the owner address of the NTT Manager that this transceiver is related to. - function getNttManagerOwner() external view returns (address); - - /// @notice Returns the address of the token associated with this NTT deployment. - function getNttManagerToken() external view returns (address); - - /// @notice Returns the string type of the transceiver. E.g. "wormhole", "axelar", etc. - function getTransceiverType() external view returns (string memory); - - /// @notice Fetch the delivery price for a given recipient chain transfer. - /// @param recipientChain The Wormhole chain ID of the target chain. - /// @param instruction An additional Instruction provided by the Transceiver to be - /// executed on the recipient chain. - /// @return deliveryPrice The cost of delivering a message to the recipient chain, - /// in this chain's native token. - function quoteDeliveryPrice( - uint16 recipientChain, - TransceiverStructs.TransceiverInstruction memory instruction - ) external view returns (uint256); - - /// @dev Send a message to another chain. - /// @param recipientChain The Wormhole chain ID of the recipient. - /// @param instruction An additional Instruction provided by the Transceiver to be - /// executed on the recipient chain. - /// @param nttManagerMessage A message to be sent to the nttManager on the recipient chain. - /// @param recipientNttManagerAddress The Wormhole formatted address of the peer NTT Manager on the recipient chain. - /// @param refundAddress The Wormhole formatted address of the refund recipient - function sendMessage( - uint16 recipientChain, - TransceiverStructs.TransceiverInstruction memory instruction, - bytes memory nttManagerMessage, - bytes32 recipientNttManagerAddress, - bytes32 refundAddress - ) external payable; - - /// @notice Upgrades the transceiver to a new implementation. - /// @param newImplementation The address of the new implementation contract - function upgrade( - address newImplementation - ) external; - - /// @notice Transfers the ownership of the transceiver to a new address. - /// @param newOwner The address of the new owner - function transferTransceiverOwnership( - address newOwner - ) external; -} diff --git a/evm/src/interfaces/IWormholeTransceiver.sol b/evm/src/interfaces/IWormholeTransceiver.sol deleted file mode 100644 index 74142178f..000000000 --- a/evm/src/interfaces/IWormholeTransceiver.sol +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity >=0.8.8 <0.9.0; - -import "../libraries/TransceiverStructs.sol"; - -import "./IWormholeTransceiverState.sol"; - -interface IWormholeTransceiver is IWormholeTransceiverState { - /// @notice The instruction for the WormholeTransceiver contract - /// to skip delivery via the relayer. - struct WormholeTransceiverInstruction { - bool shouldSkipRelayerSend; - } - - /// @notice Emitted when a relayed message is received. - /// @dev Topic0 - /// 0xf557dbbb087662f52c815f6c7ee350628a37a51eae9608ff840d996b65f87475 - /// @param digest The digest of the message. - /// @param emitterChainId The chain ID of the emitter. - /// @param emitterAddress The address of the emitter. - event ReceivedRelayedMessage(bytes32 digest, uint16 emitterChainId, bytes32 emitterAddress); - - /// @notice Emitted when a message is received. - /// @dev Topic0 - /// 0xf6fc529540981400dc64edf649eb5e2e0eb5812a27f8c81bac2c1d317e71a5f0. - /// @param digest The digest of the message. - /// @param emitterChainId The chain ID of the emitter. - /// @param emitterAddress The address of the emitter. - /// @param sequence The sequence of the message. - event ReceivedMessage( - bytes32 digest, uint16 emitterChainId, bytes32 emitterAddress, uint64 sequence - ); - - /// @notice Emitted when a message is sent from the transceiver. - /// @dev Topic0 - /// 0x79376a0dc6cbfe6f6f8f89ad24c262a8c6233f8df181d3fe5abb2e2442e8c738. - /// @param recipientChain The chain ID of the recipient. - /// @param message The message. - event SendTransceiverMessage( - uint16 recipientChain, TransceiverStructs.TransceiverMessage message - ); - - /// @notice Error when the relaying configuration is invalid. (e.g. chainId is not registered) - /// @dev Selector: 0x9449a36c. - /// @param chainId The chain ID that is invalid. - error InvalidRelayingConfig(uint16 chainId); - - /// @notice Error when the peer transceiver is invalid. - /// @dev Selector: 0x79b1ce56. - /// @param chainId The chain ID of the peer. - /// @param peerAddress The address of the invalid peer. - error InvalidWormholePeer(uint16 chainId, bytes32 peerAddress); - - /// @notice Error when the VAA has already been consumed. - /// @dev Selector: 0x406e719e. - /// @param vaaHash The hash of the VAA. - error TransferAlreadyCompleted(bytes32 vaaHash); - - /// @notice Receive an attested message from the verification layer. - /// This function should verify the `encodedVm` and then deliver the attestation - /// to the transceiver NttManager contract. - /// @param encodedMessage The attested message. - function receiveMessage( - bytes memory encodedMessage - ) external; - - /// @notice Parses the encoded instruction and returns the instruction struct. - /// This instruction is specific to the WormholeTransceiver contract. - /// @param encoded The encoded instruction. - /// @return instruction The parsed `WormholeTransceiverInstruction`. - function parseWormholeTransceiverInstruction( - bytes memory encoded - ) external pure returns (WormholeTransceiverInstruction memory instruction); - - /// @notice Encodes the `WormholeTransceiverInstruction` into a byte array. - /// @param instruction The `WormholeTransceiverInstruction` to encode. - /// @return encoded The encoded instruction. - function encodeWormholeTransceiverInstruction( - WormholeTransceiverInstruction memory instruction - ) external pure returns (bytes memory); -} diff --git a/evm/src/interfaces/IWormholeTransceiverState.sol b/evm/src/interfaces/IWormholeTransceiverState.sol deleted file mode 100644 index 6b41c7077..000000000 --- a/evm/src/interfaces/IWormholeTransceiverState.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity >=0.8.8 <0.9.0; - -import "../libraries/TransceiverStructs.sol"; - -interface IWormholeTransceiverState { - /// @notice Emitted when a message is sent from the transceiver. - /// @dev Topic0 - /// 0xc3192e083c87c556db539f071d8a298869f487e951327b5616a6f85ae3da958e. - /// @param relayingType The type of relaying. - /// @param deliveryPayment The amount of ether sent along with the tx to cover the delivery fee. - event RelayingInfo(uint8 relayingType, bytes32 refundAddress, uint256 deliveryPayment); - - /// @notice Emitted when a peer transceiver is set. - /// @dev Topic0 - /// 0xa559263ee060c7a2560843b3a064ff0376c9753ae3e2449b595a3b615d326466. - /// @param chainId The chain ID of the peer. - /// @param peerContract The address of the peer contract. - event SetWormholePeer(uint16 chainId, bytes32 peerContract); - - /// @notice Emitted when relaying is enabled for the given chain. - /// @dev Topic0 - /// 0x528b18a533e892b5401d1fb63597275df9d2bb45b13e7695c3147cd07b9746c3. - /// @param chainId The chain ID to set. - /// @param isRelayingEnabled A boolean indicating whether relaying is enabled. - event SetIsWormholeRelayingEnabled(uint16 chainId, bool isRelayingEnabled); - - /// @notice Emitted when special relaying is enabled for the given chain. - /// @dev Topic0 - /// 0x0fe301480713b2c2072ee91b3bcfcbf2c0014f0447c89046f020f0f80727003c. - /// @param chainId The chain ID to set. - event SetIsSpecialRelayingEnabled(uint16 chainId, bool isRelayingEnabled); - - /// @notice Emitted when the chain is EVM compatible. - /// @dev Topic0 - /// 0x4add57d97a7bf5035340ea1212aeeb3d4d3887eb1faf3821a8224c3a6956a10c. - /// @param chainId The chain ID to set. - /// @param isEvm A boolean indicating whether relaying is enabled. - event SetIsWormholeEvmChain(uint16 chainId, bool isEvm); - - /// @notice Additonal messages are not allowed. - /// @dev Selector: 0xc504ea29. - error UnexpectedAdditionalMessages(); - - /// @notice Error if the VAA is invalid. - /// @dev Selector: 0x8ee2e336. - /// @param reason The reason the VAA is invalid. - error InvalidVaa(string reason); - - /// @notice Error if the peer has already been set. - /// @dev Selector: 0xb55eeae9. - /// @param chainId The chain ID of the peer. - /// @param peerAddress The address of the peer. - error PeerAlreadySet(uint16 chainId, bytes32 peerAddress); - - /// @notice Error the peer contract cannot be the zero address. - /// @dev Selector: 0x26e0c7de. - error InvalidWormholePeerZeroAddress(); - - /// @notice The chain ID cannot be zero. - /// @dev Selector: 0x3dd98b24. - error InvalidWormholeChainIdZero(); - - /// @notice The caller is not the relayer. - /// @dev Selector: 0x1c269589. - /// @param caller The caller. - error CallerNotRelayer(address caller); - - /// @notice Get the corresponding Transceiver contract on other chains that have been registered - /// via governance. This design should be extendable to other chains, so each Transceiver would - /// be potentially concerned with Transceivers on multiple other chains. - /// @dev that peers are registered under Wormhole chain ID values. - /// @param chainId The Wormhole chain ID of the peer to get. - /// @return peerContract The address of the peer contract on the given chain. - function getWormholePeer( - uint16 chainId - ) external view returns (bytes32); - - /// @notice Returns a boolean indicating whether the given VAA hash has been consumed. - /// @param hash The VAA hash to check. - function isVAAConsumed( - bytes32 hash - ) external view returns (bool); - - /// @notice Returns a boolean indicating whether Wormhole relaying is enabled for the given chain. - /// @param chainId The Wormhole chain ID to check. - function isWormholeRelayingEnabled( - uint16 chainId - ) external view returns (bool); - - /// @notice Returns a boolean indicating whether special relaying is enabled for the given chain. - /// @param chainId The Wormhole chain ID to check. - function isSpecialRelayingEnabled( - uint16 chainId - ) external view returns (bool); - - /// @notice Returns a boolean indicating whether the given chain is EVM compatible. - /// @param chainId The Wormhole chain ID to check. - function isWormholeEvmChain( - uint16 chainId - ) external view returns (bool); - - /// @notice Set the Wormhole peer contract for the given chain. - /// @dev This function is only callable by the `owner`. - /// @param chainId The Wormhole chain ID of the peer to set. - /// @param peerContract The address of the peer contract on the given chain. - function setWormholePeer(uint16 chainId, bytes32 peerContract) external payable; - - /// @notice Set whether the chain is EVM compatible. - /// @dev This function is only callable by the `owner`. - /// @param chainId The Wormhole chain ID to set. - /// @param isEvm A boolean indicating whether the chain is an EVM chain. - function setIsWormholeEvmChain(uint16 chainId, bool isEvm) external; - - /// @notice Set whether Wormhole relaying is enabled for the given chain. - /// @dev This function is only callable by the `owner`. - /// @param chainId The Wormhole chain ID to set. - /// @param isRelayingEnabled A boolean indicating whether relaying is enabled. - function setIsWormholeRelayingEnabled(uint16 chainId, bool isRelayingEnabled) external; - - /// @notice Set whether special relaying is enabled for the given chain. - /// @dev This function is only callable by the `owner`. - /// @param chainId The Wormhole chain ID to set. - /// @param isRelayingEnabled A boolean indicating whether special relaying is enabled. - function setIsSpecialRelayingEnabled(uint16 chainId, bool isRelayingEnabled) external; -} diff --git a/evm/src/libraries/RateLimiter.sol b/evm/src/libraries/RateLimiter.sol index 877c86ee1..8bee4396d 100644 --- a/evm/src/libraries/RateLimiter.sol +++ b/evm/src/libraries/RateLimiter.sol @@ -70,8 +70,8 @@ abstract contract RateLimiter is IRateLimiter, IRateLimiterEvents { constructor(uint64 _rateLimitDuration, bool _skipRateLimiting) { if ( - _rateLimitDuration == 0 && !_skipRateLimiting - || _rateLimitDuration != 0 && _skipRateLimiting + (_rateLimitDuration == 0 && !_skipRateLimiting) + || (_rateLimitDuration != 0 && _skipRateLimiting) ) { revert UndefinedRateLimiting(); } @@ -302,6 +302,8 @@ abstract contract RateLimiter is IRateLimiter, IRateLimiterEvents { bytes32 recipient, bytes32 refundAddress, address senderAddress, + bytes memory executorQuote, + bytes memory relayInstructions, bytes memory transceiverInstructions ) internal { _getOutboundQueueStorage()[sequence] = OutboundQueuedTransfer({ @@ -311,6 +313,8 @@ abstract contract RateLimiter is IRateLimiter, IRateLimiterEvents { refundAddress: refundAddress, txTimestamp: uint64(block.timestamp), sender: senderAddress, + executorQuote: executorQuote, + relayInstructions: relayInstructions, transceiverInstructions: transceiverInstructions }); diff --git a/evm/src/libraries/TransceiverHelpers.sol b/evm/src/libraries/TransceiverHelpers.sol index 44906ae4d..038b8ed92 100644 --- a/evm/src/libraries/TransceiverHelpers.sol +++ b/evm/src/libraries/TransceiverHelpers.sol @@ -32,3 +32,15 @@ function countSetBits( return count; } + +// @dev Count the number of set bits in a uint128 +function countSetBits128( + uint128 x +) pure returns (uint8 count) { + while (x != 0) { + x &= x - 1; + count++; + } + + return count; +} diff --git a/evm/test/IntegrationAdditionalTransfer.t.sol b/evm/test/IntegrationAdditionalTransfer.t.sol index b281c4abb..965bed964 100755 --- a/evm/test/IntegrationAdditionalTransfer.t.sol +++ b/evm/test/IntegrationAdditionalTransfer.t.sol @@ -5,31 +5,40 @@ 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 "./libraries/TransceiverHelpers.sol"; import "./mocks/MockNttManagerAdditionalPayload.sol"; -import "./mocks/MockTransceivers.sol"; +import "./mocks/MockEndpoint.sol"; +import "./mocks/MockExecutor.sol"; +import "./mocks/DummyTransceiver.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 "example-messaging-endpoint/evm/src/Endpoint.sol"; //import "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; contract TestAdditionalPayload is Test { + MockEndpoint endpointChain1; + MockEndpoint endpointChain2; + + MockExecutor executorChain1; + MockExecutor executorChain2; + NttManagerNoRateLimiting nttManagerChain1; NttManagerNoRateLimiting nttManagerChain2; + DummyTransceiver transceiverChain1; + DummyTransceiver transceiverChain2; + using TrimmedAmountLib for uint256; using TrimmedAmountLib for TrimmedAmount; @@ -44,8 +53,6 @@ contract TestAdditionalPayload is Test { WormholeSimulator guardian; uint256 initialBlockTimestamp; - WormholeTransceiver wormholeTransceiverChain1; - WormholeTransceiver wormholeTransceiverChain2; address userA = address(0x123); address userB = address(0x456); address userC = address(0x789); @@ -61,10 +68,20 @@ contract TestAdditionalPayload is Test { guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK); + endpointChain1 = new MockEndpoint(chainId1); + endpointChain2 = new MockEndpoint(chainId2); + + executorChain1 = new MockExecutor(chainId1); + executorChain2 = new MockExecutor(chainId2); + vm.chainId(chainId1); DummyToken t1 = new DummyToken(); NttManagerNoRateLimiting implementation = new MockNttManagerAdditionalPayloadContract( - address(t1), IManagerBase.Mode.LOCKING, chainId1 + address(endpointChain1), + address(executorChain1), + address(t1), + IManagerBase.Mode.LOCKING, + chainId1 ); nttManagerChain1 = MockNttManagerAdditionalPayloadContract( @@ -72,37 +89,20 @@ contract TestAdditionalPayload is Test { ); nttManagerChain1.initialize(); - 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(); - - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); - // nttManagerChain1.setOutboundLimit(type(uint64).max); - // nttManagerChain1.setInboundLimit(type(uint64).max, chainId2); + transceiverChain1 = new DummyTransceiver(chainId1, address(endpointChain1)); + nttManagerChain1.setTransceiver(address(transceiverChain1)); + nttManagerChain1.enableSendTransceiver(chainId2, address(transceiverChain1)); + nttManagerChain1.enableRecvTransceiver(chainId2, address(transceiverChain1)); // Chain 2 setup vm.chainId(chainId2); DummyToken t2 = new DummyTokenMintAndBurn(); NttManagerNoRateLimiting implementationChain2 = new MockNttManagerAdditionalPayloadContract( - address(t2), IManagerBase.Mode.BURNING, chainId2 + address(endpointChain2), + address(executorChain2), + address(t2), + IManagerBase.Mode.BURNING, + chainId2 ); nttManagerChain2 = MockNttManagerAdditionalPayloadContract( @@ -110,49 +110,42 @@ contract TestAdditionalPayload is Test { ); 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(); - - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2)); - // nttManagerChain2.setOutboundLimit(type(uint64).max); - // nttManagerChain2.setInboundLimit(type(uint64).max, chainId1); + transceiverChain2 = new DummyTransceiver(chainId2, address(endpointChain2)); + nttManagerChain2.setTransceiver(address(transceiverChain2)); + nttManagerChain2.enableSendTransceiver(chainId1, address(transceiverChain2)); + nttManagerChain2.enableRecvTransceiver(chainId1, address(transceiverChain2)); // 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 + chainId2, + bytes32(uint256(uint160(address(nttManagerChain2)))), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); nttManagerChain2.setPeer( - chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 7, type(uint64).max + chainId1, + bytes32(uint256(uint160(address(nttManagerChain1)))), + 7, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); - // Set peers for the transceivers - wormholeTransceiverChain1.setWormholePeer( - chainId2, bytes32(uint256(uint160(address(wormholeTransceiverChain2)))) - ); - wormholeTransceiverChain2.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) + require( + nttManagerChain1.getThreshold(chainId2) != 0, + "Threshold is zero with active transceivers" ); - require(nttManagerChain1.getThreshold() != 0, "Threshold is zero with active transceivers"); - // Actually set it - nttManagerChain1.setThreshold(1); - nttManagerChain2.setThreshold(1); + nttManagerChain1.setThreshold(chainId2, 1); + nttManagerChain2.setThreshold(chainId1, 1); INttManager.NttManagerPeer memory peer = nttManagerChain1.getPeer(chainId2); require(9 == peer.tokenDecimals, "Peer has the wrong number of token decimals"); } + function test_setUp() public {} + function test_transferWithAdditionalPayload() public { vm.chainId(chainId1); @@ -169,10 +162,18 @@ contract TestAdditionalPayload is Test { vm.recordLogs(); // Send token through standard means (not relayer) + uint64 seqNo; { uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); uint256 userBalanceBefore = token1.balanceOf(address(userA)); - nttManagerChain1.transfer(sendingAmount, chainId2, bytes32(uint256(uint160(userB)))); + seqNo = nttManagerChain1.transfer( + sendingAmount, + chainId2, + bytes32(uint256(uint160(userB))), + executorChain1.createSignedQuote(executorChain2.chainId()), + executorChain1.createRelayInstructions(), + endpointChain1.createAdapterInstructions() + ); // Balance check on funds going in and out working as expected uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1)); @@ -187,10 +188,18 @@ contract TestAdditionalPayload is Test { ); } + assertEq(seqNo, 0); + DummyTransceiver.Message[] memory rmsgs = transceiverChain1.getMessages(); + assertEq(1, rmsgs.length); + + // Get the execution events from the logs. + Vm.Log[] memory recordedLogs = vm.getRecordedLogs(); + bytes memory encoded = + TransceiverHelpersLib.getExecutionSent(recordedLogs, address(nttManagerChain1), seqNo); + vm.stopPrank(); // Get the AdditionalPayloadSent(bytes) event to ensure it matches up with the AdditionalPayloadReceived(bytes) event later - Vm.Log[] memory recordedLogs = vm.getRecordedLogs(); string memory expectedAP = "banana"; string memory sentAP; for (uint256 i = 0; i < recordedLogs.length; i++) { @@ -201,22 +210,21 @@ contract TestAdditionalPayload is Test { } assertEq(sentAP, expectedAP); - // Get and sign the log to go down the other pipe. Thank you to whoever wrote this code in the past! - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(recordedLogs); - 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); // Wrong chain receiving the signed VAA - vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, chainId1, chainId2)); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + vm.expectRevert(Endpoint.InvalidDestinationChain.selector); + transceiverChain1.receiveMessage(rmsgs[0]); + + // Attest on the correct chain. + transceiverChain2.receiveMessage(rmsgs[0]); + { uint256 supplyBefore = token2.totalSupply(); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + nttManagerChain2.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); uint256 supplyAfter = token2.totalSupply(); require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); @@ -238,15 +246,6 @@ contract TestAdditionalPayload is Test { } assertEq(receivedAP, expectedAP); - // Can't resubmit the same message twice - (IWormhole.VM memory wormholeVM,,) = wormhole.parseAndVerifyVM(encodedVMs[0]); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash - ) - ); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); - // Go back the other way from a THIRD user vm.prank(userB); token2.transfer(userC, sendingAmount); @@ -258,13 +257,15 @@ contract TestAdditionalPayload is Test { // Supply checks on the transfer { uint256 supplyBefore = token2.totalSupply(); - nttManagerChain2.transfer( + seqNo = nttManagerChain2.transfer( sendingAmount, chainId1, toWormholeFormat(userD), toWormholeFormat(userC), false, - encodeTransceiverInstruction(true) + executorChain2.createSignedQuote(executorChain1.chainId()), + executorChain2.createRelayInstructions(), + endpointChain2.createAdapterInstructions() ); uint256 supplyAfter = token2.totalSupply(); @@ -278,19 +279,24 @@ contract TestAdditionalPayload is Test { ); } - // 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()); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); - } + recordedLogs = vm.getRecordedLogs(); + encoded = + TransceiverHelpersLib.getExecutionSent(recordedLogs, address(nttManagerChain2), seqNo); // Chain1 verification and checks with the receiving of the message vm.chainId(chainId1); + // Attest the message. + rmsgs = transceiverChain2.getMessages(); + assertEq(1, rmsgs.length); + transceiverChain1.receiveMessage(rmsgs[0]); + { uint256 supplyBefore = token1.totalSupply(); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + + nttManagerChain1.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); uint256 supplyAfter = token1.totalSupply(); @@ -300,19 +306,4 @@ contract TestAdditionalPayload is Test { 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); - } } diff --git a/evm/test/IntegrationManual.t.sol b/evm/test/IntegrationManual.t.sol deleted file mode 100644 index 56d762996..000000000 --- a/evm/test/IntegrationManual.t.sol +++ /dev/null @@ -1,285 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity 0.8.19; - -import "forge-std/Test.sol"; -import "forge-std/console.sol"; -import {WormholeRelayerBasicTest} from "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; -import "./libraries/IntegrationHelpers.sol"; -import "wormhole-solidity-sdk/testing/helpers/WormholeSimulator.sol"; -import "../src/NttManager/NttManager.sol"; -import "./mocks/MockNttManager.sol"; -import "./mocks/MockTransceivers.sol"; - -import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - -contract TestRelayerEndToEndManual is IntegrationHelpers, IRateLimiterEvents { - NttManager nttManagerChain1; - NttManager nttManagerChain2; - - using TrimmedAmountLib for uint256; - using TrimmedAmountLib for TrimmedAmount; - - uint16 constant chainId1 = 4; - uint16 constant chainId2 = 5; - uint8 constant FAST_CONSISTENCY_LEVEL = 200; - uint256 constant GAS_LIMIT = 500000; - - uint256 constant DEVNET_GUARDIAN_PK = - 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; - WormholeSimulator guardian; - uint256 initialBlockTimestamp; - - address userA = address(0x123); - address userB = address(0x456); - address userC = address(0x789); - address userD = address(0xABC); - - address relayer = address(0x80aC94316391752A193C1c47E27D382b507c93F3); - IWormhole wormhole = IWormhole(0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D); - - function setUp() public { - string memory url = "https://bsc-testnet-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(); - - wormholeTransceiverChain1 = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain1 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain1), "")) - ); - wormholeTransceiverChain1.initialize(); - - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); - nttManagerChain1.setOutboundLimit(type(uint64).max); - nttManagerChain1.setInboundLimit(type(uint64).max, chainId2); - - // 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(); - wormholeTransceiverChain2 = new MockWormholeTransceiverContract( - address(nttManagerChain2), - address(wormhole), - address(relayer), // TODO - add support for this later - address(0x0), // TODO - add support for this later - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain2 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain2), "")) - ); - 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( - chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 9, type(uint64).max - ); - nttManagerChain2.setPeer( - chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 7, type(uint64).max - ); - } - - function test_relayerTransceiverAuth() public { - // Set up sensible WH transceiver peers - _setTransceiverPeers( - [wormholeTransceiverChain1, wormholeTransceiverChain2], - [wormholeTransceiverChain2, wormholeTransceiverChain1], - [chainId2, chainId1] - ); - - vm.recordLogs(); - vm.chainId(chainId1); - - // Setting up the transfer - DummyToken token1 = DummyToken(nttManagerChain1.token()); - - uint8 decimals = token1.decimals(); - uint256 sendingAmount = 5 * 10 ** decimals; - _prepareTransfer(token1, userA, address(nttManagerChain1), sendingAmount); - - vm.deal(userA, 1 ether); - WormholeTransceiver[] memory transceiver = new WormholeTransceiver[](1); - transceiver[0] = wormholeTransceiverChain1; - - // Send token through the relayer - transferToken(userB, userA, nttManagerChain1, sendingAmount, chainId2, transceiver, false); - - // Get the messages from the logs for the sender - vm.chainId(chainId2); - - bytes[] memory encodedVMs = _getWormholeMessage(guardian, vm.getRecordedLogs(), chainId1); - - IWormhole.VM memory vaa = wormhole.parseVM(encodedVMs[0]); - - vm.stopPrank(); - vm.chainId(chainId2); - - // Set bad manager peer (0x1) - nttManagerChain2.setPeer(chainId1, toWormholeFormat(address(0x1)), 9, type(uint64).max); - - vm.startPrank(relayer); - - bytes[] memory a; - vm.expectRevert( - abi.encodeWithSelector( - INttManager.InvalidPeer.selector, chainId1, address(nttManagerChain1) - ) - ); - _receiveWormholeMessage( - vaa, wormholeTransceiverChain1, wormholeTransceiverChain2, vaa.emitterChainId, a - ); - vm.stopPrank(); - - _setManagerPeer(nttManagerChain2, nttManagerChain1, chainId1, 9, type(uint64).max); - - // Wrong caller - aka not relayer contract - vm.prank(userD); - vm.expectRevert( - abi.encodeWithSelector(IWormholeTransceiverState.CallerNotRelayer.selector, userD) - ); - _receiveWormholeMessage( - vaa, wormholeTransceiverChain1, wormholeTransceiverChain2, vaa.emitterChainId, a - ); - - vm.startPrank(relayer); - - // Bad chain ID for a given transceiver - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.InvalidWormholePeer.selector, - 0xFF, - address(wormholeTransceiverChain1) - ) - ); - wormholeTransceiverChain2.receiveWormholeMessages( - vaa.payload, - a, - bytes32(uint256(uint160(address(wormholeTransceiverChain1)))), - 0xFF, - vaa.hash - ); - - /* - This information is assumed to be trusted since ONLY the relayer on a given chain can call it. - However, it's still good to test various things. - - This attempt should actually work this time. - */ - wormholeTransceiverChain2.receiveWormholeMessages( - vaa.payload, // Verified - a, // Should be zero - bytes32(uint256(uint160(address(wormholeTransceiverChain1)))), // Must be a wormhole peers - vaa.emitterChainId, // ChainID from the call - vaa.hash // Hash of the VAA being used - ); - - // Should from sending a *duplicate* message - vm.expectRevert( - abi.encodeWithSelector(IWormholeTransceiver.TransferAlreadyCompleted.selector, vaa.hash) - ); - wormholeTransceiverChain2.receiveWormholeMessages( - vaa.payload, - a, // Should be zero - bytes32(uint256(uint160(address(wormholeTransceiverChain1)))), // Must be a wormhole peers - vaa.emitterChainId, // ChainID from the call - vaa.hash // Hash of the VAA being used - ); - } - - function test_relayerWithInvalidWHTransceiver() public { - // Set up dodgy wormhole transceiver peers - wormholeTransceiverChain2.setWormholePeer(chainId1, bytes32(uint256(uint160(address(0x1))))); - wormholeTransceiverChain1.setWormholePeer( - chainId2, bytes32(uint256(uint160(address(wormholeTransceiverChain2)))) - ); - - vm.recordLogs(); - vm.chainId(chainId1); - - // Setting up the transfer - DummyToken token1 = DummyToken(nttManagerChain1.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); - - // Send token through the relayer - { - vm.deal(userA, 1 ether); - nttManagerChain1.transfer{ - value: wormholeTransceiverChain1.quoteDeliveryPrice( - chainId2, buildTransceiverInstruction(false) - ) - }( - sendingAmount, - chainId2, - bytes32(uint256(uint160(userB))), - bytes32(uint256(uint160(userA))), - false, - encodeTransceiverInstruction(false) - ); - } - - // Get the messages from the logs for the sender - vm.chainId(chainId2); - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } - - IWormhole.VM memory vaa = wormhole.parseVM(encodedVMs[0]); - - vm.stopPrank(); - vm.chainId(chainId2); - - // Caller is not proper who to receive messages from - bytes[] memory a; - vm.startPrank(relayer); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.InvalidWormholePeer.selector, - chainId1, - address(wormholeTransceiverChain1) - ) - ); - wormholeTransceiverChain2.receiveWormholeMessages( - vaa.payload, - a, - bytes32(uint256(uint160(address(wormholeTransceiverChain1)))), - vaa.emitterChainId, - vaa.hash - ); - vm.stopPrank(); - } -} diff --git a/evm/test/IntegrationRelayer.t.sol b/evm/test/IntegrationRelayer.t.sol deleted file mode 100755 index 081e4f4ba..000000000 --- a/evm/test/IntegrationRelayer.t.sol +++ /dev/null @@ -1,579 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity 0.8.19; - -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/IManagerBase.sol"; -import "../src/interfaces/IRateLimiterEvents.sol"; -import "../src/interfaces/IWormholeTransceiver.sol"; -import "../src/interfaces/IWormholeTransceiverState.sol"; -import {Utils} from "./libraries/Utils.sol"; -import {DummyToken, DummyTokenMintAndBurn} from "../src/mocks/DummyToken.sol"; -import {WormholeTransceiver} from "../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; -import "../src/libraries/TransceiverStructs.sol"; -import "./libraries/TransceiverHelpers.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 {WormholeRelayerBasicTest} from "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; -import "./libraries/IntegrationHelpers.sol"; - -contract TestEndToEndRelayer is IntegrationHelpers, IRateLimiterEvents, WormholeRelayerBasicTest { - NttManager nttManagerChain1; - NttManager nttManagerChain2; - - using TrimmedAmountLib for uint256; - using TrimmedAmountLib for TrimmedAmount; - - uint16 constant chainId1 = 4; - uint16 constant chainId2 = 6; - uint8 constant FAST_CONSISTENCY_LEVEL = 200; - uint256 constant GAS_LIMIT = 500000; - - WormholeSimulator guardian; - uint256 initialBlockTimestamp; - - address userA = address(0x123); - address userB = address(0x456); - address userC = address(0x789); - address userD = address(0xABC); - - constructor() { - setTestnetForkChains(chainId1, chainId2); - } - - // https://github.com/wormhole-foundation/hello-wormhole/blob/main/test/HelloWormhole.t.sol#L14C1-L20C6 - // Setup the starting point of the network - function setUpSource() public override { - vm.deal(userA, 1 ether); - 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(); - - wormholeTransceiverChain1 = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(chainInfosTestnet[chainId1].wormhole), - address(relayerSource), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - wormholeTransceiverChain1 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain1), "")) - ); - wormholeTransceiverChain1.initialize(); - wormholeTransceiverChain1Other = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(chainInfosTestnet[chainId1].wormhole), - address(relayerSource), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - wormholeTransceiverChain1Other = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain1Other), "")) - ); - wormholeTransceiverChain1Other.initialize(); - - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1Other)); - nttManagerChain1.setOutboundLimit(type(uint64).max); - nttManagerChain1.setInboundLimit(type(uint64).max, chainId2); - nttManagerChain1.setThreshold(1); - } - - // Setup the chain to relay to of the network - function setUpTarget() public override { - vm.deal(userC, 1 ether); - - // Chain 2 setup - 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(); - wormholeTransceiverChain2 = new MockWormholeTransceiverContract( - address(nttManagerChain2), - address(chainInfosTestnet[chainId2].wormhole), - address(relayerTarget), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - wormholeTransceiverChain2 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain2), "")) - ); - wormholeTransceiverChain2.initialize(); - - wormholeTransceiverChain2Other = new MockWormholeTransceiverContract( - address(nttManagerChain2), - address(chainInfosTestnet[chainId2].wormhole), - address(relayerTarget), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - wormholeTransceiverChain2Other = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain2Other), "")) - ); - wormholeTransceiverChain2Other.initialize(); - - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2)); - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2Other)); - nttManagerChain2.setOutboundLimit(type(uint64).max); - nttManagerChain2.setInboundLimit(type(uint64).max, chainId1); - - nttManagerChain2.setThreshold(1); - } - - function test_chainToChainReverts() public { - // record all of the logs for all of the occuring events - vm.recordLogs(); - - // Setup the information for interacting with the chains - vm.selectFork(targetFork); - wormholeTransceiverChain2.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) - ); - nttManagerChain2.setPeer( - chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 9, type(uint64).max - ); - 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); - DummyToken token1 = DummyToken(nttManagerChain1.token()); - wormholeTransceiverChain1.setWormholePeer( - chainId2, bytes32(uint256(uint160((address(wormholeTransceiverChain2))))) - ); - nttManagerChain1.setPeer( - chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 7, type(uint64).max - ); - - // Enable general relaying on the chain to transfer for the funds. - wormholeTransceiverChain1.setIsWormholeRelayingEnabled(chainId2, true); - wormholeTransceiverChain1.setIsWormholeEvmChain(chainId2, true); - - // Setting up the transfer - uint8 decimals = token1.decimals(); - uint256 sendingAmount = 5 * 10 ** decimals; - token1.mintDummy(address(userA), sendingAmount); - vm.startPrank(userA); - token1.approve(address(nttManagerChain1), sendingAmount); - - // Send token through standard means (not relayer) - { - uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceBefore = token1.balanceOf(address(userA)); - - uint256 priceQuote1 = wormholeTransceiverChain1.quoteDeliveryPrice( - chainId2, buildTransceiverInstruction(false) - ); - bytes memory instructions = encodeTransceiverInstruction(false); - - // set invalid config - vm.stopPrank(); - wormholeTransceiverChain1.setIsWormholeEvmChain(chainId2, false); - - // config not set correctly - vm.startPrank(userA); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.InvalidRelayingConfig.selector, chainId2 - ) - ); - nttManagerChain1.transfer{value: priceQuote1}( - sendingAmount, - chainId2, - bytes32(uint256(uint160(userB))), - bytes32(uint256(uint160(userB))), - false, - instructions - ); - - // set valid config - vm.stopPrank(); - wormholeTransceiverChain1.setIsWormholeEvmChain(chainId2, true); - vm.startPrank(userA); - - // revert if transfer amount has dust - uint256 amount = sendingAmount - 1; - TrimmedAmount trimmedAmount = amount.trim(decimals, 7); - uint256 newAmount = trimmedAmount.untrim(decimals); - vm.expectRevert( - abi.encodeWithSelector( - INttManager.TransferAmountHasDust.selector, amount, amount - newAmount - ) - ); - nttManagerChain1.transfer{value: priceQuote1}( - sendingAmount - 1, - chainId2, - bytes32(uint256(uint160(userB))), - bytes32(uint256(uint160(userB))), - false, - instructions - ); - - // Zero funds error - vm.expectRevert(abi.encodeWithSelector(INttManager.ZeroAmount.selector)); - nttManagerChain1.transfer{value: priceQuote1}( - 0, - chainId2, - bytes32(uint256(uint160(userB))), - bytes32(uint256(uint160(userB))), - false, - instructions - ); - - // Not enough in gas costs from the 'quote'. - vm.expectRevert( - abi.encodeWithSelector( - IManagerBase.DeliveryPaymentTooLow.selector, priceQuote1, priceQuote1 - 1 - ) - ); - nttManagerChain1.transfer{value: priceQuote1 - 1}( - sendingAmount, - chainId2, - bytes32(uint256(uint160(userB))), - bytes32(uint256(uint160(userB))), - false, - instructions - ); - - // Do the payment with slightly more gas than needed. This should result in a *payback* of 1 wei. - 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)); - require( - nttManagerBalanceBefore + sendingAmount == nttManagerBalanceAfter, - "Should be locking the tokens" - ); - require( - userBalanceBefore - sendingAmount == userBalanceAfter, - "User should have sent tokens" - ); - } - - vm.stopPrank(); - - vm.selectFork(targetFork); // Move to the target chain briefly to get the total supply - uint256 supplyBefore = token2.totalSupply(); - - // Deliver the TX via the relayer mechanism. That's pretty fly! - vm.selectFork(sourceFork); // Move to back to the source chain for things to be processed - - // Turn on the log recording because we want the test framework to pick up the events. - // TODO - can't do easy testing on this. - // Foundry *eats* the logs. So, once this fails, they're gone forever. Need to set up signer then, prank then make the call to relay manually. - performDelivery(); - - vm.selectFork(targetFork); // Move to back to the target chain to look at how things were processed - - 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"); - } - - function test_chainToChainBase() public { - // record all of the logs for all of the occuring events - vm.recordLogs(); - - // Setup the information for interacting with the chains - vm.selectFork(targetFork); - wormholeTransceiverChain2.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) - ); - nttManagerChain2.setPeer( - chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 9, type(uint64).max - ); - 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.setPeer( - chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 7, type(uint64).max - ); - wormholeTransceiverChain1.setWormholePeer( - chainId2, bytes32(uint256(uint160((address(wormholeTransceiverChain2))))) - ); - DummyToken token1 = DummyToken(nttManagerChain1.token()); - - // Enable general relaying on the chain to transfer for the funds. - wormholeTransceiverChain1.setIsWormholeRelayingEnabled(chainId2, true); - wormholeTransceiverChain1.setIsWormholeEvmChain(chainId2, true); - - // Setting up the transfer - uint8 decimals = token1.decimals(); - uint256 sendingAmount = 5 * 10 ** decimals; - token1.mintDummy(address(userA), 5 * 10 ** decimals); - vm.startPrank(userA); - token1.approve(address(nttManagerChain1), sendingAmount); - - // Send token through standard means (not relayer) - { - uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceBefore = token1.balanceOf(address(userA)); - - nttManagerChain1.transfer{ - value: wormholeTransceiverChain1.quoteDeliveryPrice( - chainId2, buildTransceiverInstruction(false) - ) - }( - sendingAmount, - chainId2, - bytes32(uint256(uint160(userB))), - // refund the amount back to the user that sent the transfer - bytes32(uint256(uint160(userA))), - false, - encodeTransceiverInstruction(false) - ); - - // 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(); - - vm.selectFork(targetFork); // Move to the target chain briefly to get the total supply - uint256 supplyBefore = token2.totalSupply(); - - // Deliver the TX via the relayer mechanism. That's pretty fly! - vm.selectFork(sourceFork); // Move to back to the source chain for things to be processed - // Turn on the log recording because we want the test framework to pick up the events. - performDelivery(); - - vm.selectFork(targetFork); // Move to back to the target chain to look at how things were processed - - uint256 supplyAfter = token2.totalSupply(); - - require(sendingAmount + supplyBefore == supplyAfter, "Supplies not changed - minting"); - 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); - - { - supplyBefore = token2.totalSupply(); - nttManagerChain2.transfer{ - value: wormholeTransceiverChain2.quoteDeliveryPrice( - chainId1, buildTransceiverInstruction(false) - ) - }( - sendingAmount, - chainId1, - bytes32(uint256(uint160(userD))), - bytes32(uint256(uint160(userC))), - false, - encodeTransceiverInstruction(false) - ); - - supplyAfter = token2.totalSupply(); - - require( - sendingAmount - supplyBefore == supplyAfter, - "Supplies don't match - tokens not burned" - ); - 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" - ); - } - - // Receive the transfer - vm.selectFork(sourceFork); // Move to the source chain briefly to get the total supply - supplyBefore = token1.totalSupply(); - - vm.selectFork(targetFork); // Move to the target chain for log processing - - // Deliver the TX via the relayer mechanism. That's pretty fly! - performDelivery(); - - vm.selectFork(sourceFork); // Move back to the source chain to check out the balances - - require(supplyBefore - sendingAmount == supplyAfter, "Supplies weren't burned as expected"); - require(token1.balanceOf(userA) == 0, "UserA received funds on the transfer back"); - require(token1.balanceOf(userB) == 0, "UserB received funds on the transfer back"); - require(token1.balanceOf(userC) == 0, "UserC received funds on the transfer back"); - require(token1.balanceOf(userD) == sendingAmount, "User didn't receive tokens going back"); - require( - token1.balanceOf(address(nttManagerChain1)) == 0, - "NttManager has unintended funds going back" - ); - } - - function deliverViaRelayer() public { - vm.selectFork(sourceFork); - performDelivery(); - } - - /// @dev Checks that a refund is issued to an address - /// specified by the client on a transfer via the - /// standard relaying path. - function test_getRefundsAfterStandardRelay() public { - // record all of the logs for all of the occuring events - vm.recordLogs(); - - // Setup the information for interacting with the chains - vm.selectFork(targetFork); - - // set the manager and transceiver peers - _setTransceiverPeers( - [wormholeTransceiverChain2, wormholeTransceiverChain2Other], - [wormholeTransceiverChain1, wormholeTransceiverChain1Other], - [chainId1, chainId1] - ); - nttManagerChain2.setPeer( - chainId1, toWormholeFormat(address(nttManagerChain1)), 9, type(uint64).max - ); - - // setup token - DummyToken token2 = DummyTokenMintAndBurn(nttManagerChain2.token()); - - // enable standard relaying - _enableSR([wormholeTransceiverChain2, wormholeTransceiverChain2Other], chainId1); - - // set the manager and transceiver peers - vm.selectFork(sourceFork); - _setTransceiverPeers( - [wormholeTransceiverChain1, wormholeTransceiverChain1Other], - [wormholeTransceiverChain2, wormholeTransceiverChain2Other], - [chainId2, chainId2] - ); - nttManagerChain1.setPeer( - chainId2, toWormholeFormat(address(nttManagerChain2)), 7, type(uint64).max - ); - - DummyToken token1 = DummyToken(nttManagerChain1.token()); - uint8 decimals = token1.decimals(); - - // enable standard relaying - _enableSR([wormholeTransceiverChain1, wormholeTransceiverChain1Other], chainId2); - - uint256 sendingAmount = 5 * 10 ** token1.decimals(); - _prepareTransfer(token1, userA, address(nttManagerChain1), sendingAmount); - - uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceBefore = token1.balanceOf(address(userA)); - - // send token through standard relayer - WormholeTransceiver[] memory transceivers = new WormholeTransceiver[](2); - transceivers[0] = wormholeTransceiverChain1; - transceivers[1] = wormholeTransceiverChain1Other; - - // create fresh address so ether balance before transfer is 0. - address refundAddress = address(0x478); - transferToken( - userB, refundAddress, nttManagerChain1, sendingAmount, chainId2, transceivers, false - ); - // Balance check on funds going in and out working as expected - uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceAfter = token1.balanceOf(address(userB)); - - assertEq( - nttManagerBalanceBefore + sendingAmount, - nttManagerBalanceAfter, - "Should be locking the tokens" - ); - assertEq( - userBalanceBefore - sendingAmount, userBalanceAfter, "User should have sent tokens" - ); - vm.stopPrank(); - uint256 supplyBefore = getTotalSupply(targetFork, token2); - - // Deliver the TX via the relayer mechanism. - deliverViaRelayer(); - - // sanity checks - vm.selectFork(targetFork); - uint256 supplyAfter = token2.totalSupply(); - assertEq(sendingAmount + supplyBefore, supplyAfter, "Supplies not changed - minting"); - assertEq(token2.balanceOf(userB), sendingAmount, "User didn't receive tokens"); - assertEq(token2.balanceOf(address(nttManagerChain2)), 0, "NttManager has unintended funds"); - - // push variables onto the stack again to avoid stack too deep error - uint256 sendingAmt = sendingAmount; - uint8 decs = decimals; - DummyToken tokenPush = token1; - bytes32 hash = _computeManagerMessageDigest( - userA, userB, sendingAmt.trim(decs, 7), address(tokenPush), chainId1, chainId2 - ); - - // number of attestations on the message should be equal to the number of transceivers - assertEq(nttManagerChain2.messageAttestations(hash), 2); - - // check that the message has been executed at this point - // replay protecion in `executeMsg` should emit the `MessageAlreadyExecuted` event. - assertTrue(nttManagerChain2.isMessageExecuted(hash)); - - // ether balance of refund address should be > 0, given that - // the threshold < # of enabled transceivers - assertGt(refundAddress.balance, 0); - } - - function copyBytes( - bytes memory _bytes - ) private pure returns (bytes memory) { - bytes memory copy = new bytes(_bytes.length); - uint256 max = _bytes.length + 31; - for (uint256 i = 32; i <= max; i += 32) { - assembly { - mstore(add(copy, i), mload(add(_bytes, i))) - } - } - return copy; - } -} diff --git a/evm/test/IntegrationStandalone.t.sol b/evm/test/IntegrationStandalone.t.sol index ecaa2b310..e9d329018 100755 --- a/evm/test/IntegrationStandalone.t.sol +++ b/evm/test/IntegrationStandalone.t.sol @@ -5,31 +5,39 @@ 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 "./libraries/TransceiverHelpers.sol"; +import "./mocks/DummyTransceiver.sol"; import "./mocks/MockNttManager.sol"; -import "./mocks/MockTransceivers.sol"; +import "./mocks/MockEndpoint.sol"; +import "./mocks/MockExecutor.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"; +import "example-messaging-endpoint/evm/src/Endpoint.sol"; contract TestEndToEndBase is Test, IRateLimiterEvents { NttManager nttManagerChain1; NttManager nttManagerChain2; + MockEndpoint endpointChain1; + MockEndpoint endpointChain2; + + MockExecutor executorChain1; + MockExecutor executorChain2; + + DummyTransceiver transceiverChain1; + DummyTransceiver transceiverChain2; + using TrimmedAmountLib for uint256; using TrimmedAmountLib for TrimmedAmount; @@ -44,8 +52,6 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { WormholeSimulator guardian; uint256 initialBlockTimestamp; - WormholeTransceiver wormholeTransceiverChain1; - WormholeTransceiver wormholeTransceiverChain2; address userA = address(0x123); address userB = address(0x456); address userC = address(0x789); @@ -61,39 +67,33 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK); + endpointChain1 = new MockEndpoint(chainId1); + endpointChain2 = new MockEndpoint(chainId2); + + executorChain1 = new MockExecutor(chainId1); + executorChain2 = new MockExecutor(chainId2); + vm.chainId(chainId1); DummyToken t1 = new DummyToken(); NttManager implementation = new MockNttManagerContract( - address(t1), IManagerBase.Mode.LOCKING, chainId1, 1 days, false + address(endpointChain1), + address(executorChain1), + address(t1), + IManagerBase.Mode.LOCKING, + chainId1, + 1 days, + false ); nttManagerChain1 = MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); nttManagerChain1.initialize(); - 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(); + transceiverChain1 = new DummyTransceiver(chainId1, address(endpointChain1)); + nttManagerChain1.setTransceiver(address(transceiverChain1)); + nttManagerChain1.enableSendTransceiver(chainId2, address(transceiverChain1)); + nttManagerChain1.enableRecvTransceiver(chainId2, address(transceiverChain1)); - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); nttManagerChain1.setOutboundLimit(type(uint64).max); nttManagerChain1.setInboundLimit(type(uint64).max, chainId2); @@ -101,53 +101,54 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { vm.chainId(chainId2); DummyToken t2 = new DummyTokenMintAndBurn(); NttManager implementationChain2 = new MockNttManagerContract( - address(t2), IManagerBase.Mode.BURNING, chainId2, 1 days, false + address(endpointChain2), + address(executorChain2), + 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(); + transceiverChain2 = new DummyTransceiver(chainId2, address(endpointChain2)); + nttManagerChain2.setTransceiver(address(transceiverChain2)); + nttManagerChain2.enableSendTransceiver(chainId1, address(transceiverChain2)); + nttManagerChain2.enableRecvTransceiver(chainId1, address(transceiverChain2)); - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2)); nttManagerChain2.setOutboundLimit(type(uint64).max); - nttManagerChain2.setInboundLimit(type(uint64).max, chainId1); + nttManagerChain2.setInboundLimit(type(uint64).max, chainId2); // 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 + chainId2, + bytes32(uint256(uint160(address(nttManagerChain2)))), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); nttManagerChain2.setPeer( - chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 7, type(uint64).max + chainId1, + bytes32(uint256(uint160(address(nttManagerChain1)))), + 7, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); - // Set peers for the transceivers - wormholeTransceiverChain1.setWormholePeer( - chainId2, bytes32(uint256(uint160(address(wormholeTransceiverChain2)))) - ); - wormholeTransceiverChain2.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) + require( + nttManagerChain1.getThreshold(chainId2) != 0, + "Threshold is zero with active transceivers" ); - require(nttManagerChain1.getThreshold() != 0, "Threshold is zero with active transceivers"); - - // Actually set it - nttManagerChain1.setThreshold(1); - nttManagerChain2.setThreshold(1); + nttManagerChain1.setThreshold(chainId2, 1); + nttManagerChain2.setThreshold(chainId1, 1); } + function test_setUp() public {} + function test_chainToChainBase() public { vm.chainId(chainId1); @@ -164,10 +165,18 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { vm.recordLogs(); // Send token through standard means (not relayer) + uint64 seqNo; { uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); uint256 userBalanceBefore = token1.balanceOf(address(userA)); - nttManagerChain1.transfer(sendingAmount, chainId2, bytes32(uint256(uint160(userB)))); + seqNo = nttManagerChain1.transfer( + sendingAmount, + chainId2, + bytes32(uint256(uint160(userB))), + executorChain1.createSignedQuote(executorChain2.chainId()), + executorChain1.createRelayInstructions(), + endpointChain1.createAdapterInstructions() + ); // Balance check on funds going in and out working as expected uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1)); @@ -182,35 +191,33 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { ); } - vm.stopPrank(); + assertEq(0, seqNo); + DummyTransceiver.Message[] memory rmsgs = transceiverChain1.getMessages(); + assertEq(1, rmsgs.length); - // Get the TransferSent(bytes32) event to ensure it matches up with the TransferRedeemed(bytes32) event later - Vm.Log[] memory recordedLogs = vm.getRecordedLogs(); - bytes32 sentEventDigest; - for (uint256 i = 0; i < recordedLogs.length; i++) { - if (recordedLogs[i].topics[0] == keccak256("TransferSent(bytes32)")) { - sentEventDigest = recordedLogs[i].topics[1]; - break; - } - } - require(sentEventDigest != bytes32(0), "TransferSent(bytes32) event should be found"); + // Get the execution events from the logs. + Vm.Log[] memory logEvents = vm.getRecordedLogs(); - // Get and sign the log to go down the other pipe. Thank you to whoever wrote this code in the past! - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(recordedLogs); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } + bytes memory encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain1), seqNo); + + vm.stopPrank(); // Chain2 verification and checks vm.chainId(chainId2); - // Wrong chain receiving the signed VAA - vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, chainId1, chainId2)); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + // Wrong chain receiving the attestation. + vm.expectRevert(abi.encodeWithSelector(Endpoint.InvalidDestinationChain.selector)); + transceiverChain1.receiveMessage(rmsgs[0]); + + // Right chain receiving the attestation. + transceiverChain2.receiveMessage(rmsgs[0]); + { uint256 supplyBefore = token2.totalSupply(); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + nttManagerChain2.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); uint256 supplyAfter = token2.totalSupply(); require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); @@ -220,28 +227,9 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { ); } - // Get the TransferRedeemed(bytes32) event to ensure it matches up with the TransferSent(bytes32) event earlier - recordedLogs = vm.getRecordedLogs(); - bytes32 recvEventDigest; - for (uint256 i = 0; i < recordedLogs.length; i++) { - if (recordedLogs[i].topics[0] == keccak256("TransferRedeemed(bytes32)")) { - recvEventDigest = recordedLogs[i].topics[1]; - break; - } - } - require( - sentEventDigest == recvEventDigest, - "TransferRedeemed(bytes32) event should match TransferSent(bytes32)" - ); - // Can't resubmit the same message twice - (IWormhole.VM memory wormholeVM,,) = wormhole.parseAndVerifyVM(encodedVMs[0]); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash - ) - ); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + vm.expectRevert(abi.encodeWithSelector(Endpoint.DuplicateMessageAttestation.selector)); + transceiverChain2.receiveMessage(rmsgs[0]); // Go back the other way from a THIRD user vm.prank(userB); @@ -254,13 +242,15 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { // Supply checks on the transfer { uint256 supplyBefore = token2.totalSupply(); - nttManagerChain2.transfer( + seqNo = nttManagerChain2.transfer( sendingAmount, chainId1, toWormholeFormat(userD), toWormholeFormat(userC), false, - encodeTransceiverInstruction(true) + executorChain2.createSignedQuote(executorChain1.chainId()), + executorChain2.createRelayInstructions(), + endpointChain2.createAdapterInstructions() ); uint256 supplyAfter = token2.totalSupply(); @@ -274,19 +264,26 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { ); } - // 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()); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); - } + assertEq(0, seqNo); + rmsgs = transceiverChain2.getMessages(); + assertEq(1, rmsgs.length); + + // Get the execution events from the logs. + logEvents = vm.getRecordedLogs(); + encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain2), seqNo); // Chain1 verification and checks with the receiving of the message vm.chainId(chainId1); + // Attest the transfer. + transceiverChain1.receiveMessage(rmsgs[0]); + { uint256 supplyBefore = token1.totalSupply(); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + nttManagerChain1.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); uint256 supplyAfter = token1.totalSupply(); @@ -313,16 +310,19 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { vm.recordLogs(); // Send token through standard means (not relayer) + uint64 seqNo; { uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); uint256 userBalanceBefore = token1.balanceOf(address(userA)); - nttManagerChain1.transfer( + seqNo = nttManagerChain1.transfer( sendingAmount, chainId2, toWormholeFormat(userB), toWormholeFormat(userA), true, - encodeTransceiverInstruction(true) + executorChain1.createSignedQuote(executorChain2.chainId()), + executorChain1.createRelayInstructions(), + endpointChain1.createAdapterInstructions() ); // Balance check on funds going in and out working as expected @@ -338,28 +338,30 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { ); } + assertEq(0, seqNo); + DummyTransceiver.Message[] memory rmsgs = transceiverChain1.getMessages(); + assertEq(1, rmsgs.length); + + // Get the execution events from the logs. + Vm.Log[] memory logEvents = vm.getRecordedLogs(); + bytes memory encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain1), seqNo); + vm.stopPrank(); - // Get and sign the log to go down the other pipe. Thank you to whoever wrote this code in the past! - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } + // Wrong chain receiving the attestation. + vm.expectRevert(abi.encodeWithSelector(Endpoint.InvalidDestinationChain.selector)); + transceiverChain1.receiveMessage(rmsgs[0]); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.InvalidWormholePeer.selector, - chainId1, - wormholeTransceiverChain1 - ) - ); // Wrong chain receiving the signed VAA - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + // Right chain receiving the attestation. + transceiverChain2.receiveMessage(rmsgs[0]); vm.chainId(chainId2); { uint256 supplyBefore = token2.totalSupply(); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + nttManagerChain2.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); uint256 supplyAfter = token2.totalSupply(); require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); @@ -370,13 +372,8 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { } // Can't resubmit the same message twice - (IWormhole.VM memory wormholeVM,,) = wormhole.parseAndVerifyVM(encodedVMs[0]); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash - ) - ); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + vm.expectRevert(abi.encodeWithSelector(Endpoint.DuplicateMessageAttestation.selector)); + transceiverChain2.receiveMessage(rmsgs[0]); // Go back the other way from a THIRD user vm.prank(userB); @@ -394,13 +391,15 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { nttManagerChain2.setOutboundLimit(0); vm.startPrank(userC); - nttManagerChain2.transfer( + seqNo = nttManagerChain2.transfer( sendingAmount, chainId1, toWormholeFormat(userD), toWormholeFormat(userC), true, - encodeTransceiverInstruction(true) + executorChain2.createSignedQuote(executorChain1.chainId(), 2 days), // We are going to warp the time below. + executorChain2.createRelayInstructions(), + endpointChain2.createAdapterInstructions() ); // Test timing on the queues @@ -449,23 +448,31 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { ); } - // 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()); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); - } + // This should be the first message sent on chain2. + assertEq(0, seqNo); + rmsgs = transceiverChain2.getMessages(); + assertEq(1, rmsgs.length); + + // Get the execution events from the logs. + logEvents = vm.getRecordedLogs(); + encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain2), seqNo); // Chain1 verification and checks with the receiving of the message vm.chainId(chainId1); vm.stopPrank(); // Back to the owner of everything for this one. vm.recordLogs(); + // Attest the transfer on chain1. + transceiverChain1.receiveMessage(rmsgs[0]); + { uint256 supplyBefore = token1.totalSupply(); nttManagerChain1.setInboundLimit(0, chainId2); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + nttManagerChain1.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); bytes32[] memory queuedDigests = Utils.fetchQueuedTransferDigestsFromLogs(vm.getRecordedLogs()); @@ -491,57 +498,19 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { } } - function test_multiTransceiver() public { + function test_multIAdapter() public { vm.chainId(chainId1); - WormholeTransceiver wormholeTransceiverChain1_1 = wormholeTransceiverChain1; - - // Dual transceiver setup - WormholeTransceiver wormholeTransceiverChain1_2 = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - wormholeTransceiverChain1_2 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain1_2), "")) - ); - wormholeTransceiverChain1_2.initialize(); - - vm.chainId(chainId2); - WormholeTransceiver wormholeTransceiverChain2_1 = wormholeTransceiverChain2; - - // Dual transceiver setup - WormholeTransceiver wormholeTransceiverChain2_2 = new MockWormholeTransceiverContract( - address(nttManagerChain2), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); + // Create a dual transceiver for each manager. + DummyTransceiver[] memory transceiversChain1 = new DummyTransceiver[](2); + (transceiversChain1[0], transceiversChain1[1]) = + TransceiverHelpersLib.addTransceiver(nttManagerChain1, transceiverChain1, chainId2); - wormholeTransceiverChain2_2 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain2_2), "")) - ); - wormholeTransceiverChain2_2.initialize(); - - // Setup the new entrypoint hook ups to allow the transfers to occur - wormholeTransceiverChain1_2.setWormholePeer( - chainId2, bytes32(uint256(uint160((address(wormholeTransceiverChain2_2))))) - ); - wormholeTransceiverChain2_2.setWormholePeer( - chainId1, bytes32(uint256(uint160((address(wormholeTransceiverChain1_2))))) - ); - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2_2)); - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1_2)); - - // Change the threshold from the setUp functions 1 to 2. - nttManagerChain1.setThreshold(2); - nttManagerChain2.setThreshold(2); + nttManagerChain2.disableSendTransceiver(chainId1, address(transceiverChain2)); + nttManagerChain2.disableRecvTransceiver(chainId1, address(transceiverChain2)); + DummyTransceiver[] memory transceiversChain2 = new DummyTransceiver[](2); + (transceiversChain2[0], transceiversChain2[1]) = + TransceiverHelpersLib.setup_transceivers(nttManagerChain2, chainId1); // Setting up the transfer DummyToken token1 = DummyToken(nttManagerChain1.token()); @@ -558,47 +527,50 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { vm.recordLogs(); // Send token through standard means (not relayer) + uint64 seqNo; { - nttManagerChain1.transfer( + seqNo = nttManagerChain1.transfer( sendingAmount, chainId2, toWormholeFormat(userB), toWormholeFormat(userA), false, - encodeTransceiverInstructions(true) + executorChain1.createSignedQuote(executorChain2.chainId()), + executorChain1.createRelayInstructions(), + endpointChain1.createAdapterInstructions() ); } - // Get and sign the event emissions to go to the other chain. - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } + // This should be the first message sent on chain1 on both transceivers. + assertEq(0, seqNo); + DummyTransceiver.Message[] memory rmsgs1 = transceiversChain1[0].getMessages(); + assertEq(1, rmsgs1.length); + + DummyTransceiver.Message[] memory rmsgs2 = transceiversChain1[1].getMessages(); + assertEq(1, rmsgs2.length); + + // Get the execution events from the logs. + Vm.Log[] memory logEvents = vm.getRecordedLogs(); + bytes memory encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain1), seqNo); vm.chainId(chainId2); - // Send in the messages for the two transceivers to complete the transfer from chain1 to chain2 + // Attest the transfer on both transceivers on chain2. + transceiversChain2[0].receiveMessage(rmsgs1[0]); + transceiversChain2[1].receiveMessage(rmsgs2[0]); + + // Execute the message to complete the transfer from chain1 to chain2. Only need to execute once. { - // vm.stopPrank(); + // Nothing should update until we call execute. uint256 supplyBefore = token2.totalSupply(); - wormholeTransceiverChain2_1.receiveMessage(encodedVMs[0]); - - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.InvalidWormholePeer.selector, - chainId1, - wormholeTransceiverChain1_1 - ) - ); - wormholeTransceiverChain2_2.receiveMessage(encodedVMs[0]); - - // Threshold check require(supplyBefore == token2.totalSupply(), "Supplies have been updated too early"); require(token2.balanceOf(userB) == 0, "User received tokens to early"); - // Finish the transfer out once the second VAA arrives - wormholeTransceiverChain2_2.receiveMessage(encodedVMs[1]); + nttManagerChain2.executeMsg( + rmsgs1[0].srcChain, rmsgs1[0].srcAddr, rmsgs1[0].sequence, encoded + ); + uint256 supplyAfter = token2.totalSupply(); require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); @@ -617,13 +589,15 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { // Send token through standard means (not relayer) { uint256 userBalanceBefore = token1.balanceOf(address(userB)); - nttManagerChain2.transfer( + seqNo = nttManagerChain2.transfer( sendingAmount, chainId1, toWormholeFormat(userA), toWormholeFormat(userB), false, - encodeTransceiverInstructions(true) + executorChain2.createSignedQuote(executorChain1.chainId()), + executorChain2.createRelayInstructions(), + endpointChain2.createAdapterInstructions() ); uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain2)); uint256 userBalanceAfter = token1.balanceOf(address(userB)); @@ -632,25 +606,35 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { require(nttManagerBalanceAfter == 0, "NttManager should burn all tranferred tokens"); } - // Get the VAA proof for the transfers to use - entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); - } + // This should be the first message sent on chain2 on both transceivers. + assertEq(0, seqNo); + rmsgs1 = transceiversChain2[0].getMessages(); + assertEq(1, rmsgs1.length); + + rmsgs2 = transceiversChain2[1].getMessages(); + assertEq(1, rmsgs2.length); + + // Get the execution events from the logs. + logEvents = vm.getRecordedLogs(); + encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain2), seqNo); vm.chainId(chainId1); + + // Attest the transfer on both transceivers on chain2. + transceiversChain1[0].receiveMessage(rmsgs1[0]); + transceiversChain1[1].receiveMessage(rmsgs2[0]); + { uint256 supplyBefore = token1.totalSupply(); - wormholeTransceiverChain1_1.receiveMessage(encodedVMs[0]); - require(supplyBefore == token1.totalSupply(), "Supplies have been updated too early"); require(token2.balanceOf(userA) == 0, "User received tokens to early"); - // Finish the transfer out once the second VAA arrives - wormholeTransceiverChain1_2.receiveMessage(encodedVMs[1]); - uint256 supplyAfter = token1.totalSupply(); + nttManagerChain1.executeMsg( + rmsgs1[0].srcChain, rmsgs1[0].srcAddr, rmsgs1[0].sequence, encoded + ); + uint256 supplyAfter = token1.totalSupply(); require( supplyBefore == supplyAfter, "Supplies don't match between operations. Should not increase." @@ -676,43 +660,4 @@ contract TestEndToEndBase is Test, IRateLimiterEvents { } return copy; } - - 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 8d7e5d0fe..4b58b54f6 100755 --- a/evm/test/IntegrationWithoutRateLimiting.t.sol +++ b/evm/test/IntegrationWithoutRateLimiting.t.sol @@ -5,31 +5,39 @@ 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 "./libraries/TransceiverHelpers.sol"; +import "./mocks/DummyTransceiver.sol"; import "./mocks/MockNttManager.sol"; -import "./mocks/MockTransceivers.sol"; +import "./mocks/MockEndpoint.sol"; +import "./mocks/MockExecutor.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"; +import "example-messaging-endpoint/evm/src/Endpoint.sol"; -contract TestEndToEndNoRateLimiting is Test { +contract TestNoRateLimitingEndToEndBase is Test, IRateLimiterEvents { NttManagerNoRateLimiting nttManagerChain1; NttManagerNoRateLimiting nttManagerChain2; + MockEndpoint endpointChain1; + MockEndpoint endpointChain2; + + MockExecutor executorChain1; + MockExecutor executorChain2; + + DummyTransceiver transceiverChain1; + DummyTransceiver transceiverChain2; + using TrimmedAmountLib for uint256; using TrimmedAmountLib for TrimmedAmount; @@ -44,8 +52,6 @@ contract TestEndToEndNoRateLimiting is Test { WormholeSimulator guardian; uint256 initialBlockTimestamp; - WormholeTransceiver wormholeTransceiverChain1; - WormholeTransceiver wormholeTransceiverChain2; address userA = address(0x123); address userB = address(0x456); address userC = address(0x789); @@ -61,10 +67,20 @@ contract TestEndToEndNoRateLimiting is Test { guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK); + endpointChain1 = new MockEndpoint(chainId1); + endpointChain2 = new MockEndpoint(chainId2); + + executorChain1 = new MockExecutor(chainId1); + executorChain2 = new MockExecutor(chainId2); + vm.chainId(chainId1); DummyToken t1 = new DummyToken(); NttManagerNoRateLimiting implementation = new MockNttManagerNoRateLimitingContract( - address(t1), IManagerBase.Mode.LOCKING, chainId1 + address(endpointChain1), + address(executorChain1), + address(t1), + IManagerBase.Mode.LOCKING, + chainId1 ); nttManagerChain1 = MockNttManagerNoRateLimitingContract( @@ -72,37 +88,20 @@ contract TestEndToEndNoRateLimiting is Test { ); nttManagerChain1.initialize(); - 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(); - - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); - // nttManagerChain1.setOutboundLimit(type(uint64).max); - // nttManagerChain1.setInboundLimit(type(uint64).max, chainId2); + transceiverChain1 = new DummyTransceiver(chainId1, address(endpointChain1)); + nttManagerChain1.setTransceiver(address(transceiverChain1)); + nttManagerChain1.enableSendTransceiver(chainId2, address(transceiverChain1)); + nttManagerChain1.enableRecvTransceiver(chainId2, address(transceiverChain1)); // Chain 2 setup vm.chainId(chainId2); DummyToken t2 = new DummyTokenMintAndBurn(); NttManagerNoRateLimiting implementationChain2 = new MockNttManagerNoRateLimitingContract( - address(t2), IManagerBase.Mode.BURNING, chainId2 + address(endpointChain2), + address(executorChain2), + address(t2), + IManagerBase.Mode.BURNING, + chainId2 ); nttManagerChain2 = MockNttManagerNoRateLimitingContract( @@ -110,49 +109,41 @@ contract TestEndToEndNoRateLimiting is Test { ); 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(); - - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2)); - // nttManagerChain2.setOutboundLimit(type(uint64).max); - // nttManagerChain2.setInboundLimit(type(uint64).max, chainId1); + transceiverChain2 = new DummyTransceiver(chainId2, address(endpointChain2)); + nttManagerChain2.setTransceiver(address(transceiverChain2)); + nttManagerChain2.enableSendTransceiver(chainId1, address(transceiverChain2)); + nttManagerChain2.enableRecvTransceiver(chainId1, address(transceiverChain2)); // 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 + chainId2, + bytes32(uint256(uint160(address(nttManagerChain2)))), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); nttManagerChain2.setPeer( - chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 7, type(uint64).max + chainId1, + bytes32(uint256(uint160(address(nttManagerChain1)))), + 7, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); - // Set peers for the transceivers - wormholeTransceiverChain1.setWormholePeer( - chainId2, bytes32(uint256(uint160(address(wormholeTransceiverChain2)))) - ); - wormholeTransceiverChain2.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) + require( + nttManagerChain1.getThreshold(chainId2) != 0, + "Threshold is zero with active transceivers" ); - require(nttManagerChain1.getThreshold() != 0, "Threshold is zero with active transceivers"); - - // Actually set it - nttManagerChain1.setThreshold(1); - nttManagerChain2.setThreshold(1); + nttManagerChain1.setThreshold(chainId2, 1); + nttManagerChain2.setThreshold(chainId1, 1); INttManager.NttManagerPeer memory peer = nttManagerChain1.getPeer(chainId2); require(9 == peer.tokenDecimals, "Peer has the wrong number of token decimals"); } + function test_setUp() public {} + function test_chainToChainBase() public { vm.chainId(chainId1); @@ -169,10 +160,18 @@ contract TestEndToEndNoRateLimiting is Test { vm.recordLogs(); // Send token through standard means (not relayer) + uint64 seqNo; { uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); uint256 userBalanceBefore = token1.balanceOf(address(userA)); - nttManagerChain1.transfer(sendingAmount, chainId2, bytes32(uint256(uint160(userB)))); + seqNo = nttManagerChain1.transfer( + sendingAmount, + chainId2, + bytes32(uint256(uint160(userB))), + executorChain1.createSignedQuote(executorChain2.chainId()), + executorChain1.createRelayInstructions(), + endpointChain1.createAdapterInstructions() + ); // Balance check on funds going in and out working as expected uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain1)); @@ -187,24 +186,32 @@ contract TestEndToEndNoRateLimiting is Test { ); } - vm.stopPrank(); + assertEq(0, seqNo); + DummyTransceiver.Message[] memory rmsgs = transceiverChain1.getMessages(); + assertEq(1, rmsgs.length); - // Get and sign the log to go down the other pipe. Thank you to whoever wrote this code in the past! - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } + // Get the execution events from the logs. + Vm.Log[] memory logEvents = vm.getRecordedLogs(); + bytes memory encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain1), seqNo); + + vm.stopPrank(); // Chain2 verification and checks vm.chainId(chainId2); - // Wrong chain receiving the signed VAA - vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, chainId1, chainId2)); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + // Wrong chain receiving the attestation. + vm.expectRevert(abi.encodeWithSelector(Endpoint.InvalidDestinationChain.selector)); + transceiverChain1.receiveMessage(rmsgs[0]); + + // Right chain receiving the attestation. + transceiverChain2.receiveMessage(rmsgs[0]); + { uint256 supplyBefore = token2.totalSupply(); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + nttManagerChain2.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); uint256 supplyAfter = token2.totalSupply(); require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); @@ -216,13 +223,8 @@ contract TestEndToEndNoRateLimiting is Test { } // Can't resubmit the same message twice - (IWormhole.VM memory wormholeVM,,) = wormhole.parseAndVerifyVM(encodedVMs[0]); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash - ) - ); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + vm.expectRevert(abi.encodeWithSelector(Endpoint.DuplicateMessageAttestation.selector)); + transceiverChain2.receiveMessage(rmsgs[0]); // Go back the other way from a THIRD user vm.prank(userB); @@ -235,13 +237,15 @@ contract TestEndToEndNoRateLimiting is Test { // Supply checks on the transfer { uint256 supplyBefore = token2.totalSupply(); - nttManagerChain2.transfer( + seqNo = nttManagerChain2.transfer( sendingAmount, chainId1, toWormholeFormat(userD), toWormholeFormat(userC), false, - encodeTransceiverInstruction(true) + executorChain2.createSignedQuote(executorChain1.chainId()), + executorChain2.createRelayInstructions(), + endpointChain2.createAdapterInstructions() ); uint256 supplyAfter = token2.totalSupply(); @@ -255,19 +259,26 @@ contract TestEndToEndNoRateLimiting is Test { ); } - // 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()); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); - } + assertEq(0, seqNo); + rmsgs = transceiverChain2.getMessages(); + assertEq(1, rmsgs.length); + + // Get the execution events from the logs. + logEvents = vm.getRecordedLogs(); + encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain2), seqNo); // Chain1 verification and checks with the receiving of the message vm.chainId(chainId1); + // Attest the transfer. + transceiverChain1.receiveMessage(rmsgs[0]); + { uint256 supplyBefore = token1.totalSupply(); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + nttManagerChain1.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); uint256 supplyAfter = token1.totalSupply(); @@ -297,20 +308,34 @@ contract TestEndToEndNoRateLimiting is Test { // Everything else should. vm.expectRevert(abi.encodeWithSelector(INttManager.InvalidPeerChainIdZero.selector)); nttManagerChain1.setPeer( - 0, bytes32(uint256(uint160(address(nttManagerChain2)))), 9, type(uint64).max + 0, + bytes32(uint256(uint160(address(nttManagerChain2)))), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); vm.expectRevert(abi.encodeWithSelector(INttManager.InvalidPeerZeroAddress.selector)); - nttManagerChain1.setPeer(chainId2, bytes32(0), 9, type(uint64).max); + nttManagerChain1.setPeer( + chainId2, bytes32(0), 9, NttManagerHelpersLib.gasLimit, type(uint64).max + ); vm.expectRevert(abi.encodeWithSelector(INttManager.InvalidPeerDecimals.selector)); nttManagerChain1.setPeer( - chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 0, type(uint64).max + chainId2, + bytes32(uint256(uint160(address(nttManagerChain2)))), + 0, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); vm.expectRevert(abi.encodeWithSelector(INttManager.InvalidPeerSameChainId.selector)); nttManagerChain1.setPeer( - chainId1, bytes32(uint256(uint160(address(nttManagerChain2)))), 9, type(uint64).max + chainId1, + bytes32(uint256(uint160(address(nttManagerChain2)))), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); @@ -345,16 +370,19 @@ contract TestEndToEndNoRateLimiting is Test { vm.recordLogs(); // Send token through standard means (not relayer) + uint64 seqNo; { uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); uint256 userBalanceBefore = token1.balanceOf(address(userA)); - nttManagerChain1.transfer( + seqNo = nttManagerChain1.transfer( sendingAmount, chainId2, toWormholeFormat(userB), toWormholeFormat(userA), true, - encodeTransceiverInstruction(true) + executorChain1.createSignedQuote(executorChain2.chainId()), + executorChain1.createRelayInstructions(), + endpointChain1.createAdapterInstructions() ); // Balance check on funds going in and out working as expected @@ -370,28 +398,30 @@ contract TestEndToEndNoRateLimiting is Test { ); } + assertEq(0, seqNo); + DummyTransceiver.Message[] memory rmsgs = transceiverChain1.getMessages(); + assertEq(1, rmsgs.length); + + // Get the execution events from the logs. + Vm.Log[] memory logEvents = vm.getRecordedLogs(); + bytes memory encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain1), seqNo); + vm.stopPrank(); - // Get and sign the log to go down the other pipe. Thank you to whoever wrote this code in the past! - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } + // Wrong chain receiving the attestation. + vm.expectRevert(abi.encodeWithSelector(Endpoint.InvalidDestinationChain.selector)); + transceiverChain1.receiveMessage(rmsgs[0]); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.InvalidWormholePeer.selector, - chainId1, - wormholeTransceiverChain1 - ) - ); // Wrong chain receiving the signed VAA - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + // Right chain receiving the attestation. + transceiverChain2.receiveMessage(rmsgs[0]); vm.chainId(chainId2); { uint256 supplyBefore = token2.totalSupply(); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + nttManagerChain2.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); uint256 supplyAfter = token2.totalSupply(); require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); @@ -403,13 +433,8 @@ contract TestEndToEndNoRateLimiting is Test { } // Can't resubmit the same message twice - (IWormhole.VM memory wormholeVM,,) = wormhole.parseAndVerifyVM(encodedVMs[0]); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash - ) - ); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); + vm.expectRevert(abi.encodeWithSelector(Endpoint.DuplicateMessageAttestation.selector)); + transceiverChain2.receiveMessage(rmsgs[0]); // Go back the other way from a THIRD user vm.prank(userB); @@ -427,13 +452,15 @@ contract TestEndToEndNoRateLimiting is Test { // nttManagerChain2.setOutboundLimit(0); vm.startPrank(userC); - nttManagerChain2.transfer( + seqNo = nttManagerChain2.transfer( sendingAmount, chainId1, toWormholeFormat(userD), toWormholeFormat(userC), true, - encodeTransceiverInstruction(true) + executorChain2.createSignedQuote(executorChain1.chainId()), + executorChain2.createRelayInstructions(), + endpointChain2.createAdapterInstructions() ); uint256 supplyAfter = token2.totalSupply(); @@ -447,23 +474,30 @@ contract TestEndToEndNoRateLimiting is Test { ); } - // 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()); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); - } + // This should be the first message sent on chain2. + assertEq(0, seqNo); + rmsgs = transceiverChain2.getMessages(); + assertEq(1, rmsgs.length); + + // Get the execution events from the logs. + logEvents = vm.getRecordedLogs(); + encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain2), seqNo); // Chain1 verification and checks with the receiving of the message vm.chainId(chainId1); vm.stopPrank(); // Back to the owner of everything for this one. vm.recordLogs(); + // Attest the transfer on chain1. + transceiverChain1.receiveMessage(rmsgs[0]); + { uint256 supplyBefore = token1.totalSupply(); - // nttManagerChain1.setInboundLimit(0, chainId2); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); + nttManagerChain1.executeMsg( + rmsgs[0].srcChain, rmsgs[0].srcAddr, rmsgs[0].sequence, encoded + ); bytes32[] memory queuedDigests = Utils.fetchQueuedTransferDigestsFromLogs(vm.getRecordedLogs()); @@ -479,57 +513,19 @@ contract TestEndToEndNoRateLimiting is Test { } } - function test_multiTransceiver() public { + function test_multIAdapter() public { vm.chainId(chainId1); - WormholeTransceiver wormholeTransceiverChain1_1 = wormholeTransceiverChain1; + // Create a dual transceiver for each manager. + DummyTransceiver[] memory transceiversChain1 = new DummyTransceiver[](2); + (transceiversChain1[0], transceiversChain1[1]) = + TransceiverHelpersLib.addTransceiver(nttManagerChain1, transceiverChain1, chainId2); - // Dual transceiver setup - WormholeTransceiver wormholeTransceiverChain1_2 = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - wormholeTransceiverChain1_2 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain1_2), "")) - ); - wormholeTransceiverChain1_2.initialize(); - - vm.chainId(chainId2); - WormholeTransceiver wormholeTransceiverChain2_1 = wormholeTransceiverChain2; - - // Dual transceiver setup - WormholeTransceiver wormholeTransceiverChain2_2 = new MockWormholeTransceiverContract( - address(nttManagerChain2), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - wormholeTransceiverChain2_2 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain2_2), "")) - ); - wormholeTransceiverChain2_2.initialize(); - - // Setup the new entrypoint hook ups to allow the transfers to occur - wormholeTransceiverChain1_2.setWormholePeer( - chainId2, bytes32(uint256(uint160((address(wormholeTransceiverChain2_2))))) - ); - wormholeTransceiverChain2_2.setWormholePeer( - chainId1, bytes32(uint256(uint160((address(wormholeTransceiverChain1_2))))) - ); - nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2_2)); - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1_2)); - - // Change the threshold from the setUp functions 1 to 2. - nttManagerChain1.setThreshold(2); - nttManagerChain2.setThreshold(2); + nttManagerChain2.disableSendTransceiver(chainId1, address(transceiverChain2)); + nttManagerChain2.disableRecvTransceiver(chainId1, address(transceiverChain2)); + DummyTransceiver[] memory transceiversChain2 = new DummyTransceiver[](2); + (transceiversChain2[0], transceiversChain2[1]) = + TransceiverHelpersLib.setup_transceivers(nttManagerChain2, chainId1); // Setting up the transfer DummyToken token1 = DummyToken(nttManagerChain1.token()); @@ -546,47 +542,50 @@ contract TestEndToEndNoRateLimiting is Test { vm.recordLogs(); // Send token through standard means (not relayer) + uint64 seqNo; { - nttManagerChain1.transfer( + seqNo = nttManagerChain1.transfer( sendingAmount, chainId2, toWormholeFormat(userB), toWormholeFormat(userA), false, - encodeTransceiverInstructions(true) + executorChain1.createSignedQuote(executorChain2.chainId()), + executorChain1.createRelayInstructions(), + endpointChain1.createAdapterInstructions() ); } - // Get and sign the event emissions to go to the other chain. - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId1); - } + // This should be the first message sent on chain1 on both transceivers. + assertEq(0, seqNo); + DummyTransceiver.Message[] memory rmsgs1 = transceiversChain1[0].getMessages(); + assertEq(1, rmsgs1.length); + + DummyTransceiver.Message[] memory rmsgs2 = transceiversChain1[1].getMessages(); + assertEq(1, rmsgs2.length); + + // Get the execution events from the logs. + Vm.Log[] memory logEvents = vm.getRecordedLogs(); + bytes memory encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain1), seqNo); vm.chainId(chainId2); - // Send in the messages for the two transceivers to complete the transfer from chain1 to chain2 + // Attest the transfer on both transceivers on chain2. + transceiversChain2[0].receiveMessage(rmsgs1[0]); + transceiversChain2[1].receiveMessage(rmsgs2[0]); + + // Execute the message to complete the transfer from chain1 to chain2. Only need to execute once. { - // vm.stopPrank(); + // Nothing should update until we call execute. uint256 supplyBefore = token2.totalSupply(); - wormholeTransceiverChain2_1.receiveMessage(encodedVMs[0]); - - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.InvalidWormholePeer.selector, - chainId1, - wormholeTransceiverChain1_1 - ) - ); - wormholeTransceiverChain2_2.receiveMessage(encodedVMs[0]); - - // Threshold check require(supplyBefore == token2.totalSupply(), "Supplies have been updated too early"); require(token2.balanceOf(userB) == 0, "User received tokens to early"); - // Finish the transfer out once the second VAA arrives - wormholeTransceiverChain2_2.receiveMessage(encodedVMs[1]); + nttManagerChain2.executeMsg( + rmsgs1[0].srcChain, rmsgs1[0].srcAddr, rmsgs1[0].sequence, encoded + ); + uint256 supplyAfter = token2.totalSupply(); require(sendingAmount + supplyBefore == supplyAfter, "Supplies dont match"); @@ -606,13 +605,15 @@ contract TestEndToEndNoRateLimiting is Test { // Send token through standard means (not relayer) { uint256 userBalanceBefore = token1.balanceOf(address(userB)); - nttManagerChain2.transfer( + seqNo = nttManagerChain2.transfer( sendingAmount, chainId1, toWormholeFormat(userA), toWormholeFormat(userB), false, - encodeTransceiverInstructions(true) + executorChain2.createSignedQuote(executorChain1.chainId()), + executorChain2.createRelayInstructions(), + endpointChain2.createAdapterInstructions() ); uint256 nttManagerBalanceAfter = token1.balanceOf(address(nttManagerChain2)); uint256 userBalanceAfter = token1.balanceOf(address(userB)); @@ -624,25 +625,35 @@ contract TestEndToEndNoRateLimiting is Test { ); } - // Get the VAA proof for the transfers to use - entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], chainId2); - } + // This should be the first message sent on chain2 on both transceivers. + assertEq(0, seqNo); + rmsgs1 = transceiversChain2[0].getMessages(); + assertEq(1, rmsgs1.length); + + rmsgs2 = transceiversChain2[1].getMessages(); + assertEq(1, rmsgs2.length); + + // Get the execution events from the logs. + logEvents = vm.getRecordedLogs(); + encoded = + TransceiverHelpersLib.getExecutionSent(logEvents, address(nttManagerChain2), seqNo); vm.chainId(chainId1); + + // Attest the transfer on both transceivers on chain2. + transceiversChain1[0].receiveMessage(rmsgs1[0]); + transceiversChain1[1].receiveMessage(rmsgs2[0]); + { uint256 supplyBefore = token1.totalSupply(); - wormholeTransceiverChain1_1.receiveMessage(encodedVMs[0]); - require(supplyBefore == token1.totalSupply(), "Supplies have been updated too early"); require(token2.balanceOf(userA) == 0, "User received tokens to early"); - // Finish the transfer out once the second VAA arrives - wormholeTransceiverChain1_2.receiveMessage(encodedVMs[1]); - uint256 supplyAfter = token1.totalSupply(); + nttManagerChain1.executeMsg( + rmsgs1[0].srcChain, rmsgs1[0].srcAddr, rmsgs1[0].sequence, encoded + ); + uint256 supplyAfter = token1.totalSupply(); require( supplyBefore == supplyAfter, "Supplies don't match between operations. Should not increase." @@ -668,43 +679,4 @@ contract TestEndToEndNoRateLimiting is Test { } return copy; } - - 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/NttManager.t.sol b/evm/test/NttManager.t.sol index 171940e0d..ef1765b90 100644 --- a/evm/test/NttManager.t.sol +++ b/evm/test/NttManager.t.sol @@ -9,7 +9,7 @@ import "../src/interfaces/INttManager.sol"; import "../src/interfaces/IRateLimiter.sol"; import "../src/interfaces/IManagerBase.sol"; import "../src/interfaces/IRateLimiterEvents.sol"; -import "../src/NttManager/TransceiverRegistry.sol"; +import "../src/libraries/external/OwnableUpgradeable.sol"; import "../src/libraries/PausableUpgradeable.sol"; import "../src/libraries/TransceiverHelpers.sol"; import {Utils} from "./libraries/Utils.sol"; @@ -19,18 +19,27 @@ 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 "example-messaging-endpoint/evm/src/AdapterRegistry.sol"; import "./libraries/TransceiverHelpers.sol"; import "./libraries/NttManagerHelpers.sol"; -import "./interfaces/ITransceiverReceiver.sol"; import "./mocks/DummyTransceiver.sol"; import "../src/mocks/DummyToken.sol"; import "./mocks/MockNttManager.sol"; +import "./mocks/MockEndpoint.sol"; +import "./mocks/MockExecutor.sol"; // TODO: set this up so the common functionality tests can be run against both contract TestNttManager is Test, IRateLimiterEvents { MockNttManagerContract nttManager; MockNttManagerContract nttManagerOther; MockNttManagerContract nttManagerZeroRateLimiter; + MockNttManagerContract nttManagerZeroRateLimiterOther; + MockEndpoint endpoint; + MockEndpoint endpointOther; + MockExecutor executor; + MockExecutor executorOther; + DummyTransceiver transceiver; + DummyTransceiver transceiverOther; using TrimmedAmountLib for uint256; using TrimmedAmountLib for TrimmedAmount; @@ -38,11 +47,14 @@ contract TestNttManager is Test, IRateLimiterEvents { // 0x99'E''T''T' uint16 constant chainId = 7; uint16 constant chainId2 = 8; + + address user_A = address(0x123); + address user_B = address(0x456); + uint256 constant DEVNET_GUARDIAN_PK = 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; WormholeSimulator guardian; uint256 initialBlockTimestamp; - DummyTransceiver dummyTransceiver; function setUp() public { string memory url = "https://ethereum-sepolia-rpc.publicnode.com"; @@ -52,26 +64,72 @@ contract TestNttManager is Test, IRateLimiterEvents { guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK); + endpoint = new MockEndpoint(chainId); + endpointOther = new MockEndpoint(chainId2); + + executor = new MockExecutor(chainId); + executorOther = new MockExecutor(chainId2); + DummyToken t = new DummyToken(); NttManager implementation = new MockNttManagerContract( - address(t), IManagerBase.Mode.LOCKING, chainId, 1 days, false + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + chainId, + 1 days, + false ); - NttManager otherImplementation = new MockNttManagerContract( - address(t), IManagerBase.Mode.LOCKING, chainId, 1 days, false + NttManager implementationOther = new MockNttManagerContract( + address(endpointOther), + address(executorOther), + address(t), + IManagerBase.Mode.LOCKING, + chainId2, + 1 days, + false ); nttManager = MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); nttManager.initialize(); + assertEq(uint8(IManagerBase.Mode.LOCKING), nttManager.getMode()); + assertEq(0, nttManager.nextMessageSequence()); + nttManagerOther = - MockNttManagerContract(address(new ERC1967Proxy(address(otherImplementation), ""))); + MockNttManagerContract(address(new ERC1967Proxy(address(implementationOther), ""))); nttManagerOther.initialize(); - dummyTransceiver = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(dummyTransceiver)); + nttManager.setPeer( + chainId2, + toWormholeFormat(address(nttManagerOther)), + t.decimals(), + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); + + nttManagerOther.setPeer( + chainId, + toWormholeFormat(address(nttManager)), + t.decimals(), + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); + + transceiver = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(transceiver)); + nttManager.enableSendTransceiver(chainId2, address(transceiver)); + nttManager.enableRecvTransceiver(chainId2, address(transceiver)); + + transceiverOther = new DummyTransceiver(chainId2, address(endpointOther)); + nttManagerOther.setTransceiver(address(transceiverOther)); + nttManagerOther.enableSendTransceiver(chainId, address(transceiverOther)); + nttManagerOther.enableRecvTransceiver(chainId, address(transceiverOther)); } + function test_setUp() public {} + // === pure unit tests // naive implementation of countSetBits to test against @@ -97,40 +155,86 @@ contract TestNttManager is Test, IRateLimiterEvents { // === Deployments with rate limiter disabled function test_disabledRateLimiter() public { - DummyToken t = new DummyToken(); - NttManager implementation = - new MockNttManagerContract(address(t), IManagerBase.Mode.LOCKING, chainId, 0, true); + DummyToken token = new DummyToken(); + uint8 decimals = token.decimals(); + // Create the first NttManager without rate limiting with two transceivers. + NttManager implementation = new MockNttManagerContract( + address(endpoint), + address(executor), + address(token), + IManagerBase.Mode.LOCKING, + chainId, + 0, + true + ); nttManagerZeroRateLimiter = MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); nttManagerZeroRateLimiter.initialize(); - - DummyTransceiver e = new DummyTransceiver(address(nttManagerZeroRateLimiter)); - nttManagerZeroRateLimiter.setTransceiver(address(e)); - - address user_A = address(0x123); - address user_B = address(0x456); - - uint8 decimals = t.decimals(); + TransceiverHelpersLib.setup_transceivers(nttManagerZeroRateLimiter, chainId2); + + // Create the second NttManager without rate limiting with two transceivers. + implementation = new MockNttManagerContract( + address(endpointOther), + address(executorOther), + address(token), + IManagerBase.Mode.LOCKING, + chainId2, + 0, + true + ); + nttManagerZeroRateLimiterOther = + MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); + nttManagerZeroRateLimiterOther.initialize(); + DummyTransceiver[] memory transceiversOther = new DummyTransceiver[](2); + (transceiversOther[0], transceiversOther[1]) = + TransceiverHelpersLib.setup_transceivers(nttManagerZeroRateLimiterOther, chainId); nttManagerZeroRateLimiter.setPeer( - chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max + chainId2, + toWormholeFormat(address(nttManagerZeroRateLimiterOther)), + token.decimals(), + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); + + nttManagerZeroRateLimiterOther.setPeer( + chainId, + toWormholeFormat(address(nttManagerZeroRateLimiter)), + token.decimals(), + NttManagerHelpersLib.gasLimit, + type(uint64).max ); - t.mintDummy(address(user_A), 5 * 10 ** decimals); + token.mintDummy(address(user_A), 5 * 10 ** decimals); // Test outgoing transfers complete successfully with rate limit disabled vm.startPrank(user_A); - t.approve(address(nttManagerZeroRateLimiter), 3 * 10 ** decimals); + token.approve(address(nttManagerZeroRateLimiter), 3 * 10 ** decimals); uint64 s1 = nttManagerZeroRateLimiter.transfer( - 1 * 10 ** decimals, chainId2, toWormholeFormat(user_B) + 1 * 10 ** decimals, + chainId2, + toWormholeFormat(user_B), + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); uint64 s2 = nttManagerZeroRateLimiter.transfer( - 1 * 10 ** decimals, chainId2, toWormholeFormat(user_B) + 1 * 10 ** decimals, + chainId2, + toWormholeFormat(user_B), + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); uint64 s3 = nttManagerZeroRateLimiter.transfer( - 1 * 10 ** decimals, chainId2, toWormholeFormat(user_B) + 1 * 10 ** decimals, + chainId2, + toWormholeFormat(user_B), + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -139,32 +243,24 @@ contract TestNttManager is Test, IRateLimiterEvents { assertEq(s3, 2); // Test incoming transfer completes successfully with rate limit disabled - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerZeroRateLimiter); - nttManagerZeroRateLimiter.setThreshold(2); + TrimmedAmount amount = packTrimmedAmount(50, 8); + token.mintDummy(address(nttManagerZeroRateLimiterOther), amount.untrim(token.decimals())); - // register nttManager peer - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManagerZeroRateLimiter.setPeer( - TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max + TransceiverStructs.NttManagerMessage memory m = TransceiverHelpersLib.buildNttManagerMessage( + user_B, 0, chainId2, nttManagerZeroRateLimiter, amount ); + bytes memory encodedM = TransceiverStructs.encodeNttManagerMessage(m); - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - bytes memory transceiverMessage; - (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib - .buildTransceiverMessageWithNttManagerPayload( + // Attest and receive the message on the other manager. + DummyTransceiver.Message memory rmsg = TransceiverHelpersLib.attestAndReceiveMsg( + nttManagerZeroRateLimiter, + nttManagerZeroRateLimiterOther, 0, - bytes32(0), - peer, - toWormholeFormat(address(nttManagerZeroRateLimiter)), - abi.encode("payload") + transceiversOther, + encodedM ); - e1.receiveMessage(transceiverMessage); - - bytes32 hash = TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, nttManagerMessage - ); - assertEq(nttManagerZeroRateLimiter.messageAttestations(hash), 1); + checkAttestationAndExecution(nttManagerZeroRateLimiterOther, rmsg, 2); } // === ownership @@ -195,12 +291,18 @@ contract TestNttManager is Test, IRateLimiterEvents { nttManager.pause(); assertEq(nttManager.isPaused(), true); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + // When the NttManager is paused, initiating transfers, completing queued transfers on both source and destination chains, // executing transfers and attesting to transfers should all revert vm.expectRevert( abi.encodeWithSelector(PausableUpgradeable.RequireContractIsNotPaused.selector) ); - nttManager.transfer(0, 0, bytes32(0)); + nttManager.transfer( + 0, 0, bytes32(0), executorSignedQuote, executorRelayInstructions, adapterInstructions + ); vm.expectRevert( abi.encodeWithSelector(PausableUpgradeable.RequireContractIsNotPaused.selector) @@ -212,27 +314,26 @@ contract TestNttManager is Test, IRateLimiterEvents { ); nttManager.completeInboundQueuedTransfer(bytes32(0)); - vm.expectRevert( - abi.encodeWithSelector(PausableUpgradeable.RequireContractIsNotPaused.selector) - ); - TransceiverStructs.NttManagerMessage memory message; - nttManager.executeMsg(0, bytes32(0), message); + // The endpoint and transceiver are not pausable, so calling receiveMessage should still work. + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); - bytes memory transceiverMessage; - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - 0, - bytes32(0), - toWormholeFormat(address(nttManagerOther)), - toWormholeFormat(address(nttManager)), - abi.encode("payload") - ); + transceiver.receiveMessage(rmsg); + + bytes memory message = "Hello, World"; + + // But executeMsg should be paused in the NttManager. vm.expectRevert( abi.encodeWithSelector(PausableUpgradeable.RequireContractIsNotPaused.selector) ); - dummyTransceiver.receiveMessage(transceiverMessage); - - nttManager.unpause(); - assertEq(nttManager.isPaused(), false); + nttManager.executeMsg(0, UniversalAddressLibrary.fromAddress(address(0)), 0, message); } function test_pausePauserUnpauseOnlyOwner() public { @@ -262,7 +363,13 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_brokenToken() public { DummyToken t = new DummyTokenBroken(); NttManager implementation = new MockNttManagerContract( - address(t), IManagerBase.Mode.LOCKING, chainId, 1 days, false + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + chainId, + 1 days, + false ); NttManager newNttManager = @@ -270,19 +377,25 @@ contract TestNttManager is Test, IRateLimiterEvents { vm.expectRevert(abi.encodeWithSelector(INttManager.StaticcallFailed.selector)); newNttManager.initialize(); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + vm.expectRevert(abi.encodeWithSelector(INttManager.StaticcallFailed.selector)); - newNttManager.transfer(1, 1, bytes32("1")); + newNttManager.transfer( + 1, 1, bytes32("1"), executorSignedQuote, executorRelayInstructions, adapterInstructions + ); } // === transceiver registration function test_registerTransceiver() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); + DummyTransceiver e = new DummyTransceiver(chainId, address(endpoint)); nttManager.setTransceiver(address(e)); } function test_onlyOwnerCanModifyTransceivers() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); + DummyTransceiver e = new DummyTransceiver(chainId, address(endpoint)); nttManager.setTransceiver(address(e)); address notOwner = address(0x123); @@ -292,79 +405,72 @@ contract TestNttManager is Test, IRateLimiterEvents { abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, notOwner) ); nttManager.setTransceiver(address(e)); - - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, notOwner) - ); - nttManager.removeTransceiver(address(e)); } function test_cantEnableTransceiverTwice() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); + DummyTransceiver e = new DummyTransceiver(chainId, address(endpoint)); nttManager.setTransceiver(address(e)); vm.expectRevert( - abi.encodeWithSelector( - TransceiverRegistry.TransceiverAlreadyEnabled.selector, address(e) - ) + abi.encodeWithSelector(AdapterRegistry.AdapterAlreadyRegistered.selector, address(e)) ); nttManager.setTransceiver(address(e)); } function test_disableReenableTransceiver() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e)); - nttManager.removeTransceiver(address(e)); + DummyTransceiver e = new DummyTransceiver(chainId, address(endpoint)); nttManager.setTransceiver(address(e)); + nttManager.enableSendTransceiver(chainId2, address(e)); + nttManager.enableRecvTransceiver(chainId2, address(e)); + nttManager.disableSendTransceiver(chainId2, address(e)); + nttManager.disableRecvTransceiver(chainId2, address(e)); + nttManager.enableSendTransceiver(chainId2, address(e)); + nttManager.enableRecvTransceiver(chainId2, address(e)); } - function test_disableAllTransceiversFails() public { - vm.expectRevert(abi.encodeWithSelector(IManagerBase.ZeroThreshold.selector)); - nttManager.removeTransceiver(address(dummyTransceiver)); - } + // TODO: Not sure what this test should do now. + // function test_disableAllTransceiversFails() public { + // vm.expectRevert(abi.encodeWithSelector(IManagerBase.ZeroThreshold.selector)); + // nttManager.removeTransceiver(address(transceiver)); + // } function test_multipleTransceivers() public { - DummyTransceiver e1 = new DummyTransceiver(address(nttManager)); - DummyTransceiver e2 = new DummyTransceiver(address(nttManager)); + // Setup already added one transceiver for chainId2 so we'll add a couple more. + DummyTransceiver e1 = new DummyTransceiver(chainId2, address(endpoint)); + DummyTransceiver e2 = new DummyTransceiver(chainId2, address(endpoint)); nttManager.setTransceiver(address(e1)); nttManager.setTransceiver(address(e2)); } - function test_transceiverIncompatibleNttManager() public { - // Transceiver instantiation reverts if the nttManager doesn't have the proper token method - vm.expectRevert(bytes("")); - new DummyTransceiver(address(0xBEEF)); - } - - function test_transceiverWrongNttManager() public { - // TODO: this is accepted currently. should we include a check to ensure - // only transceivers whose nttManager is us can be registered? (this would be - // a convenience check, not a security one) - DummyToken t = new DummyToken(); - NttManager altNttManager = new MockNttManagerContract( - address(t), IManagerBase.Mode.LOCKING, chainId, 1 days, false - ); - DummyTransceiver e = new DummyTransceiver(address(altNttManager)); - nttManager.setTransceiver(address(e)); - } - function test_noEnabledTransceivers() public { DummyToken token = new DummyToken(); NttManager implementation = new MockNttManagerContract( - address(token), IManagerBase.Mode.LOCKING, chainId, 1 days, false + address(endpoint), + address(executor), + address(token), + IManagerBase.Mode.LOCKING, + chainId, + 1 days, + false ); MockNttManagerContract newNttManager = MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); newNttManager.initialize(); - address user_A = address(0x123); - address user_B = address(0x456); + user_A = address(0x123); + user_B = address(0x456); uint8 decimals = token.decimals(); - newNttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + newNttManager.setPeer( + chainId2, + toWormholeFormat(address(0x1)), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); newNttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -373,14 +479,20 @@ contract TestNttManager is Test, IRateLimiterEvents { token.approve(address(newNttManager), 3 * 10 ** decimals); - vm.expectRevert(abi.encodeWithSelector(IManagerBase.NoEnabledTransceivers.selector)); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + + vm.expectRevert(abi.encodeWithSelector(Endpoint.AdapterNotEnabled.selector)); newNttManager.transfer( 1 * 10 ** decimals, chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); } @@ -391,78 +503,31 @@ contract TestNttManager is Test, IRateLimiterEvents { } function test_maxOutTransceivers() public { - // Let's register a transceiver and then disable it. We now have 2 registered managers - // since we register 1 in the setup - DummyTransceiver e = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e)); - nttManager.removeTransceiver(address(e)); - - // We should be able to register 64 transceivers total - for (uint256 i = 0; i < 62; ++i) { - DummyTransceiver d = new DummyTransceiver(address(nttManager)); + // We should be able to register 128 transceivers total. We registered one in set up, so go with one less than the max. + uint256 numTransceivers = endpoint.maxAdapters() - 1; + for (uint256 i = 0; i < numTransceivers; ++i) { + DummyTransceiver d = new DummyTransceiver(chainId, address(endpoint)); nttManager.setTransceiver(address(d)); } // Registering a new transceiver should fail as we've hit the cap - DummyTransceiver c = new DummyTransceiver(address(nttManager)); - vm.expectRevert(TransceiverRegistry.TooManyTransceivers.selector); + DummyTransceiver c = new DummyTransceiver(chainId, address(endpoint)); + vm.expectRevert(AdapterRegistry.TooManyAdapters.selector); nttManager.setTransceiver(address(c)); - - // We should be able to renable an already registered transceiver at the cap - nttManager.setTransceiver(address(e)); } - function test_passingInstructionsToTransceivers() public { - // Let's register a transceiver and then disable the original transceiver. We now have 2 registered transceivers - // since we register 1 in the setup - DummyTransceiver e = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e)); - nttManager.removeTransceiver(address(dummyTransceiver)); - - address user_A = address(0x123); - address user_B = address(0x456); - + function test_cancellingOutboundQueuedTransfers() public { DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); - nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); - - token.mintDummy(address(user_A), 5 * 10 ** decimals); - - vm.startPrank(user_A); - - token.approve(address(nttManager), 3 * 10 ** decimals); - - // Pass some instructions for the enabled transceiver - TransceiverStructs.TransceiverInstruction memory transceiverInstruction = - TransceiverStructs.TransceiverInstruction({index: 1, payload: new bytes(1)}); - TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions = - new TransceiverStructs.TransceiverInstruction[](1); - transceiverInstructions[0] = transceiverInstruction; - bytes memory instructions = - TransceiverStructs.encodeTransceiverInstructions(transceiverInstructions); - - nttManager.transfer( - 1 * 10 ** decimals, + nttManager.setPeer( chainId2, - toWormholeFormat(user_B), - toWormholeFormat(user_A), - false, - instructions + toWormholeFormat(address(0x1)), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); - } - - function test_cancellingOutboundQueuedTransfers() public { - address user_A = address(0x123); - address user_B = address(0x456); - - DummyToken token = DummyToken(nttManager.token()); - - uint8 decimals = token.decimals(); - - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); nttManager.setOutboundLimit(0); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -480,7 +545,9 @@ contract TestNttManager is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), true, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -517,201 +584,159 @@ contract TestNttManager is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), true, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); assertEq(s2, s1 + 1); } - // == threshold - - function test_cantSetThresholdTooHigh() public { - // 1 transceiver set, so can't set threshold to 2 - vm.expectRevert(abi.encodeWithSelector(IManagerBase.ThresholdTooHigh.selector, 2, 1)); - nttManager.setThreshold(2); - } - - function test_canSetThreshold() public { - DummyTransceiver e1 = new DummyTransceiver(address(nttManager)); - DummyTransceiver e2 = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e1)); - nttManager.setTransceiver(address(e2)); - - nttManager.setThreshold(1); - nttManager.setThreshold(2); - nttManager.setThreshold(1); - } - - function test_cantSetThresholdToZero() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e)); - - vm.expectRevert(abi.encodeWithSelector(IManagerBase.ZeroThreshold.selector)); - nttManager.setThreshold(0); - } - - function test_onlyOwnerCanSetThreshold() public { - address notOwner = address(0x123); - vm.startPrank(notOwner); - - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, notOwner) - ); - nttManager.setThreshold(1); - } - - // == threshold - - function test_peerRegistrationLimitsCanBeUpdated() public { - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManager.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, 0); - - IRateLimiter.RateLimitParams memory params = - nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); - assertEq(params.limit.getAmount(), 0); - assertEq(params.limit.getDecimals(), 8); - - nttManager.setInboundLimit(type(uint64).max, TransceiverHelpersLib.SENDING_CHAIN_ID); - params = nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); - assertEq(params.limit.getAmount(), type(uint64).max / 10 ** (18 - 8)); - assertEq(params.limit.getDecimals(), 8); - } - // === attestation function test_onlyEnabledTransceiversCanAttest() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.removeTransceiver(address(e1)); - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); - - bytes memory transceiverMessage; - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - 0, bytes32(0), peer, toWormholeFormat(address(nttManagerOther)), abi.encode("payload") - ); - - vm.expectRevert( - abi.encodeWithSelector(TransceiverRegistry.CallerNotTransceiver.selector, address(e1)) - ); - e1.receiveMessage(transceiverMessage); + // Setup created two managers, each with one enabled transceiver pointed at the other. + + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); + + // This should work. + transceiver.receiveMessage(rmsg); + checkAttestationOnly(nttManager, rmsg, 1, 0); + + // But if we disable the transceiver for receiving, it should fail. + nttManager.disableRecvTransceiver(chainId2, address(transceiver)); + + vm.expectRevert(abi.encodeWithSelector(Endpoint.AdapterNotEnabled.selector)); + transceiver.receiveMessage(rmsg); } function test_onlyPeerNttManagerCanAttest() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.setThreshold(2); - - bytes32 peer = toWormholeFormat(address(nttManager)); - - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - bytes memory transceiverMessage; - (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib - .buildTransceiverMessageWithNttManagerPayload( - 0, bytes32(0), peer, toWormholeFormat(address(nttManagerOther)), abi.encode("payload") - ); - + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(0xdeadbeef)), // This is not the peer NttManager. + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); + + // The endpoint and transceiver don't block this, so this should succeed. + transceiver.receiveMessage(rmsg); + checkAttestationOnly(nttManager, rmsg, 1, 0); + + // But the call to executeMsg should check the peer. + bytes memory message = "Hello, World"; vm.expectRevert( abi.encodeWithSelector( - INttManager.InvalidPeer.selector, TransceiverHelpersLib.SENDING_CHAIN_ID, peer + INttManager.InvalidPeer.selector, + chainId2, + UniversalAddressLibrary.fromAddress(address(0xdeadbeef)).toBytes32() ) ); - e1.receiveMessage(transceiverMessage); - } - - function test_attestSimple() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.setThreshold(2); - - // register nttManager peer - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); - - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - bytes memory transceiverMessage; - (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib - .buildTransceiverMessageWithNttManagerPayload( - 0, bytes32(0), peer, toWormholeFormat(address(nttManagerOther)), abi.encode("payload") + nttManager.executeMsg( + chainId2, UniversalAddressLibrary.fromAddress(address(0xdeadbeef)), 0, message ); - - e1.receiveMessage(transceiverMessage); - - bytes32 hash = TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, nttManagerMessage - ); - assertEq(nttManagerOther.messageAttestations(hash), 1); } - function test_attestTwice() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.setThreshold(2); - - // register nttManager peer - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); - - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - bytes memory transceiverMessage; - (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib - .buildTransceiverMessageWithNttManagerPayload( - 0, bytes32(0), peer, toWormholeFormat(address(nttManagerOther)), abi.encode("payload") - ); - - bytes32 hash = TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, nttManagerMessage - ); - - e1.receiveMessage(transceiverMessage); - vm.expectRevert( - abi.encodeWithSelector(IManagerBase.TransceiverAlreadyAttestedToMessage.selector, hash) - ); - e1.receiveMessage(transceiverMessage); - - // can't double vote - assertEq(nttManagerOther.messageAttestations(hash), 1); + function test_attestSimple() public { + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); + + // Attest the message. This should work. + transceiver.receiveMessage(rmsg); + checkAttestationOnly(nttManager, rmsg, 1, 0); + + // Can't attest the same message twice. + vm.expectRevert(abi.encodeWithSelector(Endpoint.DuplicateMessageAttestation.selector)); + transceiver.receiveMessage(rmsg); + + // Can't attest when the transceiver is disabled. + nttManager.disableRecvTransceiver(chainId2, address(transceiver)); + + rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 1, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); + + vm.expectRevert(abi.encodeWithSelector(Endpoint.AdapterNotEnabled.selector)); + transceiver.receiveMessage(rmsg); } - function test_attestDisabled() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.setThreshold(2); - - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); - - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](1); - transceivers[0] = e1; - - TransceiverStructs.NttManagerMessage memory m; - (m,) = TransceiverHelpersLib.attestTransceiversHelper( - address(0x456), - 0, - chainId, - nttManager, - nttManagerOther, - packTrimmedAmount(50, 8), - packTrimmedAmount(type(uint64).max, 8), - transceivers + function test_executeWhenUnderThresholdShouldRevert() public { + DummyTransceiver[] memory transceivers = new DummyTransceiver[](2); + (transceivers[0], transceivers[1]) = + TransceiverHelpersLib.addTransceiver(nttManager, transceiver, chainId2); + + nttManager.setThreshold(chainId2, 2); + + bytes memory payload = "Hello, World"; + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256(payload), + refundAddr: address(user_A) + }); + + // Attest the message. This should work. + transceiver.receiveMessage(rmsg); + + // The attestation should've been counted. + require( + 1 + == nttManager.messageAttestations( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ), + "Message did not attest" + ); + + // But the message should not yet be approved. + require( + !nttManager.isMessageApproved( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ) ); - nttManagerOther.removeTransceiver(address(e1)); - - bytes32 hash = - TransceiverStructs.nttManagerMessageDigest(TransceiverHelpersLib.SENDING_CHAIN_ID, m); - // a disabled transceiver's vote no longer counts - assertEq(nttManagerOther.messageAttestations(hash), 0); - - nttManagerOther.setTransceiver(address(e1)); - // it counts again when reenabled - assertEq(nttManagerOther.messageAttestations(hash), 1); + // Execute should revert because we haven't met the threshold yet. + vm.expectRevert(abi.encodeWithSelector(INttManager.ThresholdNotMet.selector, 2, 1)); + nttManager.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, payload); } function test_transfer_sequences() public { - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManager.setPeer( + chainId2, + toWormholeFormat(address(0x1)), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -726,7 +751,9 @@ contract TestNttManager is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); uint64 s2 = nttManager.transfer( 1 * 10 ** decimals, @@ -734,7 +761,9 @@ contract TestNttManager is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); uint64 s3 = nttManager.transfer( 1 * 10 ** decimals, @@ -742,7 +771,9 @@ contract TestNttManager is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); assertEq(s1, 0); @@ -752,10 +783,14 @@ 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.setPeer(chainId2, toWormholeFormat(address(0x1)), 6, type(uint64).max); + nttManager.setPeer( + chainId2, + toWormholeFormat(address(0x1)), + 6, + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); - address user_A = address(0x123); - address user_B = address(0x456); DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); assertEq(decimals, 18); @@ -771,6 +806,10 @@ contract TestNttManager is Test, IRateLimiterEvents { uint256 amount = type(uint64).max * 10 ** (decimals - 6); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + vm.expectRevert("SafeCast: value doesn't fit in 64 bits"); nttManager.transfer( amount, @@ -778,7 +817,9 @@ contract TestNttManager is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); // A (slightly) more sensible amount should work normally @@ -789,75 +830,44 @@ contract TestNttManager is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); } - function test_attestationQuorum() public { - address user_B = address(0x456); - - (DummyTransceiver e1, DummyTransceiver e2) = - TransceiverHelpersLib.setup_transceivers(nttManagerOther); - + function test_alreadyExecuted() public { TrimmedAmount transferAmount = packTrimmedAmount(50, 8); + DummyTransceiver[] memory transceiversOther = new DummyTransceiver[](2); + (transceiversOther[0], transceiversOther[1]) = + TransceiverHelpersLib.addTransceiver(nttManagerOther, transceiverOther, chainId); + TransceiverStructs.NttManagerMessage memory m; - bytes memory encodedEm; - { - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](2); - transceivers[0] = e1; - transceivers[1] = e2; - - TransceiverStructs.TransceiverMessage memory em; - (m, em) = TransceiverHelpersLib.attestTransceiversHelper( - user_B, - 0, - chainId, - nttManager, - nttManagerOther, - transferAmount, - packTrimmedAmount(type(uint64).max, 8), - transceivers - ); - encodedEm = TransceiverStructs.encodeTransceiverMessage( - TransceiverHelpersLib.TEST_TRANSCEIVER_PAYLOAD_PREFIX, em - ); - } + DummyTransceiver.Message memory rmsg; + (m, rmsg) = TransceiverHelpersLib.transferAttestAndReceive( + user_B, + 0, + nttManager, + nttManagerOther, + transferAmount, + packTrimmedAmount(type(uint64).max, 8), + transceiversOther + ); - { - DummyToken token = DummyToken(nttManager.token()); - assertEq(token.balanceOf(address(user_B)), transferAmount.untrim(token.decimals())); - } + checkAttestationAndExecution(nttManagerOther, rmsg, 2); - // replay protection for transceiver - vm.recordLogs(); - vm.expectRevert( - abi.encodeWithSelector( - IManagerBase.TransceiverAlreadyAttestedToMessage.selector, - TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, m - ) - ) - ); - e2.receiveMessage(encodedEm); + // Replay protection should revert. + vm.expectRevert(abi.encodeWithSelector(Endpoint.DuplicateMessageAttestation.selector)); + transceiversOther[0].receiveMessage(rmsg); } function test_transfersOnForkedChains() public { uint256 evmChainId = block.chainid; - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); - uint8 decimals = token.decimals(); - nttManager.setPeer( - TransceiverHelpersLib.SENDING_CHAIN_ID, - toWormholeFormat(address(nttManagerOther)), - 9, - type(uint64).max - ); nttManager.setOutboundLimit(0); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -868,13 +878,19 @@ contract TestNttManager is Test, IRateLimiterEvents { uint64 sequence = nttManager.transfer( 1 * 10 ** decimals, - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), - true, - new bytes(1) + true, // Should queue + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); + // We should have enqueued message zero and not have sent anything out. + assertEq(sequence, 0); + require(0 == transceiver.getMessages().length, "Should not have sent a message out"); + vm.warp(vm.getBlockTimestamp() + 1 days); vm.chainId(chainId); @@ -887,15 +903,21 @@ contract TestNttManager is Test, IRateLimiterEvents { vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, evmChainId, chainId)); nttManager.cancelOutboundQueuedTransfer(sequence); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + // Outbound transfers fail when queued vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, evmChainId, chainId)); nttManager.transfer( 1 * 10 ** decimals, - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), - true, - new bytes(1) + true, // Should queue + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); vm.stopPrank(); @@ -905,11 +927,13 @@ contract TestNttManager is Test, IRateLimiterEvents { vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, evmChainId, chainId)); nttManager.transfer( 1 * 10 ** decimals, - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); // INBOUND @@ -924,35 +948,42 @@ contract TestNttManager is Test, IRateLimiterEvents { }) ); - bytes memory transceiverMessage; - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib - .buildTransceiverMessageWithNttManagerPayload( - 0, - toWormholeFormat(address(0x1)), - toWormholeFormat(address(nttManagerOther)), - toWormholeFormat(address(nttManager)), - tokenTransferMessage + TransceiverStructs.NttManagerMessage memory m = TransceiverStructs.NttManagerMessage( + 0, toWormholeFormat(address(0x1)), tokenTransferMessage ); + bytes memory nttManagerMessage = TransceiverStructs.encodeNttManagerMessage(m); + + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256(nttManagerMessage), + refundAddr: address(user_A) + }); + + // The endpoint doesn't do fork detection so the attestation will succeed. + transceiver.receiveMessage(rmsg); - // Inbound transfers can't be completed + // But the execute should fail. vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, evmChainId, chainId)); - dummyTransceiver.receiveMessage(transceiverMessage); + nttManager.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage); // Inbound queued transfers can't be completed - nttManager.setInboundLimit(0, TransceiverHelpersLib.SENDING_CHAIN_ID); + nttManager.setInboundLimit(0, chainId2); vm.chainId(evmChainId); - bytes32 hash = TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, nttManagerMessage - ); - dummyTransceiver.receiveMessage(transceiverMessage); + rmsg.sequence = 1; // Update the endpoint sequence number so we don't get duplicate attestation. + transceiver.receiveMessage(rmsg); + nttManager.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage); vm.chainId(chainId); vm.warp(vm.getBlockTimestamp() + 1 days); + bytes32 hash = TransceiverStructs.nttManagerMessageDigest(chainId2, m); vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, evmChainId, chainId)); nttManager.completeInboundQueuedTransfer(hash); } @@ -969,8 +1000,15 @@ contract TestNttManager is Test, IRateLimiterEvents { function test_noAutomaticSlot() public { DummyToken t = new DummyToken(); - MockNttManagerContract c = - new MockNttManagerContract(address(t), IManagerBase.Mode.LOCKING, 1, 1 days, false); + MockNttManagerContract c = new MockNttManagerContract( + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + 1, + 1 days, + false + ); assertEq(c.lastSlot(), 0x0); } @@ -979,7 +1017,15 @@ contract TestNttManager is Test, IRateLimiterEvents { vm.startStateDiffRecording(); - new MockNttManagerContract(address(t), IManagerBase.Mode.LOCKING, 1, 1 days, false); + new MockNttManagerContract( + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + 1, + 1 days, + false + ); Utils.assertSafeUpgradeableConstructor(vm.stopAndReturnStateDiff()); } @@ -992,28 +1038,40 @@ contract TestNttManager is Test, IRateLimiterEvents { address to = address(0x456); DummyToken token = DummyToken(nttManager.token()); - uint8 decimals = token.decimals(); uint256 maxAmount = 5 * 10 ** decimals; token.mintDummy(from, maxAmount); - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManager.setPeer( + chainId2, + toWormholeFormat(address(0x1)), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); nttManager.setInboundLimit( - packTrimmedAmount(type(uint64).max, 8).untrim(decimals), - TransceiverHelpersLib.SENDING_CHAIN_ID + packTrimmedAmount(type(uint64).max, 8).untrim(decimals), nttManagerOther.chainId() ); vm.startPrank(from); - uint256 transferAmount = 3 * 10 ** decimals; - assertEq( - transferAmount < maxAmount - 500, true, "Transferring more tokens than what exists" - ); + uint256 amountWithDust; + uint256 dustAmount; + { + uint256 transferAmount = 3 * 10 ** decimals; + assertEq( + transferAmount < maxAmount - 500, true, "Transferring more tokens than what exists" + ); + + dustAmount = 500; + amountWithDust = transferAmount + dustAmount; // An amount with 19 digits, which will result in dust due to 18 decimals + token.approve(address(nttManager), amountWithDust); + } - uint256 dustAmount = 500; - uint256 amountWithDust = transferAmount + dustAmount; // An amount with 19 digits, which will result in dust due to 18 decimals - token.approve(address(nttManager), amountWithDust); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); vm.expectRevert( abi.encodeWithSelector( @@ -1026,7 +1084,9 @@ contract TestNttManager is Test, IRateLimiterEvents { toWormholeFormat(to), toWormholeFormat(from), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); vm.stopPrank(); @@ -1054,48 +1114,45 @@ contract TestNttManager is Test, IRateLimiterEvents { // transceivers) and receive a message through it. // This ensures that the storage slots don't get clobbered through the upgrades. - address user_B = address(0x456); DummyToken token = DummyToken(nttManager.token()); TrimmedAmount transferAmount = packTrimmedAmount(50, 8); - (ITransceiverReceiver e1, ITransceiverReceiver e2) = - TransceiverHelpersLib.setup_transceivers(nttManagerOther); // Step 1 (contract is deployed by setUp()) - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](2); - transceivers[0] = e1; - transceivers[1] = e2; + DummyTransceiver[] memory transceivers = new DummyTransceiver[](2); + (transceivers[0], transceivers[1]) = + TransceiverHelpersLib.addTransceiver(nttManagerOther, transceiverOther, chainId); TransceiverStructs.NttManagerMessage memory m; - bytes memory encodedEm; - { - TransceiverStructs.TransceiverMessage memory em; - (m, em) = TransceiverHelpersLib.attestTransceiversHelper( - user_B, - 0, - chainId, - nttManager, - nttManagerOther, - transferAmount, - packTrimmedAmount(type(uint64).max, 8), - transceivers - ); - encodedEm = TransceiverStructs.encodeTransceiverMessage( - TransceiverHelpersLib.TEST_TRANSCEIVER_PAYLOAD_PREFIX, em - ); - } + DummyTransceiver.Message memory rmsg; + (m, rmsg) = TransceiverHelpersLib.transferAttestAndReceive( + user_B, + 0, + nttManager, + nttManagerOther, + transferAmount, + packTrimmedAmount(type(uint64).max, 8), + transceivers + ); + checkAttestationAndExecution(nttManagerOther, rmsg, 2); assertEq(token.balanceOf(address(user_B)), transferAmount.untrim(token.decimals())); // Step 2 (upgrade to a new nttManager) MockNttManagerContract newNttManager = new MockNttManagerContract( - nttManager.token(), IManagerBase.Mode.LOCKING, chainId, 1 days, false + address(nttManagerOther.endpoint()), + address(nttManagerOther.executor()), + nttManagerOther.token(), + nttManagerOther.mode(), + nttManagerOther.chainId(), + 1 days, + false ); + nttManagerOther.upgrade(address(newNttManager)); - TransceiverHelpersLib.attestTransceiversHelper( + (m, rmsg) = TransceiverHelpersLib.transferAttestAndReceive( user_B, bytes32(uint256(1)), - chainId, nttManager, // this is the proxy nttManagerOther, // this is the proxy transferAmount, @@ -1103,6 +1160,7 @@ contract TestNttManager is Test, IRateLimiterEvents { transceivers ); + checkAttestationAndExecution(nttManagerOther, rmsg, 2); assertEq(token.balanceOf(address(user_B)), transferAmount.untrim(token.decimals()) * 2); } @@ -1114,7 +1172,13 @@ contract TestNttManager is Test, IRateLimiterEvents { DummyTokenMintAndBurn(address(new ERC1967Proxy(address(dummy1), ""))); NttManager implementation = new MockNttManagerContract( - address(t), IManagerBase.Mode.LOCKING, chainId, 1 days, false + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + chainId, + 1 days, + false ); MockNttManagerContract newNttManager = @@ -1123,14 +1187,12 @@ contract TestNttManager is Test, IRateLimiterEvents { // register nttManager peer and transceiver bytes32 peer = toWormholeFormat(address(nttManager)); - newNttManager.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); - { - DummyTransceiver e = new DummyTransceiver(address(newNttManager)); - newNttManager.setTransceiver(address(e)); - } + newNttManager.setPeer(chainId2, peer, 9, NttManagerHelpersLib.gasLimit, type(uint64).max); + DummyTransceiver e1 = new DummyTransceiver(chainId, address(endpoint)); + newNttManager.setTransceiver(address(e1)); + newNttManager.enableSendTransceiver(chainId2, address(e1)); + newNttManager.enableRecvTransceiver(chainId2, address(e1)); - address user_A = address(0x123); - address user_B = address(0x456); t.mintDummy(address(user_A), 5 * 10 ** t.decimals()); // Check that we can initiate a transfer @@ -1138,19 +1200,17 @@ contract TestNttManager is Test, IRateLimiterEvents { t.approve(address(newNttManager), 3 * 10 ** t.decimals()); newNttManager.transfer( 1 * 10 ** t.decimals(), - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); // Check that we can receive a transfer - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(newNttManager); - newNttManager.setThreshold(1); - - bytes memory transceiverMessage; bytes memory tokenTransferMessage; TrimmedAmount transferAmount = packTrimmedAmount(100, 8); @@ -1165,11 +1225,25 @@ contract TestNttManager is Test, IRateLimiterEvents { }) ); - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - 0, bytes32(0), peer, toWormholeFormat(address(newNttManager)), tokenTransferMessage + TransceiverStructs.NttManagerMessage memory m = TransceiverStructs.NttManagerMessage( + 0, toWormholeFormat(address(0x1)), tokenTransferMessage ); + bytes memory nttManagerMessage = TransceiverStructs.encodeNttManagerMessage(m); + + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(newNttManager)), + payloadHash: keccak256(nttManagerMessage), + refundAddr: address(user_A) + }); + + // The endpoint doesn't do fork detection so the attestation will succeed. + e1.receiveMessage(rmsg); + newNttManager.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage); - e1.receiveMessage(transceiverMessage); uint256 userBBalanceBefore = t.balanceOf(address(user_B)); assertEq(userBBalanceBefore, transferAmount.untrim(t.decimals())); @@ -1179,23 +1253,27 @@ contract TestNttManager is Test, IRateLimiterEvents { vm.startPrank(user_A); newNttManager.transfer( - 1 * 10 ** 10, - TransceiverHelpersLib.SENDING_CHAIN_ID, + 1 * 10 ** t.decimals(), + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - bytes32("1"), - bytes32(0), - peer, - toWormholeFormat(address(newNttManager)), - tokenTransferMessage + m = TransceiverStructs.NttManagerMessage( + bytes32("1"), toWormholeFormat(address(0x1)), tokenTransferMessage ); - e1.receiveMessage(transceiverMessage); + nttManagerMessage = TransceiverStructs.encodeNttManagerMessage(m); + + rmsg.sequence++; + rmsg.payloadHash = keccak256(nttManagerMessage); + e1.receiveMessage(rmsg); + newNttManager.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage); + assertEq( t.balanceOf(address(user_B)), userBBalanceBefore + transferAmount.untrim(t.decimals()) ); @@ -1204,64 +1282,151 @@ contract TestNttManager is Test, IRateLimiterEvents { DummyTokenDifferentDecimals dummy3 = new DummyTokenDifferentDecimals(7); // 7 is 7 trimmed t.upgrade(address(dummy3)); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + vm.startPrank(user_A); vm.expectRevert(abi.encodeWithSelector(NumberOfDecimalsNotEqual.selector, 8, 7)); newNttManager.transfer( 1 * 10 ** 7, - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); vm.stopPrank(); - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - bytes32("2"), - bytes32(0), - peer, - toWormholeFormat(address(newNttManager)), - tokenTransferMessage + m = TransceiverStructs.NttManagerMessage( + bytes32("2"), toWormholeFormat(address(0x1)), tokenTransferMessage ); + nttManagerMessage = TransceiverStructs.encodeNttManagerMessage(m); + + rmsg.sequence++; + rmsg.payloadHash = keccak256(nttManagerMessage); + e1.receiveMessage(rmsg); vm.expectRevert(abi.encodeWithSelector(NumberOfDecimalsNotEqual.selector, 8, 7)); - e1.receiveMessage(transceiverMessage); + newNttManager.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage); } - function test_transferWithInstructionIndexOutOfBounds() public { - TransceiverStructs.TransceiverInstruction memory TransceiverInstruction = - TransceiverStructs.TransceiverInstruction({index: 100, payload: new bytes(1)}); - TransceiverStructs.TransceiverInstruction[] memory TransceiverInstructions = - new TransceiverStructs.TransceiverInstruction[](1); - TransceiverInstructions[0] = TransceiverInstruction; - bytes memory encodedInstructions = - TransceiverStructs.encodeTransceiverInstructions(TransceiverInstructions); - - address user_A = address(0x123); - address user_B = address(0x456); + function test_cantSetGasLimitToZero() public { + assertEq(NttManagerHelpersLib.gasLimit, nttManager.getPeer(chainId2).gasLimit); + + // Can't set the gas limit to zero directly. + vm.expectRevert(abi.encodeWithSelector(INttManager.InvalidGasLimitZero.selector, chainId2)); + nttManager.setGasLimit(chainId2, 0); + assertEq(NttManagerHelpersLib.gasLimit, nttManager.getPeer(chainId2).gasLimit); + + // Can't specify a gas limit of zero when setting the peer. + bytes32 peer = toWormholeFormat(address(nttManagerOther)); + uint8 decimals = DummyToken(nttManagerOther.token()).decimals(); + vm.expectRevert(abi.encodeWithSelector(INttManager.InvalidGasLimitZero.selector, chainId2)); + nttManager.setPeer(chainId2, peer, decimals, 0, type(uint64).max); + assertEq(NttManagerHelpersLib.gasLimit, nttManager.getPeer(chainId2).gasLimit); + + // Can't set the gas limit if the peer is not already set. + vm.expectRevert(abi.encodeWithSelector(INttManager.InvalidPeerZeroAddress.selector)); + nttManager.setGasLimit(chainId2 + 1, NttManagerHelpersLib.gasLimit); + assertEq(0, nttManager.getPeer(chainId2 + 1).gasLimit); + + // Can update the gas on an existing peer. + nttManager.setGasLimit(chainId2, NttManagerHelpersLib.gasLimit - 100000); + assertEq(NttManagerHelpersLib.gasLimit - 100000, nttManager.getPeer(chainId2).gasLimit); + } + function test_cantTransferWithZeroGasLimit() public { DummyToken token = DummyToken(nttManager.token()); + uint256 amount = 1 * 10 ** token.decimals(); + uint256 limit = 5 * amount; - uint8 decimals = token.decimals(); + token.mintDummy(address(user_A), limit); - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); - nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); - - token.mintDummy(address(user_A), 5 * 10 ** decimals); + nttManager.setGasLimitToZero(nttManagerOther.chainId()); vm.startPrank(user_A); + token.approve(address(nttManager), amount); - token.approve(address(nttManager), 3 * 10 ** decimals); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); - vm.expectRevert( - abi.encodeWithSelector(TransceiverStructs.InvalidInstructionIndex.selector, 100, 1) - ); + vm.expectRevert(abi.encodeWithSelector(INttManager.InvalidGasLimitZero.selector, chainId2)); nttManager.transfer( - 1 * 10 ** decimals, + amount, chainId2, toWormholeFormat(user_B), - toWormholeFormat(user_A), - false, - encodedInstructions + executorSignedQuote, + executorRelayInstructions, + adapterInstructions + ); + } + + function checkAttestationOnly( + NttManager nttm, + DummyTransceiver.Message memory rmsg, + uint8 expectedAttestations, + uint8 transceiverIdx + ) public view { + // Verify that it shows as attested. + require( + expectedAttestations + == nttm.messageAttestations( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ), + "Message did not attest" + ); + + // Verify that the right transceiver attested. + require( + nttm.transceiverAttestedToMessage( + rmsg.srcChain, + rmsg.srcAddr, + rmsg.sequence, + rmsg.dstAddr, + rmsg.payloadHash, + transceiverIdx + ), + "Transceiver did not attest to message" + ); + + require( + nttm.isMessageApproved( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ) + ); + + // But the message should not be marked as executed. + require( + !nttm.isMessageExecuted( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ), + "Message should not be marked executed yet" + ); + } + + error WrongNumberOfAttestations(uint8 expected, uint8 actual); + + function checkAttestationAndExecution( + NttManager nttm, + DummyTransceiver.Message memory rmsg, + uint8 expectedAttestations + ) public view { + // Verify that it shows as attested. + uint8 actual = nttm.messageAttestations( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ); + if (actual != expectedAttestations) { + revert WrongNumberOfAttestations(expectedAttestations, actual); + } + + require( + nttManagerOther.isMessageExecuted( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ), + "Message should be marked executed yet" ); } } diff --git a/evm/test/NttManagerNoRateLimiting.t.sol b/evm/test/NttManagerNoRateLimiting.t.sol index 9df4d4d80..a5525547d 100644 --- a/evm/test/NttManagerNoRateLimiting.t.sol +++ b/evm/test/NttManagerNoRateLimiting.t.sol @@ -9,7 +9,7 @@ import "../src/interfaces/INttManager.sol"; import "../src/interfaces/IRateLimiter.sol"; import "../src/interfaces/IManagerBase.sol"; import "../src/interfaces/IRateLimiterEvents.sol"; -import "../src/NttManager/TransceiverRegistry.sol"; +import "../src/libraries/external/OwnableUpgradeable.sol"; import "../src/libraries/PausableUpgradeable.sol"; import "../src/libraries/TransceiverHelpers.sol"; import {Utils} from "./libraries/Utils.sol"; @@ -19,18 +19,25 @@ 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 "example-messaging-endpoint/evm/src/AdapterRegistry.sol"; import "./libraries/TransceiverHelpers.sol"; import "./libraries/NttManagerHelpers.sol"; -import "./interfaces/ITransceiverReceiver.sol"; import "./mocks/DummyTransceiver.sol"; import "../src/mocks/DummyToken.sol"; import "./mocks/MockNttManager.sol"; +import "./mocks/MockEndpoint.sol"; +import "./mocks/MockExecutor.sol"; // TODO: set this up so the common functionality tests can be run against both -contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { +contract TestNoRateLimitingNttManager is Test, IRateLimiterEvents { MockNttManagerNoRateLimitingContract nttManager; MockNttManagerNoRateLimitingContract nttManagerOther; - MockNttManagerNoRateLimitingContract nttManagerZeroRateLimiter; + MockEndpoint endpoint; + MockEndpoint endpointOther; + MockExecutor executor; + MockExecutor executorOther; + DummyTransceiver transceiver; + DummyTransceiver transceiverOther; using TrimmedAmountLib for uint256; using TrimmedAmountLib for TrimmedAmount; @@ -38,11 +45,14 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { // 0x99'E''T''T' uint16 constant chainId = 7; uint16 constant chainId2 = 8; + + address user_A = address(0x123); + address user_B = address(0x456); + uint256 constant DEVNET_GUARDIAN_PK = 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; WormholeSimulator guardian; uint256 initialBlockTimestamp; - DummyTransceiver dummyTransceiver; function setUp() public { string memory url = "https://ethereum-sepolia-rpc.publicnode.com"; @@ -52,12 +62,24 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK); + endpoint = new MockEndpoint(chainId); + endpointOther = new MockEndpoint(chainId2); + + executor = new MockExecutor(chainId); + executorOther = new MockExecutor(chainId2); + DummyToken t = new DummyToken(); - NttManagerNoRateLimiting implementation = - new MockNttManagerNoRateLimitingContract(address(t), IManagerBase.Mode.LOCKING, chainId); + NttManagerNoRateLimiting implementation = new MockNttManagerNoRateLimitingContract( + address(endpoint), address(executor), address(t), IManagerBase.Mode.LOCKING, chainId + ); - NttManagerNoRateLimiting otherImplementation = - new MockNttManagerNoRateLimitingContract(address(t), IManagerBase.Mode.LOCKING, chainId); + NttManagerNoRateLimiting otherImplementation = new MockNttManagerNoRateLimitingContract( + address(endpointOther), + address(executorOther), + address(t), + IManagerBase.Mode.LOCKING, + chainId2 + ); nttManager = MockNttManagerNoRateLimitingContract( address(new ERC1967Proxy(address(implementation), "")) @@ -69,31 +91,34 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { ); nttManagerOther.initialize(); - dummyTransceiver = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(dummyTransceiver)); - } - - // === pure unit tests + nttManager.setPeer( + chainId2, + toWormholeFormat(address(nttManagerOther)), + t.decimals(), + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); - // naive implementation of countSetBits to test against - function simpleCount( - uint64 n - ) public pure returns (uint8) { - uint8 count; + nttManagerOther.setPeer( + chainId, + toWormholeFormat(address(nttManager)), + t.decimals(), + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); - while (n > 0) { - count += uint8(n & 1); - n >>= 1; - } + transceiver = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(transceiver)); + nttManager.enableSendTransceiver(chainId2, address(transceiver)); + nttManager.enableRecvTransceiver(chainId2, address(transceiver)); - return count; + transceiverOther = new DummyTransceiver(chainId2, address(endpointOther)); + nttManagerOther.setTransceiver(address(transceiverOther)); + nttManagerOther.enableSendTransceiver(chainId, address(transceiverOther)); + nttManagerOther.enableRecvTransceiver(chainId, address(transceiverOther)); } - function testFuzz_countSetBits( - uint64 n - ) public { - assertEq(simpleCount(n), countSetBits(n)); - } + function test_setUp() public {} // === ownership @@ -123,12 +148,18 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { nttManager.pause(); assertEq(nttManager.isPaused(), true); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + // When the NttManagerNoRateLimiting is paused, initiating transfers, completing queued transfers on both source and destination chains, // executing transfers and attesting to transfers should all revert vm.expectRevert( abi.encodeWithSelector(PausableUpgradeable.RequireContractIsNotPaused.selector) ); - nttManager.transfer(0, 0, bytes32(0)); + nttManager.transfer( + 0, 0, bytes32(0), executorSignedQuote, executorRelayInstructions, adapterInstructions + ); vm.expectRevert( abi.encodeWithSelector(PausableUpgradeable.RequireContractIsNotPaused.selector) @@ -140,27 +171,26 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { ); nttManager.completeInboundQueuedTransfer(bytes32(0)); - vm.expectRevert( - abi.encodeWithSelector(PausableUpgradeable.RequireContractIsNotPaused.selector) - ); - TransceiverStructs.NttManagerMessage memory message; - nttManager.executeMsg(0, bytes32(0), message); + // The endpoint and transceiver are not pausable, so calling receiveMessage should still work. + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); - bytes memory transceiverMessage; - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - 0, - bytes32(0), - toWormholeFormat(address(nttManagerOther)), - toWormholeFormat(address(nttManager)), - abi.encode("payload") - ); + transceiver.receiveMessage(rmsg); + + bytes memory message = "Hello, World"; + + // But executeMsg should be paused in the NttManagerNoRateLimiting. vm.expectRevert( abi.encodeWithSelector(PausableUpgradeable.RequireContractIsNotPaused.selector) ); - dummyTransceiver.receiveMessage(transceiverMessage); - - nttManager.unpause(); - assertEq(nttManager.isPaused(), false); + nttManager.executeMsg(0, UniversalAddressLibrary.fromAddress(address(0)), 0, message); } function test_pausePauserUnpauseOnlyOwner() public { @@ -189,8 +219,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { // === deployment with invalid token function test_brokenToken() public { DummyToken t = new DummyTokenBroken(); - NttManagerNoRateLimiting implementation = - new MockNttManagerNoRateLimitingContract(address(t), IManagerBase.Mode.LOCKING, chainId); + NttManagerNoRateLimiting implementation = new MockNttManagerNoRateLimitingContract( + address(endpoint), address(executor), address(t), IManagerBase.Mode.LOCKING, chainId + ); NttManagerNoRateLimiting newNttManagerNoRateLimiting = MockNttManagerNoRateLimitingContract( address(new ERC1967Proxy(address(implementation), "")) @@ -198,19 +229,25 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { vm.expectRevert(abi.encodeWithSelector(INttManager.StaticcallFailed.selector)); newNttManagerNoRateLimiting.initialize(); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + vm.expectRevert(abi.encodeWithSelector(INttManager.StaticcallFailed.selector)); - newNttManagerNoRateLimiting.transfer(1, 1, bytes32("1")); + newNttManagerNoRateLimiting.transfer( + 1, 1, bytes32("1"), executorSignedQuote, executorRelayInstructions, adapterInstructions + ); } // === transceiver registration function test_registerTransceiver() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); + DummyTransceiver e = new DummyTransceiver(chainId, address(endpoint)); nttManager.setTransceiver(address(e)); } function test_onlyOwnerCanModifyTransceivers() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); + DummyTransceiver e = new DummyTransceiver(chainId, address(endpoint)); nttManager.setTransceiver(address(e)); address notOwner = address(0x123); @@ -220,79 +257,69 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, notOwner) ); nttManager.setTransceiver(address(e)); - - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, notOwner) - ); - nttManager.removeTransceiver(address(e)); } function test_cantEnableTransceiverTwice() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); + DummyTransceiver e = new DummyTransceiver(chainId, address(endpoint)); nttManager.setTransceiver(address(e)); vm.expectRevert( - abi.encodeWithSelector( - TransceiverRegistry.TransceiverAlreadyEnabled.selector, address(e) - ) + abi.encodeWithSelector(AdapterRegistry.AdapterAlreadyRegistered.selector, address(e)) ); nttManager.setTransceiver(address(e)); } function test_disableReenableTransceiver() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e)); - nttManager.removeTransceiver(address(e)); + DummyTransceiver e = new DummyTransceiver(chainId, address(endpoint)); nttManager.setTransceiver(address(e)); + nttManager.enableSendTransceiver(chainId2, address(e)); + nttManager.enableRecvTransceiver(chainId2, address(e)); + nttManager.disableSendTransceiver(chainId2, address(e)); + nttManager.disableRecvTransceiver(chainId2, address(e)); + nttManager.enableSendTransceiver(chainId2, address(e)); + nttManager.enableRecvTransceiver(chainId2, address(e)); } - function test_disableAllTransceiversFails() public { - vm.expectRevert(abi.encodeWithSelector(IManagerBase.ZeroThreshold.selector)); - nttManager.removeTransceiver(address(dummyTransceiver)); - } + // TODO: Not sure what this test should do now. + // function test_disableAllTransceiversFails() public { + // vm.expectRevert(abi.encodeWithSelector(IManagerBase.ZeroThreshold.selector)); + // nttManager.removeTransceiver(address(transceiver)); + // } function test_multipleTransceivers() public { - DummyTransceiver e1 = new DummyTransceiver(address(nttManager)); - DummyTransceiver e2 = new DummyTransceiver(address(nttManager)); + // Setup already added one transceiver for chainId2 so we'll add a couple more. + DummyTransceiver e1 = new DummyTransceiver(chainId2, address(endpoint)); + DummyTransceiver e2 = new DummyTransceiver(chainId2, address(endpoint)); nttManager.setTransceiver(address(e1)); nttManager.setTransceiver(address(e2)); } - function test_transceiverIncompatibleNttManagerNoRateLimiting() public { - // Transceiver instantiation reverts if the nttManager doesn't have the proper token method - vm.expectRevert(bytes("")); - new DummyTransceiver(address(0xBEEF)); - } - - function test_transceiverWrongNttManagerNoRateLimiting() public { - // TODO: this is accepted currently. should we include a check to ensure - // only transceivers whose nttManager is us can be registered? (this would be - // a convenience check, not a security one) - DummyToken t = new DummyToken(); - NttManagerNoRateLimiting altNttManagerNoRateLimiting = - new MockNttManagerNoRateLimitingContract(address(t), IManagerBase.Mode.LOCKING, chainId); - DummyTransceiver e = new DummyTransceiver(address(altNttManagerNoRateLimiting)); - nttManager.setTransceiver(address(e)); - } - function test_noEnabledTransceivers() public { DummyToken token = new DummyToken(); NttManagerNoRateLimiting implementation = new MockNttManagerNoRateLimitingContract( - address(token), IManagerBase.Mode.LOCKING, chainId + address(endpoint), + address(executorOther), + address(token), + IManagerBase.Mode.LOCKING, + chainId ); MockNttManagerNoRateLimitingContract newNttManagerNoRateLimiting = MockNttManagerNoRateLimitingContract(address(new ERC1967Proxy(address(implementation), ""))); newNttManagerNoRateLimiting.initialize(); - address user_A = address(0x123); - address user_B = address(0x456); + user_A = address(0x123); + user_B = address(0x456); uint8 decimals = token.decimals(); newNttManagerNoRateLimiting.setPeer( - chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max + chainId2, + toWormholeFormat(address(0x1)), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max ); newNttManagerNoRateLimiting.setOutboundLimit( packTrimmedAmount(type(uint64).max, 8).untrim(decimals) @@ -304,14 +331,20 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { token.approve(address(newNttManagerNoRateLimiting), 3 * 10 ** decimals); - vm.expectRevert(abi.encodeWithSelector(IManagerBase.NoEnabledTransceivers.selector)); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + + vm.expectRevert(abi.encodeWithSelector(Endpoint.AdapterNotEnabled.selector)); newNttManagerNoRateLimiting.transfer( 1 * 10 ** decimals, chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); } @@ -321,120 +354,17 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { nttManager.setTransceiver(address(0x123)); } - function test_maxOutTransceivers() public { - // Let's register a transceiver and then disable it. We now have 2 registered managers - // since we register 1 in the setup - DummyTransceiver e = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e)); - nttManager.removeTransceiver(address(e)); - - // We should be able to register 64 transceivers total - for (uint256 i = 0; i < 62; ++i) { - DummyTransceiver d = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(d)); - } - - // Registering a new transceiver should fail as we've hit the cap - DummyTransceiver c = new DummyTransceiver(address(nttManager)); - vm.expectRevert(TransceiverRegistry.TooManyTransceivers.selector); - nttManager.setTransceiver(address(c)); - - // We should be able to renable an already registered transceiver at the cap - nttManager.setTransceiver(address(e)); - } - - function test_passingInstructionsToTransceivers() public { - // Let's register a transceiver and then disable the original transceiver. We now have 2 registered transceivers - // since we register 1 in the setup - DummyTransceiver e = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e)); - nttManager.removeTransceiver(address(dummyTransceiver)); - - address user_A = address(0x123); - address user_B = address(0x456); - - DummyToken token = DummyToken(nttManager.token()); - - uint8 decimals = token.decimals(); - - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); - nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); - - token.mintDummy(address(user_A), 5 * 10 ** decimals); - - vm.startPrank(user_A); - - token.approve(address(nttManager), 3 * 10 ** decimals); - - // Pass some instructions for the enabled transceiver - TransceiverStructs.TransceiverInstruction memory transceiverInstruction = - TransceiverStructs.TransceiverInstruction({index: 1, payload: new bytes(1)}); - TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions = - new TransceiverStructs.TransceiverInstruction[](1); - transceiverInstructions[0] = transceiverInstruction; - bytes memory instructions = - TransceiverStructs.encodeTransceiverInstructions(transceiverInstructions); - - nttManager.transfer( - 1 * 10 ** decimals, - chainId2, - toWormholeFormat(user_B), - toWormholeFormat(user_A), - false, - instructions - ); - } - - // == threshold - - function test_cantSetThresholdTooHigh() public { - // 1 transceiver set, so can't set threshold to 2 - vm.expectRevert(abi.encodeWithSelector(IManagerBase.ThresholdTooHigh.selector, 2, 1)); - nttManager.setThreshold(2); - } - - function test_canSetThreshold() public { - DummyTransceiver e1 = new DummyTransceiver(address(nttManager)); - DummyTransceiver e2 = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e1)); - nttManager.setTransceiver(address(e2)); - - nttManager.setThreshold(1); - nttManager.setThreshold(2); - nttManager.setThreshold(1); - } - - function test_cantSetThresholdToZero() public { - DummyTransceiver e = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e)); - - vm.expectRevert(abi.encodeWithSelector(IManagerBase.ZeroThreshold.selector)); - nttManager.setThreshold(0); - } - - function test_onlyOwnerCanSetThreshold() public { - address notOwner = address(0x123); - vm.startPrank(notOwner); - - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, notOwner) - ); - nttManager.setThreshold(1); - } - - // == threshold - function test_peerRegistrationLimitsCantBeUpdated() public { bytes32 peer = toWormholeFormat(address(nttManager)); - nttManager.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, 0); + nttManager.setPeer(nttManagerOther.chainId(), peer, 9, NttManagerHelpersLib.gasLimit, 0); IRateLimiter.RateLimitParams memory params = - nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); + nttManager.getInboundLimitParams(nttManagerOther.chainId()); assertEq(params.limit.getAmount(), 0); assertEq(params.limit.getDecimals(), 0); - nttManager.setInboundLimit(type(uint64).max, TransceiverHelpersLib.SENDING_CHAIN_ID); - params = nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); + nttManager.setInboundLimit(type(uint64).max, nttManagerOther.chainId()); + params = nttManager.getInboundLimitParams(nttManagerOther.chainId()); assertEq(params.limit.getAmount(), 0); assertEq(params.limit.getDecimals(), 0); } @@ -442,138 +372,106 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { // === attestation function test_onlyEnabledTransceiversCanAttest() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.removeTransceiver(address(e1)); - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); - - bytes memory transceiverMessage; - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - 0, bytes32(0), peer, toWormholeFormat(address(nttManagerOther)), abi.encode("payload") - ); - - vm.expectRevert( - abi.encodeWithSelector(TransceiverRegistry.CallerNotTransceiver.selector, address(e1)) - ); - e1.receiveMessage(transceiverMessage); + // Setup created two managers, each with one enabled transceiver pointed at the other. + + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); + + // This should work. + transceiver.receiveMessage(rmsg); + checkAttestationOnly(nttManager, rmsg, 1, 0); + + // But if we disable the transceiver for receiving, it should fail. + nttManager.disableRecvTransceiver(chainId2, address(transceiver)); + + vm.expectRevert(abi.encodeWithSelector(Endpoint.AdapterNotEnabled.selector)); + transceiver.receiveMessage(rmsg); } - function test_onlyPeerNttManagerNoRateLimitingCanAttest() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.setThreshold(2); - - bytes32 peer = toWormholeFormat(address(nttManager)); - - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - bytes memory transceiverMessage; - (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib - .buildTransceiverMessageWithNttManagerPayload( - 0, bytes32(0), peer, toWormholeFormat(address(nttManagerOther)), abi.encode("payload") - ); - + function test_onlyPeerNttManagerCanAttest() public { + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(0xdeadbeef)), // This is not the peer NttManagerNoRateLimiting. + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); + + // The endpoint and transceiver don't block this, so this should succeed. + transceiver.receiveMessage(rmsg); + checkAttestationOnly(nttManager, rmsg, 1, 0); + + // But the call to executeMsg should check the peer. + bytes memory message = "Hello, World"; vm.expectRevert( abi.encodeWithSelector( - INttManager.InvalidPeer.selector, TransceiverHelpersLib.SENDING_CHAIN_ID, peer + INttManager.InvalidPeer.selector, + chainId2, + UniversalAddressLibrary.fromAddress(address(0xdeadbeef)).toBytes32() ) ); - e1.receiveMessage(transceiverMessage); - } - - function test_attestSimple() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.setThreshold(2); - - // register nttManager peer - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); - - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - bytes memory transceiverMessage; - (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib - .buildTransceiverMessageWithNttManagerPayload( - 0, bytes32(0), peer, toWormholeFormat(address(nttManagerOther)), abi.encode("payload") + nttManager.executeMsg( + chainId2, UniversalAddressLibrary.fromAddress(address(0xdeadbeef)), 0, message ); - - e1.receiveMessage(transceiverMessage); - - bytes32 hash = TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, nttManagerMessage - ); - assertEq(nttManagerOther.messageAttestations(hash), 1); - } - - function test_attestTwice() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.setThreshold(2); - - // register nttManager peer - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); - - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - bytes memory transceiverMessage; - (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib - .buildTransceiverMessageWithNttManagerPayload( - 0, bytes32(0), peer, toWormholeFormat(address(nttManagerOther)), abi.encode("payload") - ); - - bytes32 hash = TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, nttManagerMessage - ); - - e1.receiveMessage(transceiverMessage); - vm.expectRevert( - abi.encodeWithSelector(IManagerBase.TransceiverAlreadyAttestedToMessage.selector, hash) - ); - e1.receiveMessage(transceiverMessage); - - // can't double vote - assertEq(nttManagerOther.messageAttestations(hash), 1); } - function test_attestDisabled() public { - (DummyTransceiver e1,) = TransceiverHelpersLib.setup_transceivers(nttManagerOther); - nttManagerOther.setThreshold(2); - - bytes32 peer = toWormholeFormat(address(nttManager)); - nttManagerOther.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max); - - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](1); - transceivers[0] = e1; - - TransceiverStructs.NttManagerMessage memory m; - (m,) = TransceiverHelpersLib.attestTransceiversHelper( - address(0x456), - 0, - chainId, - nttManager, - nttManagerOther, - packTrimmedAmount(50, 8), - packTrimmedAmount(type(uint64).max, 8), - transceivers - ); - - nttManagerOther.removeTransceiver(address(e1)); - - bytes32 hash = - TransceiverStructs.nttManagerMessageDigest(TransceiverHelpersLib.SENDING_CHAIN_ID, m); - // a disabled transceiver's vote no longer counts - assertEq(nttManagerOther.messageAttestations(hash), 0); - - nttManagerOther.setTransceiver(address(e1)); - // it counts again when reenabled - assertEq(nttManagerOther.messageAttestations(hash), 1); + function test_attestSimple() public { + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); + + // Attest the message. This should work. + transceiver.receiveMessage(rmsg); + checkAttestationOnly(nttManager, rmsg, 1, 0); + + // Can't attest the same message twice. + vm.expectRevert(abi.encodeWithSelector(Endpoint.DuplicateMessageAttestation.selector)); + transceiver.receiveMessage(rmsg); + + // Can't attest when the transceiver is disabled. + nttManager.disableRecvTransceiver(chainId2, address(transceiver)); + + rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 1, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256("Hello, World"), + refundAddr: address(user_A) + }); + + vm.expectRevert(abi.encodeWithSelector(Endpoint.AdapterNotEnabled.selector)); + transceiver.receiveMessage(rmsg); } function test_transfer_sequences() public { - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManager.setPeer( + chainId2, + toWormholeFormat(address(0x1)), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -588,7 +486,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); uint64 s2 = nttManager.transfer( 1 * 10 ** decimals, @@ -596,7 +496,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); uint64 s3 = nttManager.transfer( 1 * 10 ** decimals, @@ -604,7 +506,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); assertEq(s1, 0); @@ -614,10 +518,14 @@ contract TestNttManagerNoRateLimiting 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.setPeer(chainId2, toWormholeFormat(address(0x1)), 6, type(uint64).max); + nttManager.setPeer( + chainId2, + toWormholeFormat(address(0x1)), + 6, + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); - address user_A = address(0x123); - address user_B = address(0x456); DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); assertEq(decimals, 18); @@ -627,6 +535,10 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { vm.startPrank(user_A); token.approve(address(nttManager), type(uint256).max); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + // When transferring to a chain with 6 decimals the amount will get trimmed to 6 decimals. // Without rate limiting, this won't be scaled back up to 8 for local accounting. uint256 amount = type(uint64).max * 10 ** (decimals - 6); @@ -636,7 +548,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); // However, attempting to transfer an amount higher than the destination chain can handle will revert. @@ -648,7 +562,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); // A (slightly) more sensible amount should work normally @@ -659,75 +575,44 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); } - function test_attestationQuorum() public { - address user_B = address(0x456); - - (DummyTransceiver e1, DummyTransceiver e2) = - TransceiverHelpersLib.setup_transceivers(nttManagerOther); - + function test_alreadyExecuted() public { TrimmedAmount transferAmount = packTrimmedAmount(50, 8); + DummyTransceiver[] memory transceivers = new DummyTransceiver[](2); + (transceivers[0], transceivers[1]) = + TransceiverHelpersLib.addTransceiver(nttManagerOther, transceiverOther, chainId); + TransceiverStructs.NttManagerMessage memory m; - bytes memory encodedEm; - { - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](2); - transceivers[0] = e1; - transceivers[1] = e2; - - TransceiverStructs.TransceiverMessage memory em; - (m, em) = TransceiverHelpersLib.attestTransceiversHelper( - user_B, - 0, - chainId, - nttManager, - nttManagerOther, - transferAmount, - packTrimmedAmount(type(uint64).max, 8), - transceivers - ); - encodedEm = TransceiverStructs.encodeTransceiverMessage( - TransceiverHelpersLib.TEST_TRANSCEIVER_PAYLOAD_PREFIX, em - ); - } + DummyTransceiver.Message memory rmsg; + (m, rmsg) = TransceiverHelpersLib.transferAttestAndReceive( + user_B, + 0, + nttManager, + nttManagerOther, + transferAmount, + packTrimmedAmount(type(uint64).max, 8), + transceivers + ); - { - DummyToken token = DummyToken(nttManager.token()); - assertEq(token.balanceOf(address(user_B)), transferAmount.untrim(token.decimals())); - } + checkAttestationAndExecution(nttManagerOther, rmsg, 2); - // replay protection for transceiver - vm.recordLogs(); - vm.expectRevert( - abi.encodeWithSelector( - IManagerBase.TransceiverAlreadyAttestedToMessage.selector, - TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, m - ) - ) - ); - e2.receiveMessage(encodedEm); + // Replay protection should revert. + vm.expectRevert(abi.encodeWithSelector(Endpoint.DuplicateMessageAttestation.selector)); + transceivers[0].receiveMessage(rmsg); } function test_transfersOnForkedChains() public { uint256 evmChainId = block.chainid; - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); - uint8 decimals = token.decimals(); - nttManager.setPeer( - TransceiverHelpersLib.SENDING_CHAIN_ID, - toWormholeFormat(address(nttManagerOther)), - 9, - type(uint64).max - ); nttManager.setOutboundLimit(0); token.mintDummy(address(user_A), 5 * 10 ** decimals); @@ -738,13 +623,19 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { uint64 sequence = nttManager.transfer( 1 * 10 ** decimals, - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), - true, - new bytes(1) + true, // Should queue + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); + // We should have sent out message zero. + assertEq(sequence, 0); + require(1 == transceiver.getMessages().length, "Should have sent a message out"); + vm.warp(vm.getBlockTimestamp() + 1 days); vm.chainId(chainId); @@ -757,15 +648,21 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); nttManager.cancelOutboundQueuedTransfer(sequence); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + // Outbound transfers fail when queued vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, evmChainId, chainId)); nttManager.transfer( 1 * 10 ** decimals, - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), - true, - new bytes(1) + true, // Should queue + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); vm.stopPrank(); @@ -775,11 +672,13 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, evmChainId, chainId)); nttManager.transfer( 1 * 10 ** decimals, - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); // INBOUND @@ -794,35 +693,42 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { }) ); - bytes memory transceiverMessage; - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - (nttManagerMessage, transceiverMessage) = TransceiverHelpersLib - .buildTransceiverMessageWithNttManagerPayload( - 0, - toWormholeFormat(address(0x1)), - toWormholeFormat(address(nttManagerOther)), - toWormholeFormat(address(nttManager)), - tokenTransferMessage + TransceiverStructs.NttManagerMessage memory m = TransceiverStructs.NttManagerMessage( + 0, toWormholeFormat(address(0x1)), tokenTransferMessage ); - - // Inbound transfers can't be completed + bytes memory nttManagerMessage = TransceiverStructs.encodeNttManagerMessage(m); + + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManagerOther)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + payloadHash: keccak256(nttManagerMessage), + refundAddr: address(user_A) + }); + + // The endpoint doesn't do fork detection so the attestation will succeed. + transceiver.receiveMessage(rmsg); + + // But the execute should fail. vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, evmChainId, chainId)); - dummyTransceiver.receiveMessage(transceiverMessage); + nttManager.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage); // Inbound queued transfers can't be completed, per usual - nttManager.setInboundLimit(0, TransceiverHelpersLib.SENDING_CHAIN_ID); + nttManager.setInboundLimit(0, chainId2); vm.chainId(evmChainId); - bytes32 hash = TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, nttManagerMessage - ); - dummyTransceiver.receiveMessage(transceiverMessage); + rmsg.sequence = 1; // Update the endpoint sequence number so we don't get duplicate attestation. + transceiver.receiveMessage(rmsg); + nttManager.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage); vm.chainId(chainId); vm.warp(vm.getBlockTimestamp() + 1 days); + bytes32 hash = TransceiverStructs.nttManagerMessageDigest(chainId2, m); vm.expectRevert(abi.encodeWithSelector(INttManager.NotImplemented.selector)); nttManager.completeInboundQueuedTransfer(hash); } @@ -839,8 +745,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { function test_noAutomaticSlot() public { DummyToken t = new DummyToken(); - MockNttManagerNoRateLimitingContract c = - new MockNttManagerNoRateLimitingContract(address(t), IManagerBase.Mode.LOCKING, 1); + MockNttManagerNoRateLimitingContract c = new MockNttManagerNoRateLimitingContract( + address(endpoint), address(executor), address(t), IManagerBase.Mode.LOCKING, 1 + ); assertEq(c.lastSlot(), 0x0); } @@ -849,7 +756,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { vm.startStateDiffRecording(); - new MockNttManagerNoRateLimitingContract(address(t), IManagerBase.Mode.LOCKING, 1); + new MockNttManagerNoRateLimitingContract( + address(endpoint), address(executor), address(t), IManagerBase.Mode.LOCKING, 1 + ); Utils.assertSafeUpgradeableConstructor(vm.stopAndReturnStateDiff()); } @@ -862,28 +771,40 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { address to = address(0x456); DummyToken token = DummyToken(nttManager.token()); - uint8 decimals = token.decimals(); uint256 maxAmount = 5 * 10 ** decimals; token.mintDummy(from, maxAmount); - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManager.setPeer( + chainId2, + toWormholeFormat(address(0x1)), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); nttManager.setInboundLimit( - packTrimmedAmount(type(uint64).max, 8).untrim(decimals), - TransceiverHelpersLib.SENDING_CHAIN_ID + packTrimmedAmount(type(uint64).max, 8).untrim(decimals), nttManagerOther.chainId() ); vm.startPrank(from); - uint256 transferAmount = 3 * 10 ** decimals; - assertEq( - transferAmount < maxAmount - 500, true, "Transferring more tokens than what exists" - ); + uint256 amountWithDust; + uint256 dustAmount; + { + uint256 transferAmount = 3 * 10 ** decimals; + assertEq( + transferAmount < maxAmount - 500, true, "Transferring more tokens than what exists" + ); - uint256 dustAmount = 500; - uint256 amountWithDust = transferAmount + dustAmount; // An amount with 19 digits, which will result in dust due to 18 decimals - token.approve(address(nttManager), amountWithDust); + dustAmount = 500; + amountWithDust = transferAmount + dustAmount; // An amount with 19 digits, which will result in dust due to 18 decimals + token.approve(address(nttManager), amountWithDust); + } + + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); vm.expectRevert( abi.encodeWithSelector( @@ -896,7 +817,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { toWormholeFormat(to), toWormholeFormat(from), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); vm.stopPrank(); @@ -924,48 +847,43 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { // transceivers) and receive a message through it. // This ensures that the storage slots don't get clobbered through the upgrades. - address user_B = address(0x456); DummyToken token = DummyToken(nttManager.token()); TrimmedAmount transferAmount = packTrimmedAmount(50, 8); - (ITransceiverReceiver e1, ITransceiverReceiver e2) = - TransceiverHelpersLib.setup_transceivers(nttManagerOther); // Step 1 (contract is deployed by setUp()) - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](2); - transceivers[0] = e1; - transceivers[1] = e2; + DummyTransceiver[] memory transceivers = new DummyTransceiver[](2); + (transceivers[0], transceivers[1]) = + TransceiverHelpersLib.addTransceiver(nttManagerOther, transceiverOther, chainId); TransceiverStructs.NttManagerMessage memory m; - bytes memory encodedEm; - { - TransceiverStructs.TransceiverMessage memory em; - (m, em) = TransceiverHelpersLib.attestTransceiversHelper( - user_B, - 0, - chainId, - nttManager, - nttManagerOther, - transferAmount, - packTrimmedAmount(type(uint64).max, 8), - transceivers - ); - encodedEm = TransceiverStructs.encodeTransceiverMessage( - TransceiverHelpersLib.TEST_TRANSCEIVER_PAYLOAD_PREFIX, em - ); - } + DummyTransceiver.Message memory rmsg; + (m, rmsg) = TransceiverHelpersLib.transferAttestAndReceive( + user_B, + 0, + nttManager, + nttManagerOther, + transferAmount, + packTrimmedAmount(type(uint64).max, 8), + transceivers + ); + checkAttestationAndExecution(nttManagerOther, rmsg, 2); assertEq(token.balanceOf(address(user_B)), transferAmount.untrim(token.decimals())); // Step 2 (upgrade to a new nttManager) MockNttManagerNoRateLimitingContract newNttManagerNoRateLimiting = new MockNttManagerNoRateLimitingContract( - nttManager.token(), IManagerBase.Mode.LOCKING, chainId + address(nttManagerOther.endpoint()), + address(nttManagerOther.executor()), + nttManagerOther.token(), + nttManagerOther.mode(), + nttManagerOther.chainId() ); + nttManagerOther.upgrade(address(newNttManagerNoRateLimiting)); - TransceiverHelpersLib.attestTransceiversHelper( + (m, rmsg) = TransceiverHelpersLib.transferAttestAndReceive( user_B, bytes32(uint256(1)), - chainId, nttManager, // this is the proxy nttManagerOther, // this is the proxy transferAmount, @@ -973,6 +891,7 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { transceivers ); + checkAttestationAndExecution(nttManagerOther, rmsg, 2); assertEq(token.balanceOf(address(user_B)), transferAmount.untrim(token.decimals()) * 2); } @@ -981,18 +900,32 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { function test_canUpgradeFromNoRateLimitingToRateLimitingDisabled() public { // Create a standard manager with rate limiting disabled. DummyToken t = new DummyToken(); - NttManager implementation = - new MockNttManagerContract(address(t), IManagerBase.Mode.LOCKING, chainId, 0, true); + NttManager implementation = new MockNttManagerContract( + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + chainId, + 0, + true + ); MockNttManagerContract thisNttManager = MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); thisNttManager.initialize(); - thisNttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + thisNttManager.setPeer( + chainId2, + toWormholeFormat(address(0x1)), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); // Upgrade from NttManagerNoRateLimiting to NttManager with rate limiting enabled. This should work. - NttManager rateLimitingImplementation = - new MockNttManagerNoRateLimitingContract(address(t), IManagerBase.Mode.LOCKING, chainId); + NttManager rateLimitingImplementation = new MockNttManagerNoRateLimitingContract( + address(endpoint), address(executor), address(t), IManagerBase.Mode.LOCKING, chainId + ); thisNttManager.upgrade(address(rateLimitingImplementation)); } @@ -1001,18 +934,31 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { // Create a standard manager with rate limiting enabled. DummyToken t = new DummyToken(); NttManager implementation = new MockNttManagerContract( - address(t), IManagerBase.Mode.LOCKING, chainId, 1 days, false + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + chainId, + 1 days, + false ); MockNttManagerContract thisNttManager = MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); thisNttManager.initialize(); - thisNttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + thisNttManager.setPeer( + chainId2, + toWormholeFormat(address(0x1)), + 9, + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); // Upgrade from NttManagerNoRateLimiting to NttManager with rate limiting enabled. The immutable check should panic. - NttManager rateLimitingImplementation = - new MockNttManagerNoRateLimitingContract(address(t), IManagerBase.Mode.LOCKING, chainId); + NttManager rateLimitingImplementation = new MockNttManagerNoRateLimitingContract( + address(endpoint), address(executor), address(t), IManagerBase.Mode.LOCKING, chainId + ); vm.expectRevert(); // Reverts with a panic on the assert. So, no way to tell WHY this happened. thisNttManager.upgrade(address(rateLimitingImplementation)); @@ -1025,8 +971,9 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { DummyTokenMintAndBurn t = DummyTokenMintAndBurn(address(new ERC1967Proxy(address(dummy1), ""))); - NttManagerNoRateLimiting implementation = - new MockNttManagerNoRateLimitingContract(address(t), IManagerBase.Mode.LOCKING, chainId); + NttManagerNoRateLimiting implementation = new MockNttManagerNoRateLimitingContract( + address(endpoint), address(executor), address(t), IManagerBase.Mode.LOCKING, chainId + ); MockNttManagerNoRateLimitingContract newNttManagerNoRateLimiting = MockNttManagerNoRateLimitingContract(address(new ERC1967Proxy(address(implementation), ""))); @@ -1035,15 +982,13 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { // register nttManager peer and transceiver bytes32 peer = toWormholeFormat(address(nttManager)); newNttManagerNoRateLimiting.setPeer( - TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max + chainId2, peer, 9, NttManagerHelpersLib.gasLimit, type(uint64).max ); - { - DummyTransceiver e = new DummyTransceiver(address(newNttManagerNoRateLimiting)); - newNttManagerNoRateLimiting.setTransceiver(address(e)); - } + DummyTransceiver e1 = new DummyTransceiver(chainId, address(endpoint)); + newNttManagerNoRateLimiting.setTransceiver(address(e1)); + newNttManagerNoRateLimiting.enableSendTransceiver(chainId2, address(e1)); + newNttManagerNoRateLimiting.enableRecvTransceiver(chainId2, address(e1)); - address user_A = address(0x123); - address user_B = address(0x456); t.mintDummy(address(user_A), 5 * 10 ** t.decimals()); // Check that we can initiate a transfer @@ -1051,20 +996,17 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { t.approve(address(newNttManagerNoRateLimiting), 3 * 10 ** t.decimals()); newNttManagerNoRateLimiting.transfer( 1 * 10 ** t.decimals(), - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); // Check that we can receive a transfer - (DummyTransceiver e1,) = - TransceiverHelpersLib.setup_transceivers(newNttManagerNoRateLimiting); - newNttManagerNoRateLimiting.setThreshold(1); - - bytes memory transceiverMessage; bytes memory tokenTransferMessage; TrimmedAmount transferAmount = packTrimmedAmount(100, 8); @@ -1079,15 +1021,27 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { }) ); - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - 0, - bytes32(0), - peer, - toWormholeFormat(address(newNttManagerNoRateLimiting)), - tokenTransferMessage + TransceiverStructs.NttManagerMessage memory m = TransceiverStructs.NttManagerMessage( + 0, toWormholeFormat(address(0x1)), tokenTransferMessage + ); + bytes memory nttManagerMessage = TransceiverStructs.encodeNttManagerMessage(m); + + DummyTransceiver.Message memory rmsg = DummyTransceiver.Message({ + srcChain: chainId2, + srcAddr: UniversalAddressLibrary.fromAddress(address(nttManager)), + sequence: 0, + dstChain: chainId, + dstAddr: UniversalAddressLibrary.fromAddress(address(newNttManagerNoRateLimiting)), + payloadHash: keccak256(nttManagerMessage), + refundAddr: address(user_A) + }); + + // The endpoint doesn't do fork detection so the attestation will succeed. + e1.receiveMessage(rmsg); + newNttManagerNoRateLimiting.executeMsg( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage ); - e1.receiveMessage(transceiverMessage); uint256 userBExpectedBalance = transferAmount.untrim(t.decimals()); assertEq(t.balanceOf(address(user_B)), userBExpectedBalance); @@ -1097,23 +1051,29 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { vm.startPrank(user_A); newNttManagerNoRateLimiting.transfer( - 1 * 10 ** 10, - TransceiverHelpersLib.SENDING_CHAIN_ID, + 1 * 10 ** t.decimals(), + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - bytes32("1"), - bytes32(0), - peer, - toWormholeFormat(address(newNttManagerNoRateLimiting)), - tokenTransferMessage + m = TransceiverStructs.NttManagerMessage( + bytes32("1"), toWormholeFormat(address(0x1)), tokenTransferMessage + ); + nttManagerMessage = TransceiverStructs.encodeNttManagerMessage(m); + + rmsg.sequence++; + rmsg.payloadHash = keccak256(nttManagerMessage); + e1.receiveMessage(rmsg); + newNttManagerNoRateLimiting.executeMsg( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage ); - e1.receiveMessage(transceiverMessage); + userBExpectedBalance = userBExpectedBalance + transferAmount.untrim(t.decimals()); assertEq(t.balanceOf(address(user_B)), userBExpectedBalance); @@ -1125,61 +1085,88 @@ contract TestNttManagerNoRateLimiting is Test, IRateLimiterEvents { vm.startPrank(user_A); newNttManagerNoRateLimiting.transfer( 1 * 10 ** 7, - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); - (, transceiverMessage) = TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload( - bytes32("2"), - bytes32(0), - peer, - toWormholeFormat(address(newNttManagerNoRateLimiting)), - tokenTransferMessage + m = TransceiverStructs.NttManagerMessage( + bytes32("2"), toWormholeFormat(address(0x1)), tokenTransferMessage + ); + nttManagerMessage = TransceiverStructs.encodeNttManagerMessage(m); + + rmsg.sequence++; + rmsg.payloadHash = keccak256(nttManagerMessage); + e1.receiveMessage(rmsg); + newNttManagerNoRateLimiting.executeMsg( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, nttManagerMessage ); - e1.receiveMessage(transceiverMessage); userBExpectedBalance = userBExpectedBalance + transferAmount.untrim(t.decimals()); assertEq(t.balanceOf(address(user_B)), userBExpectedBalance); } - function test_transferWithInstructionIndexOutOfBounds() public { - TransceiverStructs.TransceiverInstruction memory TransceiverInstruction = - TransceiverStructs.TransceiverInstruction({index: 100, payload: new bytes(1)}); - TransceiverStructs.TransceiverInstruction[] memory TransceiverInstructions = - new TransceiverStructs.TransceiverInstruction[](1); - TransceiverInstructions[0] = TransceiverInstruction; - bytes memory encodedInstructions = - TransceiverStructs.encodeTransceiverInstructions(TransceiverInstructions); - - address user_A = address(0x123); - address user_B = address(0x456); - - DummyToken token = DummyToken(nttManager.token()); - - uint8 decimals = token.decimals(); - - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); - nttManager.setOutboundLimit(packTrimmedAmount(type(uint64).max, 8).untrim(decimals)); + function checkAttestationOnly( + NttManagerNoRateLimiting nttm, + DummyTransceiver.Message memory rmsg, + uint8 expectedAttestations, + uint8 transceiverIdx + ) public view { + // Verify that it shows as attested. + require( + expectedAttestations + == nttm.messageAttestations( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ), + "Message did not attest" + ); - token.mintDummy(address(user_A), 5 * 10 ** decimals); + // Verify that the right transceiver attested. + require( + nttm.transceiverAttestedToMessage( + rmsg.srcChain, + rmsg.srcAddr, + rmsg.sequence, + rmsg.dstAddr, + rmsg.payloadHash, + transceiverIdx + ), + "Transceiver did not attest to message" + ); - vm.startPrank(user_A); + // But the message should not be marked as executed. + require( + !nttm.isMessageExecuted( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ), + "Message should not be marked executed yet" + ); + } - token.approve(address(nttManager), 3 * 10 ** decimals); + error WrongNumberOfAttestations(uint8 expected, uint8 actual); - vm.expectRevert( - abi.encodeWithSelector(TransceiverStructs.InvalidInstructionIndex.selector, 100, 1) + function checkAttestationAndExecution( + NttManagerNoRateLimiting nttm, + DummyTransceiver.Message memory rmsg, + uint8 expectedAttestations + ) public view { + // Verify that it shows as attested. + uint8 actual = nttm.messageAttestations( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash ); - nttManager.transfer( - 1 * 10 ** decimals, - chainId2, - toWormholeFormat(user_B), - toWormholeFormat(user_A), - false, - encodedInstructions + if (actual != expectedAttestations) { + revert WrongNumberOfAttestations(expectedAttestations, actual); + } + + require( + nttManagerOther.isMessageExecuted( + rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, rmsg.dstAddr, rmsg.payloadHash + ), + "Message should be marked executed yet" ); } } diff --git a/evm/test/Ownership.t.sol b/evm/test/Ownership.t.sol index d2c0127dd..cb4a3e93f 100644 --- a/evm/test/Ownership.t.sol +++ b/evm/test/Ownership.t.sol @@ -8,33 +8,49 @@ import "../src/interfaces/IManagerBase.sol"; import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {DummyTransceiver} from "./NttManager.t.sol"; import {DummyToken} from "./NttManager.t.sol"; +import "./mocks/MockEndpoint.sol"; +import "./mocks/MockExecutor.sol"; contract OwnershipTests is Test { + MockEndpoint endpoint; + MockExecutor executor; NttManager nttManager; uint16 constant chainId = 7; function setUp() public { DummyToken t = new DummyToken(); + endpoint = new MockEndpoint(chainId); + executor = new MockExecutor(chainId); + NttManager implementation = new MockNttManagerContract( - address(t), IManagerBase.Mode.LOCKING, chainId, 1 days, false + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + chainId, + 1 days, + false ); nttManager = MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); nttManager.initialize(); } - function checkOwnership(DummyTransceiver e, address nttManagerOwner) public { - address transceiverNttManager = e.getNttManagerOwner(); - assertEq(transceiverNttManager, nttManagerOwner); - } + function test_setUp() public {} - /// transceiver retrieves the nttManager owner correctly - function testTransceiverOwnership() public { - // TODO: use setup_transceivers here - DummyTransceiver e1 = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e1)); - nttManager.setThreshold(1); + // The transceiver is no longer owned by the NttManager. It's owned by the endpoint. Nothing to test here. + // function checkOwnership(DummyTransceiver e, address nttManagerOwner) public { + // address transceiverNttManager = e.getNttManagerOwner(); + // assertEq(transceiverNttManager, nttManagerOwner); + // } - checkOwnership(e1, nttManager.owner()); - } + // /// transceiver retrieves the nttManager owner correctly + // function testTransceiverOwnership() public { + // // TODO: use setup_transceivers here + // DummyTransceiver e1 = new DummyTransceiver(address(nttManager)); + // nttManager.setTransceiver(address(e1)); + // nttManager.setThreshold(1); + + // checkOwnership(e1, nttManager.owner()); + // } } diff --git a/evm/test/RateLimit.t.sol b/evm/test/RateLimit.t.sol index 159246e20..7c67dc54c 100644 --- a/evm/test/RateLimit.t.sol +++ b/evm/test/RateLimit.t.sol @@ -12,11 +12,22 @@ import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "./libraries/TransceiverHelpers.sol"; import "./libraries/NttManagerHelpers.sol"; import "wormhole-solidity-sdk/libraries/BytesParsing.sol"; +import "./mocks/MockNttManager.sol"; +import "./mocks/MockEndpoint.sol"; +import "./mocks/MockExecutor.sol"; +import "./mocks/DummyTransceiver.sol"; pragma solidity >=0.8.8 <0.9.0; contract TestRateLimit is Test, IRateLimiterEvents { + MockEndpoint endpoint; + MockEndpoint endpointOther; + MockExecutor executor; + MockExecutor executorOther; MockNttManagerContract nttManager; + MockNttManagerContract nttManagerOther; + DummyTransceiver transceiver; + DummyTransceiver transceiverOther; using TrimmedAmountLib for uint256; using TrimmedAmountLib for TrimmedAmount; @@ -25,6 +36,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { uint16 constant chainId = 7; uint16 constant chainId2 = 8; + address user_A = address(0x123); + address user_B = address(0x456); + uint256 constant DEVNET_GUARDIAN_PK = 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; WormholeSimulator guardian; @@ -38,20 +52,69 @@ contract TestRateLimit is Test, IRateLimiterEvents { guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK); + endpoint = new MockEndpoint(chainId); + endpointOther = new MockEndpoint(chainId2); + + executor = new MockExecutor(chainId); + executorOther = new MockExecutor(chainId2); + DummyToken t = new DummyToken(); NttManager implementation = new MockNttManagerContract( - address(t), IManagerBase.Mode.LOCKING, chainId, 1 days, false + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + chainId, + 1 days, + false + ); + + NttManager implementationOther = new MockNttManagerContract( + address(endpointOther), + address(executorOther), + address(t), + IManagerBase.Mode.LOCKING, + chainId2, + 1 days, + false ); nttManager = MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); nttManager.initialize(); - nttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); + nttManagerOther = + MockNttManagerContract(address(new ERC1967Proxy(address(implementationOther), ""))); + nttManagerOther.initialize(); + + nttManager.setPeer( + chainId2, + toWormholeFormat(address(nttManagerOther)), + t.decimals(), + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); + + nttManagerOther.setPeer( + chainId, + toWormholeFormat(address(nttManager)), + t.decimals(), + NttManagerHelpersLib.gasLimit, + type(uint64).max + ); + + transceiver = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(transceiver)); + nttManager.enableSendTransceiver(chainId2, address(transceiver)); + nttManager.enableRecvTransceiver(chainId2, address(transceiver)); - DummyTransceiver e = new DummyTransceiver(address(nttManager)); - nttManager.setTransceiver(address(e)); + transceiverOther = new DummyTransceiver(chainId2, address(endpointOther)); + nttManagerOther.setTransceiver(address(transceiverOther)); + nttManagerOther.enableSendTransceiver(chainId, address(transceiverOther)); + nttManagerOther.enableRecvTransceiver(chainId, address(transceiverOther)); } + function test_setUp() public {} + function test_outboundRateLimit_setLimitSimple() public { DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -72,9 +135,6 @@ contract TestRateLimit is Test, IRateLimiterEvents { function test_outboundRateLimit() public { // transfer 3 tokens - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -93,7 +153,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -119,9 +181,6 @@ contract TestRateLimit is Test, IRateLimiterEvents { function test_outboundRateLimit_setHigherLimit() public { // transfer 3 tokens - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -140,7 +199,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -166,9 +227,6 @@ contract TestRateLimit is Test, IRateLimiterEvents { function test_outboundRateLimit_setLowerLimit() public { // transfer 3 tokens - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -187,7 +245,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -208,9 +268,6 @@ contract TestRateLimit is Test, IRateLimiterEvents { function test_outboundRateLimit_setHigherLimit_duration() public { // transfer 3 tokens - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -229,7 +286,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -264,9 +323,6 @@ contract TestRateLimit is Test, IRateLimiterEvents { function test_outboundRateLimit_setLowerLimit_durationCaseOne() public { // transfer 3 tokens - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -285,7 +341,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -312,9 +370,6 @@ contract TestRateLimit is Test, IRateLimiterEvents { } function test_outboundRateLimit_setLowerLimit_durationCaseTwo() public { - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -335,7 +390,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -369,9 +426,6 @@ contract TestRateLimit is Test, IRateLimiterEvents { } function test_outboundRateLimit_singleHit() public { - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -385,6 +439,10 @@ contract TestRateLimit is Test, IRateLimiterEvents { uint256 transferAmount = 3 * 10 ** decimals; token.approve(address(nttManager), transferAmount); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + vm.expectRevert( abi.encodeWithSelector( IRateLimiter.NotEnoughCapacity.selector, outboundLimit, transferAmount @@ -396,14 +454,13 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); } function test_outboundRateLimit_multiHit() public { - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -422,7 +479,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); // assert that first transfer went through @@ -437,6 +496,10 @@ contract TestRateLimit is Test, IRateLimiterEvents { uint256 badTransferAmount = 2 * 10 ** decimals; token.approve(address(nttManager), badTransferAmount); + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + vm.expectRevert( abi.encodeWithSelector( IRateLimiter.NotEnoughCapacity.selector, @@ -450,7 +513,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); } @@ -460,9 +525,6 @@ contract TestRateLimit is Test, IRateLimiterEvents { // test that it exits queue after >= rateLimitDuration // test that it's removed from queue and can't be replayed function test_outboundRateLimit_queue() public { - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -483,7 +545,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), true, - new bytes(1) + executor.createSignedQuote(executorOther.chainId(), 2 days), // We are going to warp the time below. + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); // assert that the transfer got queued up @@ -523,21 +587,17 @@ contract TestRateLimit is Test, IRateLimiterEvents { } function test_inboundRateLimit_simple() public { - address user_B = address(0x456); - - (DummyTransceiver e1, DummyTransceiver e2) = - TransceiverHelpersLib.setup_transceivers(nttManager); + DummyTransceiver[] memory transceiversOther = new DummyTransceiver[](2); + (transceiversOther[0], transceiversOther[1]) = + TransceiverHelpersLib.addTransceiver(nttManagerOther, transceiverOther, chainId); DummyToken token = DummyToken(nttManager.token()); - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](2); - transceivers[0] = e1; - transceivers[1] = e2; - TrimmedAmount transferAmount = packTrimmedAmount(50, 8); TrimmedAmount limitAmount = packTrimmedAmount(100, 8); - TransceiverHelpersLib.attestTransceiversHelper( - user_B, 0, chainId, nttManager, nttManager, transferAmount, limitAmount, transceivers + + TransceiverHelpersLib.transferAttestAndReceive( + user_B, 0, nttManager, nttManagerOther, transferAmount, limitAmount, transceiversOther ); // assert that the user received tokens @@ -545,7 +605,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { // assert that the inbound limits updated IRateLimiter.RateLimitParams memory inboundLimitParams = - nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); + nttManagerOther.getInboundLimitParams(chainId); assertEq( inboundLimitParams.currentCapacity.getAmount(), (limitAmount - (transferAmount)).getAmount() @@ -563,49 +623,39 @@ contract TestRateLimit is Test, IRateLimiterEvents { } function test_inboundRateLimit_queue() public { - address user_B = address(0x456); - - (DummyTransceiver e1, DummyTransceiver e2) = - TransceiverHelpersLib.setup_transceivers(nttManager); - DummyToken token = DummyToken(nttManager.token()); - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](1); - transceivers[0] = e1; - - TransceiverStructs.NttManagerMessage memory m; - bytes memory encodedEm; - { - TransceiverStructs.TransceiverMessage memory em; - (m, em) = TransceiverHelpersLib.attestTransceiversHelper( - user_B, - 0, - chainId, - nttManager, - nttManager, - packTrimmedAmount(50, 8), - uint256(5).trim(token.decimals(), token.decimals()), - transceivers - ); - encodedEm = TransceiverStructs.encodeTransceiverMessage( - TransceiverHelpersLib.TEST_TRANSCEIVER_PAYLOAD_PREFIX, em - ); - } + DummyTransceiver[] memory transceivers = new DummyTransceiver[](2); + (transceivers[0], transceivers[1]) = + TransceiverHelpersLib.addTransceiver(nttManagerOther, transceiverOther, chainId); + + ( + TransceiverStructs.NttManagerMessage memory m, + bytes memory encodedM, + DummyTransceiver.Message memory rmsg + ) = TransceiverHelpersLib.transferAndAttest( + user_B, + 0, + nttManager, + nttManagerOther, + packTrimmedAmount(50, 8), + uint256(5).trim(token.decimals(), token.decimals()), + transceivers + ); - bytes32 digest = - TransceiverStructs.nttManagerMessageDigest(TransceiverHelpersLib.SENDING_CHAIN_ID, m); + bytes32 digest = TransceiverStructs.nttManagerMessageDigest(chainId, m); - // no quorum yet + // Haven't executed yet. assertEq(token.balanceOf(address(user_B)), 0); - vm.expectEmit(address(nttManager)); + vm.expectEmit(address(nttManagerOther)); emit InboundTransferQueued(digest); - e2.receiveMessage(encodedEm); + nttManagerOther.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, encodedM); { // now we have quorum but it'll hit limit IRateLimiter.InboundQueuedTransfer memory qt = - nttManager.getInboundQueuedTransfer(digest); + nttManagerOther.getInboundQueuedTransfer(digest); assertEq(qt.amount.getAmount(), 50); assertEq(qt.txTimestamp, initialBlockTimestamp); assertEq(qt.recipient, user_B); @@ -615,7 +665,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { assertEq(token.balanceOf(address(user_B)), 0); // change block time to (duration - 1) seconds later - uint256 durationElapsedTime = initialBlockTimestamp + nttManager.rateLimitDuration(); + uint256 durationElapsedTime = initialBlockTimestamp + nttManagerOther.rateLimitDuration(); vm.warp(durationElapsedTime - 1); { @@ -627,49 +677,26 @@ contract TestRateLimit is Test, IRateLimiterEvents { initialBlockTimestamp ) ); - nttManager.completeInboundQueuedTransfer(digest); + nttManagerOther.completeInboundQueuedTransfer(digest); } // now complete transfer vm.warp(durationElapsedTime); - nttManager.completeInboundQueuedTransfer(digest); + nttManagerOther.completeInboundQueuedTransfer(digest); { // assert transfer no longer in queue vm.expectRevert( abi.encodeWithSelector(IRateLimiter.InboundQueuedTransferNotFound.selector, digest) ); - nttManager.completeInboundQueuedTransfer(digest); + nttManagerOther.completeInboundQueuedTransfer(digest); } // assert user now has funds assertEq(token.balanceOf(address(user_B)), 50 * 10 ** (token.decimals() - 8)); - - // replay protection on executeMsg - vm.recordLogs(); - nttManager.executeMsg( - TransceiverHelpersLib.SENDING_CHAIN_ID, toWormholeFormat(address(nttManager)), m - ); - - { - Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries.length, 1); - assertEq(entries[0].topics.length, 3); - assertEq(entries[0].topics[0], keccak256("MessageAlreadyExecuted(bytes32,bytes32)")); - assertEq(entries[0].topics[1], toWormholeFormat(address(nttManager))); - assertEq( - entries[0].topics[2], - TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, m - ) - ); - } } function test_circular_flow() public { - address user_A = address(0x123); - address user_B = address(0x456); - DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -691,7 +718,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -717,15 +746,13 @@ contract TestRateLimit is Test, IRateLimiterEvents { vm.warp(receiveTime); // now receive 10 tokens from user_B -> user_A - (DummyTransceiver e1, DummyTransceiver e2) = - TransceiverHelpersLib.setup_transceivers(nttManager); - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](2); - transceivers[0] = e1; - transceivers[1] = e2; + DummyTransceiver[] memory transceivers = new DummyTransceiver[](2); + (transceivers[0], transceivers[1]) = + TransceiverHelpersLib.addTransceiver(nttManager, transceiver, chainId2); - TransceiverHelpersLib.attestTransceiversHelper( - user_A, 0, chainId, nttManager, nttManager, transferAmount, mintAmount, transceivers + TransceiverHelpersLib.transferAttestAndReceive( + user_A, 0, nttManagerOther, nttManager, transferAmount, mintAmount, transceivers ); // assert that user_A has original amount @@ -735,7 +762,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { // consume capacity on the inbound side // assert that the inbound capacity decreased IRateLimiter.RateLimitParams memory inboundLimitParams = - nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); + nttManager.getInboundLimitParams(chainId2); assertEq( inboundLimitParams.currentCapacity.getAmount(), (inboundLimitParams.limit - transferAmount).getAmount() @@ -766,11 +793,13 @@ contract TestRateLimit is Test, IRateLimiterEvents { nttManager.transfer( transferAmount.untrim(decimals), - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(userA), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -789,7 +818,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { { // assert that the inbound limit is at max again (because of backflow) IRateLimiter.RateLimitParams memory inboundLimitParams = - nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); + nttManager.getInboundLimitParams(chainId2); assertEq( inboundLimitParams.currentCapacity.getAmount(), inboundLimitParams.limit.getAmount() ); @@ -798,26 +827,19 @@ contract TestRateLimit is Test, IRateLimiterEvents { } // helper functions - function setupToken() public returns (address, address, DummyToken, uint8) { - address user_A = address(0x123); - address user_B = address(0x456); - + function setupToken() public returns (DummyToken, uint8) { DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); assertEq(decimals, 18); - return (user_A, user_B, token, decimals); + return (token, decimals); } - function initializeTransceivers() public returns (ITransceiverReceiver[] memory) { - (DummyTransceiver e1, DummyTransceiver e2) = - TransceiverHelpersLib.setup_transceivers(nttManager); - - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](2); - transceivers[0] = e1; - transceivers[1] = e2; - + function initializeTransceivers() public returns (DummyTransceiver[] memory) { + DummyTransceiver[] memory transceivers = new DummyTransceiver[](2); + (transceivers[0], transceivers[1]) = + TransceiverHelpersLib.addTransceiver(nttManager, transceiver, chainId2); return transceivers; } @@ -849,7 +871,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { // enforces transferAmt <= mintAmt transferAmt = bound(transferAmt, 0, mintAmt); - (address user_A, address user_B, DummyToken token, uint8 decimals) = setupToken(); + (DummyToken token, uint8 decimals) = setupToken(); // allow for amounts greater than uint64 to check if [`setOutboundLimit`] reverts // on amounts greater than u64 MAX. @@ -873,6 +895,10 @@ contract TestRateLimit is Test, IRateLimiterEvents { // check error conditions // revert if amount to be transferred is 0 if (transferAmount.getAmount() == 0) { + bytes memory executorSignedQuote = executor.createSignedQuote(executorOther.chainId()); + bytes memory executorRelayInstructions = executor.createRelayInstructions(); + bytes memory adapterInstructions = endpoint.createAdapterInstructions(); + vm.expectRevert(abi.encodeWithSelector(INttManager.ZeroAmount.selector)); nttManager.transfer( transferAmount.untrim(decimals), @@ -880,7 +906,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executorSignedQuote, + executorRelayInstructions, + adapterInstructions ); return; @@ -893,7 +921,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -918,10 +948,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { uint256 receiveTime = initialBlockTimestamp + 1; vm.warp(receiveTime); - ITransceiverReceiver[] memory transceivers = initializeTransceivers(); - // now receive tokens from user_B -> user_A - TransceiverHelpersLib.attestTransceiversHelper( - user_A, 0, chainId, nttManager, nttManager, transferAmount, mintAmount, transceivers + DummyTransceiver[] memory transceivers = initializeTransceivers(); + TransceiverHelpersLib.transferAttestAndReceive( + user_A, 0, nttManagerOther, nttManager, transferAmount, mintAmount, transceivers ); // assert that user_A has original amount @@ -931,7 +960,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { // consume capacity on the inbound side // assert that the inbound capacity decreased IRateLimiter.RateLimitParams memory inboundLimitParams = - nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); + nttManager.getInboundLimitParams(chainId2); assertEq( inboundLimitParams.currentCapacity.getAmount(), (inboundLimitParams.limit - transferAmount).getAmount() @@ -959,11 +988,13 @@ contract TestRateLimit is Test, IRateLimiterEvents { nttManager.transfer( transferAmount.untrim(decimals), - TransceiverHelpersLib.SENDING_CHAIN_ID, + chainId2, toWormholeFormat(user_B), toWormholeFormat(user_A), false, - new bytes(1) + executor.createSignedQuote(executorOther.chainId()), + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); vm.stopPrank(); @@ -982,7 +1013,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { { // assert that the inbound limit is at max again (because of backflow) IRateLimiter.RateLimitParams memory inboundLimitParams = - nttManager.getInboundLimitParams(TransceiverHelpersLib.SENDING_CHAIN_ID); + nttManager.getInboundLimitParams(chainId2); assertEq( inboundLimitParams.currentCapacity.getAmount(), inboundLimitParams.limit.getAmount() ); @@ -992,8 +1023,6 @@ contract TestRateLimit is Test, IRateLimiterEvents { function testFuzz_outboundRateLimitShouldQueue(uint256 limitAmt, uint256 transferAmt) public { // setup - address user_A = address(0x123); - address user_B = address(0x456); DummyToken token = DummyToken(nttManager.token()); uint8 decimals = token.decimals(); @@ -1023,7 +1052,9 @@ contract TestRateLimit is Test, IRateLimiterEvents { toWormholeFormat(user_B), toWormholeFormat(user_A), true, - new bytes(1) + executor.createSignedQuote(executorOther.chainId(), 2 days), // We are going to warp the time below. + executor.createRelayInstructions(), + endpoint.createAdapterInstructions() ); // assert that the transfer got queued up @@ -1065,52 +1096,60 @@ contract TestRateLimit is Test, IRateLimiterEvents { function testFuzz_inboundRateLimitShouldQueue(uint256 inboundLimitAmt, uint256 amount) public { amount = bound(amount, 1, type(uint64).max); inboundLimitAmt = bound(amount, 0, amount - 1); - - address user_B = address(0x456); - - (DummyTransceiver e1, DummyTransceiver e2) = - TransceiverHelpersLib.setup_transceivers(nttManager); - DummyToken token = DummyToken(nttManager.token()); - ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](1); - transceivers[0] = e1; + DummyTransceiver[] memory transceivers = new DummyTransceiver[](2); + (transceivers[0], transceivers[1]) = + TransceiverHelpersLib.addTransceiver(nttManagerOther, transceiverOther, chainId); - TransceiverStructs.NttManagerMessage memory m; - bytes memory encodedEm; + // TransceiverStructs.NttManagerMessage memory m; + // bytes memory encodedEm; uint256 inboundLimit = inboundLimitAmt; TrimmedAmount trimmedAmount = packTrimmedAmount(uint64(amount), 8); - { - TransceiverStructs.TransceiverMessage memory em; - (m, em) = TransceiverHelpersLib.attestTransceiversHelper( - user_B, - 0, - chainId, - nttManager, - nttManager, - trimmedAmount, - inboundLimit.trim(token.decimals(), token.decimals()), - transceivers - ); - encodedEm = TransceiverStructs.encodeTransceiverMessage( - TransceiverHelpersLib.TEST_TRANSCEIVER_PAYLOAD_PREFIX, em - ); - } + // { + // TransceiverStructs.TransceiverMessage memory em; + // (m, em) = TransceiverHelpersLib.attestTransceiversHelper( + // user_B, + // 0, + // chainId, + // nttManager, + // nttManager, + // trimmedAmount, + // inboundLimit.trim(token.decimals(), token.decimals()), + // transceivers + // ); + // encodedEm = TransceiverStructs.encodeTransceiverMessage( + // TransceiverHelpersLib.TEST_TRANSCEIVER_PAYLOAD_PREFIX, em + // ); + // } + + ( + TransceiverStructs.NttManagerMessage memory m, + bytes memory encodedM, + DummyTransceiver.Message memory rmsg + ) = TransceiverHelpersLib.transferAndAttest( + user_B, + 0, + nttManager, + nttManagerOther, + trimmedAmount, + inboundLimit.trim(token.decimals(), token.decimals()), + transceivers + ); - bytes32 digest = - TransceiverStructs.nttManagerMessageDigest(TransceiverHelpersLib.SENDING_CHAIN_ID, m); + bytes32 digest = TransceiverStructs.nttManagerMessageDigest(chainId, m); - // no quorum yet + // Haven't executed yet. assertEq(token.balanceOf(address(user_B)), 0); - vm.expectEmit(address(nttManager)); + vm.expectEmit(address(nttManagerOther)); emit InboundTransferQueued(digest); - e2.receiveMessage(encodedEm); + nttManagerOther.executeMsg(rmsg.srcChain, rmsg.srcAddr, rmsg.sequence, encodedM); { // now we have quorum but it'll hit limit IRateLimiter.InboundQueuedTransfer memory qt = - nttManager.getInboundQueuedTransfer(digest); + nttManagerOther.getInboundQueuedTransfer(digest); assertEq(qt.amount.getAmount(), trimmedAmount.getAmount()); assertEq(qt.txTimestamp, initialBlockTimestamp); assertEq(qt.recipient, user_B); @@ -1120,7 +1159,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { assertEq(token.balanceOf(address(user_B)), 0); // change block time to (duration - 1) seconds later - uint256 durationElapsedTime = initialBlockTimestamp + nttManager.rateLimitDuration(); + uint256 durationElapsedTime = initialBlockTimestamp + nttManagerOther.rateLimitDuration(); vm.warp(durationElapsedTime - 1); { @@ -1132,19 +1171,19 @@ contract TestRateLimit is Test, IRateLimiterEvents { initialBlockTimestamp ) ); - nttManager.completeInboundQueuedTransfer(digest); + nttManagerOther.completeInboundQueuedTransfer(digest); } // now complete transfer vm.warp(durationElapsedTime); - nttManager.completeInboundQueuedTransfer(digest); + nttManagerOther.completeInboundQueuedTransfer(digest); { // assert transfer no longer in queue vm.expectRevert( abi.encodeWithSelector(IRateLimiter.InboundQueuedTransferNotFound.selector, digest) ); - nttManager.completeInboundQueuedTransfer(digest); + nttManagerOther.completeInboundQueuedTransfer(digest); } // assert user now has funds @@ -1152,25 +1191,5 @@ contract TestRateLimit is Test, IRateLimiterEvents { token.balanceOf(address(user_B)), trimmedAmount.getAmount() * 10 ** (token.decimals() - 8) ); - - // replay protection on executeMsg - vm.recordLogs(); - nttManager.executeMsg( - TransceiverHelpersLib.SENDING_CHAIN_ID, toWormholeFormat(address(nttManager)), m - ); - - { - Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries.length, 1); - assertEq(entries[0].topics.length, 3); - assertEq(entries[0].topics[0], keccak256("MessageAlreadyExecuted(bytes32,bytes32)")); - assertEq(entries[0].topics[1], toWormholeFormat(address(nttManager))); - assertEq( - entries[0].topics[2], - TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, m - ) - ); - } } } diff --git a/evm/test/Threshold.t.sol b/evm/test/Threshold.t.sol new file mode 100644 index 000000000..aab888620 --- /dev/null +++ b/evm/test/Threshold.t.sol @@ -0,0 +1,240 @@ +// 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/interfaces/INttManager.sol"; +import "../src/interfaces/IManagerBase.sol"; +import "../src/libraries/external/OwnableUpgradeable.sol"; + +import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "./mocks/DummyTransceiver.sol"; +import "../src/mocks/DummyToken.sol"; +import "./mocks/MockNttManager.sol"; +import "./mocks/MockEndpoint.sol"; +import "./mocks/MockExecutor.sol"; + +contract TestThreshold is Test { + MockNttManagerContract nttManager; + MockEndpoint endpoint; + MockExecutor executor; + + uint16 constant chainId = 7; + uint16 constant chainId2 = 8; + uint16 constant chainId3 = 9; + + function setUp() public { + endpoint = new MockEndpoint(chainId); + endpoint = new MockEndpoint(chainId); + + DummyToken t = new DummyToken(); + NttManager implementation = new MockNttManagerContract( + address(endpoint), + address(executor), + address(t), + IManagerBase.Mode.LOCKING, + chainId, + 1 days, + false + ); + + nttManager = MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); + nttManager.initialize(); + } + + function test_setUp() public {} + + function test_canSetThreshold() public { + DummyTransceiver e1 = new DummyTransceiver(chainId, address(endpoint)); + DummyTransceiver e2 = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e1)); + nttManager.setTransceiver(address(e2)); + + nttManager.enableRecvTransceiver(chainId2, address(e1)); + nttManager.enableRecvTransceiver(chainId2, address(e2)); + + nttManager.setThreshold(chainId2, 1); + nttManager.setThreshold(chainId2, 2); + nttManager.setThreshold(chainId2, 1); + } + + function test_cantSetThresholdToZero() public { + DummyTransceiver e = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e)); + nttManager.enableRecvTransceiver(chainId2, address(e)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.ZeroThreshold.selector)); + nttManager.setThreshold(chainId2, 0); + } + + function test_cantSetThresholdTooHigh() public { + // With one transceiver, can't set the threshold to two. + DummyTransceiver e1 = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e1)); + nttManager.enableRecvTransceiver(chainId2, address(e1)); + vm.expectRevert(abi.encodeWithSelector(IManagerBase.ThresholdTooHigh.selector, 2, 1)); + nttManager.setThreshold(chainId2, 2); + } + + function test_onlyOwnerCanSetThreshold() public { + address notOwner = address(0x123); + vm.startPrank(notOwner); + + vm.expectRevert( + abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, notOwner) + ); + nttManager.setThreshold(chainId2, 1); + } + + function test_thresholdGetsSetOnFirstEnable() public { + // The threshold starts at zero. + assertEq(0, nttManager.getThreshold(chainId2)); + + // When we enable the first transceiver, it should go to one. + DummyTransceiver e1 = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e1)); + nttManager.enableRecvTransceiver(chainId2, address(e1)); + assertEq(1, nttManager.getThreshold(chainId2)); + + // But it should not increase when we enable more. + DummyTransceiver e2 = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e2)); + nttManager.enableRecvTransceiver(chainId2, address(e2)); + assertEq(1, nttManager.getThreshold(chainId2)); + } + + function test_thresholdReducesOnDisable() public { + //Create and enable a few transceivers on a couple of chains. + DummyTransceiver e1 = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e1)); + nttManager.enableRecvTransceiver(chainId2, address(e1)); + + DummyTransceiver e2 = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e2)); + nttManager.enableRecvTransceiver(chainId2, address(e2)); + + DummyTransceiver e3 = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e3)); + nttManager.enableRecvTransceiver(chainId2, address(e3)); + + DummyTransceiver e4 = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e4)); + nttManager.enableRecvTransceiver(chainId3, address(e4)); + + DummyTransceiver e5 = new DummyTransceiver(chainId, address(endpoint)); + nttManager.setTransceiver(address(e5)); + nttManager.enableRecvTransceiver(chainId3, address(e5)); + + // The thresholds should have been set to one automatically. + assertEq(1, nttManager.getThreshold(chainId2)); + assertEq(1, nttManager.getThreshold(chainId3)); + + // Bump the thresholds up. + nttManager.setThreshold(chainId2, 3); + nttManager.setThreshold(chainId3, 2); + assertEq(3, nttManager.getThreshold(chainId2)); + assertEq(2, nttManager.getThreshold(chainId3)); + + // Disabling should reduce the threshold if necessary. + nttManager.disableRecvTransceiver(chainId2, address(e3)); + assertEq(2, nttManager.getThreshold(chainId2)); + assertEq(2, nttManager.getThreshold(chainId3)); + + // But disabling should not reduce the threshold if it's not necessary. + nttManager.setThreshold(chainId2, 1); + assertEq(1, nttManager.getThreshold(chainId2)); + nttManager.disableRecvTransceiver(chainId2, address(e2)); + assertEq(1, nttManager.getThreshold(chainId2)); + assertEq(2, nttManager.getThreshold(chainId3)); + + // Threshold should go to zero when we disable the last one. + nttManager.disableRecvTransceiver(chainId2, address(e1)); + assertEq(0, nttManager.getThreshold(chainId2)); + assertEq(2, nttManager.getThreshold(chainId3)); + } + + function test_chainsEnabledForReceive() public { + uint16[] memory chains = nttManager.getChainsEnabledForReceive(); + assertEq(0, chains.length); + + nttManager.addChainEnabledForReceive(42); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(1, chains.length); + assertEq(42, chains[0]); + + nttManager.addChainEnabledForReceive(41); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(2, chains.length); + assertEq(42, chains[0]); + assertEq(41, chains[1]); + + nttManager.addChainEnabledForReceive(43); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(3, chains.length); + assertEq(42, chains[0]); + assertEq(41, chains[1]); + assertEq(43, chains[2]); + + // Adding the same thing again shouldn't do anything. + nttManager.addChainEnabledForReceive(43); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(3, chains.length); + assertEq(42, chains[0]); + assertEq(41, chains[1]); + assertEq(43, chains[2]); + + nttManager.addChainEnabledForReceive(41); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(3, chains.length); + assertEq(42, chains[0]); + assertEq(41, chains[1]); + assertEq(43, chains[2]); + + // Add one more. + nttManager.addChainEnabledForReceive(44); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(4, chains.length); + assertEq(42, chains[0]); + assertEq(41, chains[1]); + assertEq(43, chains[2]); + assertEq(44, chains[3]); + + // Now test removing. + + // Remove one from the middle. The last one should get moved into its slot. + nttManager.removeChainEnabledForReceive(41); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(3, chains.length); + assertEq(42, chains[0]); + assertEq(44, chains[1]); + assertEq(43, chains[2]); + + // Removing something not in the list shouldn't do anything. + nttManager.removeChainEnabledForReceive(410); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(3, chains.length); + assertEq(42, chains[0]); + assertEq(44, chains[1]); + assertEq(43, chains[2]); + + // Remove the first one. The last one should get moved into its slot. + nttManager.removeChainEnabledForReceive(42); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(2, chains.length); + assertEq(43, chains[0]); + assertEq(44, chains[1]); + + // Remove the last one. + nttManager.removeChainEnabledForReceive(44); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(1, chains.length); + assertEq(43, chains[0]); + + // Remove the only one. + nttManager.removeChainEnabledForReceive(43); + chains = nttManager.getChainsEnabledForReceive(); + assertEq(0, chains.length); + } +} diff --git a/evm/test/TransceiverStructs.t.sol b/evm/test/TransceiverStructs.t.sol index 178871522..b71524736 100644 --- a/evm/test/TransceiverStructs.t.sol +++ b/evm/test/TransceiverStructs.t.sol @@ -5,7 +5,6 @@ import "forge-std/Test.sol"; import "../src/libraries/TransceiverStructs.sol"; import "../src/interfaces/IManagerBase.sol"; -import "../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; import "../src/interfaces/INttManager.sol"; contract TestTransceiverStructs is Test { diff --git a/evm/test/Upgrades.t.sol b/evm/test/Upgrades.t.sol deleted file mode 100644 index 6819ee279..000000000 --- a/evm/test/Upgrades.t.sol +++ /dev/null @@ -1,710 +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/interfaces/INttManager.sol"; -import "../src/interfaces/IManagerBase.sol"; -import "../src/interfaces/IRateLimiter.sol"; -import "../src/interfaces/IRateLimiterEvents.sol"; -import "../src/libraries/external/OwnableUpgradeable.sol"; -import "../src/libraries/external/Initializable.sol"; -import "../src/libraries/Implementation.sol"; -import {Utils} from "./libraries/Utils.sol"; -import {DummyToken, DummyTokenMintAndBurn} from "./NttManager.t.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"; - -contract TestUpgrades is Test, IRateLimiterEvents { - NttManager nttManagerChain1; - NttManager nttManagerChain2; - - using TrimmedAmountLib for uint256; - using TrimmedAmountLib for TrimmedAmount; - - uint16 constant chainId1 = 7; - uint16 constant chainId2 = 100; - - uint16 constant SENDING_CHAIN_ID = 1; - uint256 constant DEVNET_GUARDIAN_PK = - 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; - WormholeSimulator guardian; - uint256 initialBlockTimestamp; - uint8 constant FAST_CONSISTENCY_LEVEL = 200; - uint256 constant GAS_LIMIT = 500000; - - WormholeTransceiver wormholeTransceiverChain1; - WormholeTransceiver wormholeTransceiverChain2; - 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 virtual { - 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(); - - WormholeTransceiver wormholeTransceiverChain1Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain1 = MockWormholeTransceiverContract( - address(new ERC1967Proxy(address(wormholeTransceiverChain1Implementation), "")) - ); - wormholeTransceiverChain1.initialize(); - - nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1)); - nttManagerChain1.setOutboundLimit(type(uint64).max); - nttManagerChain1.setInboundLimit(type(uint64).max, chainId2); - - // 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(); - - 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( - chainId2, - bytes32(uint256(uint160(address(nttManagerChain2)))), - DummyToken(nttManagerChain2.token()).decimals(), - type(uint64).max - ); - nttManagerChain2.setPeer( - chainId1, - bytes32(uint256(uint160(address(nttManagerChain1)))), - DummyToken(nttManagerChain1.token()).decimals(), - type(uint64).max - ); - - wormholeTransceiverChain1.setWormholePeer( - chainId2, bytes32(uint256(uint160((address(wormholeTransceiverChain2))))) - ); - wormholeTransceiverChain2.setWormholePeer( - chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1)))) - ); - - nttManagerChain1.setThreshold(1); - nttManagerChain2.setThreshold(1); - vm.chainId(chainId1); - } - - function test_basicUpgradeNttManager() public { - // Basic call to upgrade with the same contact as ewll - NttManager newImplementation = new MockNttManagerContract( - address(nttManagerChain1.token()), IManagerBase.Mode.LOCKING, chainId1, 1 days, false - ); - nttManagerChain1.upgrade(address(newImplementation)); - - basicFunctionality(); - } - - //Upgradability stuff for transceivers is real borked because of some missing implementation. Test this later once fixed. - function test_basicUpgradeTransceiver() public { - // Basic call to upgrade with the same contact as well - WormholeTransceiver wormholeTransceiverChain1Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain1.upgrade(address(wormholeTransceiverChain1Implementation)); - - basicFunctionality(); - } - - // Confirm that we can handle multiple upgrades as a nttManager - function test_doubleUpgradeNttManager() public { - // Basic call to upgrade with the same contact as ewll - NttManager newImplementation = new MockNttManagerContract( - address(nttManagerChain1.token()), IManagerBase.Mode.LOCKING, chainId1, 1 days, false - ); - nttManagerChain1.upgrade(address(newImplementation)); - basicFunctionality(); - - newImplementation = new MockNttManagerContract( - address(nttManagerChain1.token()), IManagerBase.Mode.LOCKING, chainId1, 1 days, false - ); - nttManagerChain1.upgrade(address(newImplementation)); - - basicFunctionality(); - } - - // NOTE: There are additional tests in `Upgrades.t.sol` to verifying downgrading from `NttManagerNoRateLimiting` to `NttManager`. - - function test_cannotUpgradeToNoRateLimitingIfItWasEnabled() public { - // The default set up has rate limiting enabled. When we attempt to upgrade to no rate limiting, the immutable check should panic. - NttManager rateLimitingImplementation = new MockNttManagerNoRateLimitingContract( - address(nttManagerChain1.token()), IManagerBase.Mode.LOCKING, chainId1 - ); - - vm.expectRevert(); // Reverts with a panic on the assert. So, no way to tell WHY this happened. - nttManagerChain1.upgrade(address(rateLimitingImplementation)); - } - - function test_upgradeToNoRateLimiting() public { - // Create a standard manager with rate limiting disabled. - DummyToken t = new DummyToken(); - NttManager implementation = - new MockNttManagerContract(address(t), IManagerBase.Mode.LOCKING, chainId1, 0, true); - - MockNttManagerContract thisNttManager = - MockNttManagerContract(address(new ERC1967Proxy(address(implementation), ""))); - thisNttManager.initialize(); - - thisNttManager.setPeer(chainId2, toWormholeFormat(address(0x1)), 9, type(uint64).max); - - // Upgrade from NttManager with rate limiting disabled to NttManagerNoRateLimiting. - NttManager rateLimitingImplementation = new MockNttManagerNoRateLimitingContract( - address(t), IManagerBase.Mode.LOCKING, chainId1 - ); - thisNttManager.upgrade(address(rateLimitingImplementation)); - basicFunctionality(); - - // Upgrade from NttManagerNoRateLimiting to NttManagerNoRateLimiting. - rateLimitingImplementation = new MockNttManagerNoRateLimitingContract( - address(t), IManagerBase.Mode.LOCKING, chainId1 - ); - thisNttManager.upgrade(address(rateLimitingImplementation)); - basicFunctionality(); - - // Upgrade from NttManagerNoRateLimiting back to NttManager. - NttManager nttManagerImplementation = - new MockNttManagerContract(address(t), IManagerBase.Mode.LOCKING, chainId1, 0, true); - thisNttManager.upgrade(address(nttManagerImplementation)); - basicFunctionality(); - } - - //Upgradability stuff for transceivers is real borked because of some missing implementation. Test this later once fixed. - function test_doubleUpgradeTransceiver() public { - // Basic call to upgrade with the same contact as well - WormholeTransceiver wormholeTransceiverChain1Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain1.upgrade(address(wormholeTransceiverChain1Implementation)); - - basicFunctionality(); - - // Basic call to upgrade with the same contact as well - wormholeTransceiverChain1.upgrade(address(wormholeTransceiverChain1Implementation)); - - basicFunctionality(); - } - - function test_storageSlotNttManager() public { - // Basic call to upgrade with the same contact as ewll - NttManager newImplementation = new MockNttManagerStorageLayoutChange( - address(nttManagerChain1.token()), IManagerBase.Mode.LOCKING, chainId1, 1 days, false - ); - nttManagerChain1.upgrade(address(newImplementation)); - - address oldOwner = nttManagerChain1.owner(); - MockNttManagerStorageLayoutChange(address(nttManagerChain1)).setData(); - - // If we overrode something important, it would probably break here - basicFunctionality(); - - require(oldOwner == nttManagerChain1.owner(), "Owner changed in an unintended way."); - } - - function test_storageSlotTransceiver() public { - // Basic call to upgrade with the same contact as ewll - WormholeTransceiver newImplementation = new MockWormholeTransceiverLayoutChange( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain1.upgrade(address(newImplementation)); - - address oldOwner = nttManagerChain1.owner(); - MockWormholeTransceiverLayoutChange(address(wormholeTransceiverChain1)).setData(); - - // If we overrode something important, it would probably break here - basicFunctionality(); - - require(oldOwner == nttManagerChain1.owner(), "Owner changed in an unintended way."); - } - - function test_callMigrateNttManager() public { - // Basic call to upgrade with the same contact as ewll - NttManager newImplementation = new MockNttManagerMigrateBasic( - address(nttManagerChain1.token()), IManagerBase.Mode.LOCKING, chainId1, 1 days, false - ); - - vm.expectRevert("Proper migrate called"); - nttManagerChain1.upgrade(address(newImplementation)); - - basicFunctionality(); - } - - //Upgradability stuff for transceivers is real borked because of some missing implementation. Test this later once fixed. - function test_callMigrateTransceiver() public { - // Basic call to upgrade with the same contact as well - MockWormholeTransceiverMigrateBasic wormholeTransceiverChain1Implementation = new MockWormholeTransceiverMigrateBasic( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - vm.expectRevert("Proper migrate called"); - wormholeTransceiverChain1.upgrade(address(wormholeTransceiverChain1Implementation)); - - basicFunctionality(); - } - - function test_immutableBlockUpdateFailureNttManager() public { - DummyToken tnew = new DummyToken(); - - // Basic call to upgrade with the same contact as ewll - NttManager newImplementation = new MockNttManagerImmutableCheck( - address(tnew), IManagerBase.Mode.LOCKING, chainId1, 1 days, false - ); - - vm.expectRevert(); // Reverts with a panic on the assert. So, no way to tell WHY this happened. - nttManagerChain1.upgrade(address(newImplementation)); - - require(nttManagerChain1.token() != address(tnew), "Token updated when it shouldn't be"); - - basicFunctionality(); - } - - function test_immutableBlockUpdateFailureTransceiver() public { - // Don't allow upgrade to work with a change immutable - - address oldNttManager = wormholeTransceiverChain1.nttManager(); - WormholeTransceiver wormholeTransceiverChain1Implementation = new MockWormholeTransceiverMigrateBasic( - address(nttManagerChain2), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - vm.expectRevert(); // Reverts with a panic on the assert. So, no way to tell WHY this happened. - wormholeTransceiverChain1.upgrade(address(wormholeTransceiverChain1Implementation)); - - require( - wormholeTransceiverChain1.nttManager() == oldNttManager, - "NttManager updated when it shouldn't be" - ); - } - - function test_immutableBlockUpdateSuccessNttManager() public { - DummyToken tnew = new DummyToken(); - - // Basic call to upgrade with the same contact as ewll - NttManager newImplementation = new MockNttManagerImmutableRemoveCheck( - address(tnew), IManagerBase.Mode.LOCKING, chainId1, 1 days, false - ); - - // Allow an upgrade, since we enabled the ability to edit the immutables within the code - nttManagerChain1.upgrade(address(newImplementation)); - require(nttManagerChain1.token() == address(tnew), "Token not updated"); - - basicFunctionality(); - } - - function test_immutableBlockUpdateSuccessTransceiver() public { - WormholeTransceiver wormholeTransceiverChain1Implementation = new MockWormholeTransceiverImmutableAllow( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - - //vm.expectRevert(); // Reverts with a panic on the assert. So, no way to tell WHY this happened. - wormholeTransceiverChain1.upgrade(address(wormholeTransceiverChain1Implementation)); - - require( - wormholeTransceiverChain1.nttManager() == address(nttManagerChain1), - "NttManager updated when it shouldn't be" - ); - } - - function test_authNttManager() public { - // User not owner so this should fail - vm.prank(userA); - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, userA) - ); - nttManagerChain1.upgrade(address(0x1)); - - // Basic call to upgrade so that we can get the real implementation. - NttManager newImplementation = new MockNttManagerContract( - address(nttManagerChain1.token()), IManagerBase.Mode.LOCKING, chainId1, 1 days, false - ); - nttManagerChain1.upgrade(address(newImplementation)); - - basicFunctionality(); // Ensure that the upgrade was proper - - vm.expectRevert(abi.encodeWithSelector(Implementation.NotMigrating.selector)); - nttManagerChain1.migrate(); - - // Test if we can 'migrate' from this point - // Migrate without delegatecall - vm.expectRevert(abi.encodeWithSelector(Implementation.OnlyDelegateCall.selector)); - newImplementation.migrate(); - - // Transfer the ownership - shouldn't have permission for that - vm.prank(userA); - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, userA) - ); - nttManagerChain1.transferOwnership(address(0x1)); - - // Should fail because it's already initialized - vm.expectRevert(Initializable.InvalidInitialization.selector); - nttManagerChain1.initialize(); - - // Should fail because we're calling the implementation directly instead of the proxy. - vm.expectRevert(Implementation.OnlyDelegateCall.selector); - newImplementation.initialize(); - } - - function test_authTransceiver() public { - // User not owner so this should fail - vm.prank(userA); - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, userA) - ); - wormholeTransceiverChain1.upgrade(address(0x01)); - - // Basic call so that we can easily see what the new transceiver is. - WormholeTransceiver wormholeTransceiverChain1Implementation = new MockWormholeTransceiverContract( - address(nttManagerChain1), - address(wormhole), - address(relayer), - address(0x0), - FAST_CONSISTENCY_LEVEL, - GAS_LIMIT - ); - wormholeTransceiverChain1.upgrade(address(wormholeTransceiverChain1Implementation)); - basicFunctionality(); // Ensure that the upgrade was proper - - // Test if we can 'migrate' from this point - // Migrate without delegatecall - vm.expectRevert(abi.encodeWithSelector(Implementation.OnlyDelegateCall.selector)); - wormholeTransceiverChain1Implementation.migrate(); - - // Migrate - should fail since we're executing something outside of a migration - vm.expectRevert(abi.encodeWithSelector(Implementation.NotMigrating.selector)); - wormholeTransceiverChain1.migrate(); - - // Transfer the ownership - shouldn't have permission for that - vm.prank(userA); - vm.expectRevert( - abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, userA) - ); - wormholeTransceiverChain1.transferOwnership(address(0x1)); - - // Should fail because it's already initialized - vm.expectRevert(Initializable.InvalidInitialization.selector); - wormholeTransceiverChain1.initialize(); - - // // Should fail because we're calling the implementation directly instead of the proxy. - vm.expectRevert(Implementation.OnlyDelegateCall.selector); - wormholeTransceiverChain1Implementation.initialize(); - } - - function test_nonZeroWormholeFee() public { - // Set the message fee to be non-zero - vm.chainId(11155111); // Sepolia testnet id - uint256 fee = 0.000001e18; - guardian.setMessageFee(fee); - uint256 balanceBefore = address(userA).balance; - basicFunctionality(); - uint256 balanceAfter = address(userA).balance; - assertEq(balanceAfter + fee, balanceBefore); - } - - function basicFunctionality() 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); - vm.startPrank(userA); - token1.approve(address(nttManagerChain1), sendingAmount); - - vm.recordLogs(); - - // Fetch quote - (, uint256 totalQuote) = - nttManagerChain1.quoteDeliveryPrice(chainId2, encodeTransceiverInstruction(true)); - - // Send token through standard means (not relayer) - { - uint256 nttManagerBalanceBefore = token1.balanceOf(address(nttManagerChain1)); - uint256 userBalanceBefore = token1.balanceOf(address(userA)); - nttManagerChain1.transfer{value: totalQuote}( - sendingAmount, - chainId2, - toWormholeFormat(userB), - toWormholeFormat(userA), - false, - encodeTransceiverInstruction(true) - ); - - // 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 pipe. Thank you to whoever wrote this code in the past! - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(vm.getRecordedLogs()); - 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); - - // Wrong chain receiving the signed VAA - vm.expectRevert(abi.encodeWithSelector(InvalidFork.selector, chainId1, chainId2)); - wormholeTransceiverChain1.receiveMessage(encodedVMs[0]); - { - 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" - ); - } - - // Can't resubmit the same message twice - (IWormhole.VM memory wormholeVM,,) = wormhole.parseAndVerifyVM(encodedVMs[0]); - vm.expectRevert( - abi.encodeWithSelector( - IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash - ) - ); - wormholeTransceiverChain2.receiveMessage(encodedVMs[0]); - - // 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(); - - // Fetch quote - (, totalQuote) = - nttManagerChain2.quoteDeliveryPrice(chainId1, encodeTransceiverInstruction(true)); - - // Supply checks on the transfer - { - uint256 supplyBefore = token2.totalSupply(); - nttManagerChain2.transfer{value: totalQuote}( - 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()); - 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); - - { - uint256 supplyBefore = token1.totalSupply(); - uint256 userDBalanceBefore = token1.balanceOf(userD); - 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 + userDBalanceBefore, "User received funds" - ); - } - - vm.stopPrank(); - } - - 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); - } -} - -contract TestInitialize is Test { - function setUp() public {} - - NttManager nttManagerChain1; - NttManager nttManagerChain2; - - using TrimmedAmountLib for uint256; - using TrimmedAmountLib for TrimmedAmount; - - uint16 constant chainId1 = 7; - - uint256 constant DEVNET_GUARDIAN_PK = - 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; - - WormholeTransceiver wormholeTransceiverChain1; - address userA = address(0x123); - - address relayer = address(0x28D8F1Be96f97C1387e94A53e00eCcFb4E75175a); - IWormhole wormhole = IWormhole(0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78); - - function test_doubleInitialize() public { - string memory url = "https://ethereum-sepolia-rpc.publicnode.com"; - vm.createSelectFork(url); - - 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), ""))); - - // Initialize once - nttManagerChain1.initialize(); - - // Initialize twice - vm.expectRevert(Initializable.InvalidInitialization.selector); - nttManagerChain1.initialize(); - } - - function test_cannotFrontrunInitialize() public { - string memory url = "https://ethereum-sepolia-rpc.publicnode.com"; - vm.createSelectFork(url); - - 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), ""))); - - // Attempt to initialize the contract from a non-deployer account. - vm.prank(userA); - vm.expectRevert( - abi.encodeWithSelector(INttManager.UnexpectedDeployer.selector, address(this), userA) - ); - nttManagerChain1.initialize(); - } -} diff --git a/evm/test/interfaces/ITransceiverReceiver.sol b/evm/test/interfaces/ITransceiverReceiver.sol index 6e707a6e5..68e71bfb7 100644 --- a/evm/test/interfaces/ITransceiverReceiver.sol +++ b/evm/test/interfaces/ITransceiverReceiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache 2 pragma solidity >=0.8.8 <0.9.0; -interface ITransceiverReceiver { +interface IAdapterReceiver { function receiveMessage( bytes memory encodedMessage ) external; diff --git a/evm/test/libraries/IntegrationHelpers.sol b/evm/test/libraries/IntegrationHelpers.sol deleted file mode 100644 index 22f595805..000000000 --- a/evm/test/libraries/IntegrationHelpers.sol +++ /dev/null @@ -1,198 +0,0 @@ -// SPDX-License-Identifier: Apache 2 -pragma solidity 0.8.19; - -import "forge-std/Test.sol"; -import "forge-std/console.sol"; -import {WormholeTransceiver} from - "../../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; -import {DummyToken, DummyTokenMintAndBurn} from "../../src/mocks/DummyToken.sol"; -import "../../src/libraries/TrimmedAmount.sol"; -import {Utils} from "./../libraries/Utils.sol"; -import "../../src/libraries/TransceiverStructs.sol"; -import "wormhole-solidity-sdk/Utils.sol"; -import "../../src/interfaces/IWormholeTransceiver.sol"; -import {WormholeRelayerBasicTest} from "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; -import "../../src/NttManager/NttManager.sol"; -import "wormhole-solidity-sdk/testing/helpers/WormholeSimulator.sol"; - -contract IntegrationHelpers is Test { - using TrimmedAmountLib for uint256; - using TrimmedAmountLib for TrimmedAmount; - - WormholeTransceiver wormholeTransceiverChain1; - WormholeTransceiver wormholeTransceiverChain1Other; - WormholeTransceiver wormholeTransceiverChain2; - WormholeTransceiver wormholeTransceiverChain2Other; - - function buildTransceiverInstruction( - bool relayer_off - ) public view returns (TransceiverStructs.TransceiverInstruction memory) { - WormholeTransceiver.WormholeTransceiverInstruction memory instruction = - IWormholeTransceiver.WormholeTransceiverInstruction(relayer_off); - - bytes memory encodedInstructionWormhole; - // Source fork has id 0 and corresponds to chain 1 - if (vm.activeFork() == 0) { - encodedInstructionWormhole = - wormholeTransceiverChain1.encodeWormholeTransceiverInstruction(instruction); - } else { - encodedInstructionWormhole = - wormholeTransceiverChain2.encodeWormholeTransceiverInstruction(instruction); - } - return TransceiverStructs.TransceiverInstruction({ - index: 0, - payload: encodedInstructionWormhole - }); - } - - function encodeTransceiverInstruction( - bool relayer_off - ) public view returns (bytes memory) { - TransceiverStructs.TransceiverInstruction memory TransceiverInstruction = - buildTransceiverInstruction(relayer_off); - TransceiverStructs.TransceiverInstruction[] memory TransceiverInstructions = - new TransceiverStructs.TransceiverInstruction[](1); - TransceiverInstructions[0] = TransceiverInstruction; - return TransceiverStructs.encodeTransceiverInstructions(TransceiverInstructions); - } - - function _setTransceiverPeers( - WormholeTransceiver[2] memory transceivers, - WormholeTransceiver[2] memory transceiverPeers, - uint16[2] memory chainIds - ) internal { - for (uint256 i; i < transceivers.length; i++) { - transceivers[i].setWormholePeer( - chainIds[i], toWormholeFormat(address(transceiverPeers[i])) - ); - } - } - - function _setManagerPeer( - NttManager sourceManager, - NttManager peerManagerAddr, - uint16 peerChainId, - uint8 peerDecimals, - uint64 inboundLimit - ) internal { - sourceManager.setPeer( - peerChainId, toWormholeFormat(address(peerManagerAddr)), peerDecimals, inboundLimit - ); - } - - function _enableSR(WormholeTransceiver[2] memory transceivers, uint16 chainId) internal { - for (uint256 i; i < transceivers.length; i++) { - transceivers[i].setIsWormholeRelayingEnabled(chainId, true); - transceivers[i].setIsWormholeEvmChain(chainId, true); - } - } - - function _quotePrices( - WormholeTransceiver[] memory transceivers, - uint16 recipientChainId, - bool shouldSkipRelay - ) internal view returns (uint256) { - uint256 quoteSum; - for (uint256 i; i < transceivers.length; i++) { - quoteSum = quoteSum - + transceivers[i].quoteDeliveryPrice( - recipientChainId, buildTransceiverInstruction(shouldSkipRelay) - ); - } - - return quoteSum; - } - - // Setting up the transfer - function _prepareTransfer( - DummyToken token, - address user, - address contractAddr, - uint256 amount - ) internal { - vm.startPrank(user); - - token.mintDummy(user, amount); - token.approve(contractAddr, amount); - } - - function _computeManagerMessageDigest( - address from, - address to, - TrimmedAmount sendingAmount, - address tokenAddr, - uint16 sourceChainId, - uint16 recipientChainId - ) internal pure returns (bytes32) { - TransceiverStructs.NttManagerMessage memory nttManagerMessage; - nttManagerMessage = TransceiverStructs.NttManagerMessage( - 0, - toWormholeFormat(from), - TransceiverStructs.encodeNativeTokenTransfer( - TransceiverStructs.NativeTokenTransfer({ - amount: sendingAmount, - sourceToken: toWormholeFormat(tokenAddr), - to: toWormholeFormat(to), - toChain: recipientChainId, - additionalPayload: "" - }) - ) - ); - - return TransceiverStructs.nttManagerMessageDigest(sourceChainId, nttManagerMessage); - } - - function getTotalSupply(uint256 forkId, DummyToken token) public returns (uint256) { - vm.selectFork(forkId); - return token.totalSupply(); - } - - // Send token through standard relayer - function transferToken( - address to, - address refund, - NttManager sourceManager, - uint256 sendingAmount, - uint16 recipientChainId, - WormholeTransceiver[] memory transceivers, - bool relayer_off - ) public { - uint256 quoteSum = _quotePrices(transceivers, recipientChainId, relayer_off); - - // refund the amount back to the user that sent the transfer - sourceManager.transfer{value: quoteSum}( - sendingAmount, - recipientChainId, - toWormholeFormat(to), - toWormholeFormat(refund), - relayer_off, - encodeTransceiverInstruction(relayer_off) - ); - } - - function _getWormholeMessage( - WormholeSimulator guardian, - Vm.Log[] memory logs, - uint16 emitterChain - ) internal view returns (bytes[] memory) { - Vm.Log[] memory entries = guardian.fetchWormholeMessageFromLog(logs); - bytes[] memory encodedVMs = new bytes[](entries.length); - for (uint256 i = 0; i < encodedVMs.length; i++) { - encodedVMs[i] = guardian.fetchSignedMessageFromLogs(entries[i], emitterChain); - } - - return encodedVMs; - } - - function _receiveWormholeMessage( - IWormhole.VM memory vaa, - WormholeTransceiver sourceTransceiver, - WormholeTransceiver targetTransceiver, - uint16 emitterChainId, - bytes[] memory a - ) internal { - targetTransceiver.receiveWormholeMessages( - vaa.payload, a, toWormholeFormat(address(sourceTransceiver)), emitterChainId, vaa.hash - ); - } -} diff --git a/evm/test/libraries/NttManagerHelpers.sol b/evm/test/libraries/NttManagerHelpers.sol index dbef7b6fe..78ae46bff 100644 --- a/evm/test/libraries/NttManagerHelpers.sol +++ b/evm/test/libraries/NttManagerHelpers.sol @@ -2,15 +2,17 @@ pragma solidity >=0.8.8 <0.9.0; +import "forge-std/Test.sol"; + import "../../src/libraries/TrimmedAmount.sol"; import "../../src/NttManager/NttManager.sol"; import "../../src/interfaces/INttManager.sol"; library NttManagerHelpersLib { - uint16 constant SENDING_CHAIN_ID = 1; - using TrimmedAmountLib for TrimmedAmount; + uint128 public constant gasLimit = 100000000; + function setConfigs( TrimmedAmount inboundLimit, NttManager nttManager, @@ -26,8 +28,12 @@ library NttManagerHelpersLib { uint8 tokenDecimals = abi.decode(queriedDecimals, (uint8)); recipientNttManager.setPeer( - SENDING_CHAIN_ID, toWormholeFormat(address(nttManager)), tokenDecimals, type(uint64).max + nttManager.chainId(), + toWormholeFormat(address(nttManager)), + tokenDecimals, + gasLimit, + type(uint64).max ); - recipientNttManager.setInboundLimit(inboundLimit.untrim(decimals), SENDING_CHAIN_ID); + recipientNttManager.setInboundLimit(inboundLimit.untrim(decimals), nttManager.chainId()); } } diff --git a/evm/test/libraries/TransceiverHelpers.sol b/evm/test/libraries/TransceiverHelpers.sol index 7b7c39a4d..66a9573e9 100644 --- a/evm/test/libraries/TransceiverHelpers.sol +++ b/evm/test/libraries/TransceiverHelpers.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.8 <0.9.0; +import "wormhole-solidity-sdk/libraries/BytesParsing.sol"; import "./NttManagerHelpers.sol"; import "../mocks/DummyTransceiver.sol"; import "../../src/mocks/DummyToken.sol"; @@ -9,61 +10,127 @@ import "../../src/NttManager/NttManager.sol"; import "../../src/libraries/TrimmedAmount.sol"; library TransceiverHelpersLib { + using BytesParsing for bytes; using TrimmedAmountLib for TrimmedAmount; // 0x99'E''T''T' bytes4 constant TEST_TRANSCEIVER_PAYLOAD_PREFIX = 0x99455454; - uint16 constant SENDING_CHAIN_ID = 1; function setup_transceivers( - NttManager nttManager + NttManager nttManager, + uint16 peerChainId ) internal returns (DummyTransceiver, DummyTransceiver) { - DummyTransceiver e1 = new DummyTransceiver(address(nttManager)); - DummyTransceiver e2 = new DummyTransceiver(address(nttManager)); + DummyTransceiver e1 = + new DummyTransceiver(nttManager.chainId(), address(nttManager.endpoint())); + DummyTransceiver e2 = + new DummyTransceiver(nttManager.chainId(), address(nttManager.endpoint())); nttManager.setTransceiver(address(e1)); + nttManager.enableSendTransceiver(peerChainId, address(e1)); + nttManager.enableRecvTransceiver(peerChainId, address(e1)); + nttManager.setTransceiver(address(e2)); + nttManager.enableSendTransceiver(peerChainId, address(e2)); + nttManager.enableRecvTransceiver(peerChainId, address(e2)); + nttManager.setThreshold(peerChainId, 2); + return (e1, e2); + } + + function addTransceiver( + NttManager nttManager, + DummyTransceiver e1, + uint16 peerChainId + ) internal returns (DummyTransceiver, DummyTransceiver) { + DummyTransceiver e2 = + new DummyTransceiver(nttManager.chainId(), address(nttManager.endpoint())); nttManager.setTransceiver(address(e2)); - nttManager.setThreshold(2); + nttManager.enableSendTransceiver(peerChainId, address(e2)); + nttManager.enableRecvTransceiver(peerChainId, address(e2)); + nttManager.setThreshold(peerChainId, 2); return (e1, e2); } - function attestTransceiversHelper( + function transferAndAttest( address to, bytes32 id, - uint16 toChain, NttManager nttManager, NttManager recipientNttManager, TrimmedAmount amount, TrimmedAmount inboundLimit, - ITransceiverReceiver[] memory transceivers + DummyTransceiver[] memory transceivers ) internal returns ( - TransceiverStructs.NttManagerMessage memory, - TransceiverStructs.TransceiverMessage memory + TransceiverStructs.NttManagerMessage memory m, + bytes memory encodedM, + DummyTransceiver.Message memory rmsg ) { - TransceiverStructs.NttManagerMessage memory m = - buildNttManagerMessage(to, id, toChain, nttManager, amount); - bytes memory encodedM = TransceiverStructs.encodeNttManagerMessage(m); + m = buildNttManagerMessage(to, id, recipientNttManager.chainId(), nttManager, amount); + encodedM = TransceiverStructs.encodeNttManagerMessage(m); + prepTokenReceive(nttManager, recipientNttManager, amount, inboundLimit); + rmsg = attestMsg(nttManager, recipientNttManager, 0, transceivers, encodedM); + } + function transferAttestAndReceive( + address to, + bytes32 id, + NttManager nttManager, + NttManager recipientNttManager, + TrimmedAmount amount, + TrimmedAmount inboundLimit, + DummyTransceiver[] memory transceivers + ) + internal + returns ( + TransceiverStructs.NttManagerMessage memory m, + DummyTransceiver.Message memory rmsg + ) + { + m = buildNttManagerMessage(to, id, recipientNttManager.chainId(), nttManager, amount); + bytes memory encodedM = TransceiverStructs.encodeNttManagerMessage(m); prepTokenReceive(nttManager, recipientNttManager, amount, inboundLimit); + rmsg = attestAndReceiveMsg(nttManager, recipientNttManager, 0, transceivers, encodedM); + } - TransceiverStructs.TransceiverMessage memory em; - bytes memory encodedEm; - (em, encodedEm) = TransceiverStructs.buildAndEncodeTransceiverMessage( - TEST_TRANSCEIVER_PAYLOAD_PREFIX, - toWormholeFormat(address(nttManager)), - toWormholeFormat(address(recipientNttManager)), - encodedM, - new bytes(0) - ); + function attestMsg( + NttManager srcNttManager, + NttManager dstNttManager, + uint64 sequence, + DummyTransceiver[] memory transceivers, + bytes memory encodedM + ) internal returns (DummyTransceiver.Message memory rmsg) { + rmsg = DummyTransceiver.Message({ + srcChain: srcNttManager.chainId(), + srcAddr: UniversalAddressLibrary.fromAddress(address(srcNttManager)), + sequence: sequence, + dstChain: dstNttManager.chainId(), + dstAddr: UniversalAddressLibrary.fromAddress(address(dstNttManager)), + payloadHash: keccak256(encodedM), + refundAddr: address(0) + }); - for (uint256 i; i < transceivers.length; i++) { - ITransceiverReceiver e = transceivers[i]; - e.receiveMessage(encodedEm); + // Attest the message on all the transceivers. + uint8 numTrans = uint8(transceivers.length); + for (uint8 i; i < numTrans; i++) { + transceivers[i].receiveMessage(rmsg); } + } + + function attestAndReceiveMsg( + NttManager srcNttManager, + NttManager dstNttManager, + uint64 sequence, + DummyTransceiver[] memory transceivers, + bytes memory encodedM + ) internal returns (DummyTransceiver.Message memory rmsg) { + rmsg = attestMsg(srcNttManager, dstNttManager, sequence, transceivers, encodedM); - return (m, em); + // Execute the message. + dstNttManager.executeMsg( + srcNttManager.chainId(), + UniversalAddressLibrary.fromAddress(address(srcNttManager)), + 0, + encodedM + ); } function buildNttManagerMessage( @@ -123,4 +190,64 @@ library TransceiverHelpersLib { ); return (m, transceiverMessage); } + + error TransferSentEventNotFoundInLogs(uint64 nttSeqNo); + error ExecutorEventNotFoundInLogs(uint64 nttSeqNo, bytes32 payloadHash); + + function getExecutionSent( + Vm.Log[] memory events, + address nttManager, + uint64 nttSeqNo + ) public pure returns (bytes memory payload) { + // To find the payload bytes from the logs we need to do the following steps: + // 1. Look for the TransferSent event for our NttManager and sequence number. + // 2. Immediately following that should be the RequestForExecution event. + // 3. Extract the payload of that, which is an MMRequest. Parse that to get the NTT payload. + for (uint256 idx = 0; idx < events.length; ++idx) { + if ( + events[idx].topics[0] + == bytes32(0x75eb8927cc7c4810b30fa2e8011fce37da6da7d18eb82c642c367ae4445c3625) + && events[idx].emitter == nttManager + ) { + (,,, uint64 sequence,) = + abi.decode(events[idx].data, (uint256, uint256, uint16, uint64, bytes32)); + + if (sequence == nttSeqNo) { + // The next event in the log should be from the executor + if ( + (idx + 1 < events.length) + && ( + events[idx + 1].topics[0] + == bytes32( + 0xd870d87e4a7c33d0943b0a3d2822b174e239cc55c169af14cc56467a4489e3b5 + ) + ) + ) { + bytes memory execPayload; + (,,,,, execPayload,) = abi.decode( + events[idx + 1].data, + (uint256, uint16, bytes32, address, bytes, bytes, bytes) + ); + return decodeMMRequest(execPayload); + } + } + } + } + revert TransferSentEventNotFoundInLogs(nttSeqNo); + } + + /// @dev This decodes an ExecutorMessages.makeMMRequest and returns the enclosed payload. + function decodeMMRequest( + bytes memory execPayload + ) internal pure returns (bytes memory payload) { + uint256 offset = 0; + uint32 payloadLen; + + (, offset) = execPayload.asBytes4(offset); // msgType + (, offset) = execPayload.asUint16(offset); // srcChain + (, offset) = execPayload.asBytes32(offset); // srcAddr + (, offset) = execPayload.asUint64(offset); // seqNo + (payloadLen, offset) = execPayload.asUint32(offset); + (payload, offset) = execPayload.sliceUnchecked(offset, payloadLen); + } } diff --git a/evm/test/mocks/DummyTransceiver.sol b/evm/test/mocks/DummyTransceiver.sol index 52598acd0..24ea1a194 100644 --- a/evm/test/mocks/DummyTransceiver.sol +++ b/evm/test/mocks/DummyTransceiver.sol @@ -3,56 +3,73 @@ pragma solidity >=0.8.8 <0.9.0; import "forge-std/Test.sol"; -import "../../src/Transceiver/Transceiver.sol"; -import "../interfaces/ITransceiverReceiver.sol"; -contract DummyTransceiver is Transceiver, ITransceiverReceiver { - uint16 constant SENDING_CHAIN_ID = 1; +import "example-messaging-endpoint/evm/src/interfaces/IEndpointAdapter.sol"; +import "example-messaging-endpoint/evm/src/interfaces/IAdapter.sol"; + +contract DummyTransceiver is IAdapter { + uint16 public immutable chainId; + address public immutable endpoint; bytes4 constant TEST_TRANSCEIVER_PAYLOAD_PREFIX = 0x99455454; - constructor( - address nttManager - ) Transceiver(nttManager) {} + constructor(uint16 _chainId, address _endpoint) { + chainId = _chainId; + endpoint = _endpoint; + } - function getTransceiverType() external pure override returns (string memory) { + function getAdapterType() external pure returns (string memory) { return "dummy"; } - function _quoteDeliveryPrice( + function quoteDeliveryPrice( uint16, /* recipientChain */ - TransceiverStructs.TransceiverInstruction memory /* transceiverInstruction */ - ) internal pure override returns (uint256) { + bytes calldata /* adapterInstructions */ + ) external pure returns (uint256) { return 0; } - function _sendMessage( - uint16, /* recipientChain */ - uint256, /* deliveryPayment */ - address, /* caller */ - bytes32, /* recipientNttManagerAddress */ - bytes32, /* refundAddres */ - TransceiverStructs.TransceiverInstruction memory, /* instruction */ - bytes memory /* payload */ - ) internal override { - // do nothing + struct Message { + uint16 srcChain; + UniversalAddress srcAddr; + uint64 sequence; + uint16 dstChain; + UniversalAddress dstAddr; + bytes32 payloadHash; + address refundAddr; + } + + Message[] public messages; + + function getMessages() external view returns (Message[] memory) { + return messages; + } + + function sendMessage( + UniversalAddress srcAddr, + uint64 sequence, + uint16 dstChain, + UniversalAddress dstAddr, + bytes32 payloadHash, + address refundAddr, + bytes calldata // adapterInstructions + ) external payable { + Message memory m = Message({ + srcChain: chainId, + srcAddr: srcAddr, + sequence: sequence, + dstChain: dstChain, + dstAddr: dstAddr, + payloadHash: payloadHash, + refundAddr: refundAddr + }); + messages.push(m); } function receiveMessage( - bytes memory encodedMessage + Message memory m ) external { - TransceiverStructs.TransceiverMessage memory parsedTransceiverMessage; - TransceiverStructs.NttManagerMessage memory parsedNttManagerMessage; - (parsedTransceiverMessage, parsedNttManagerMessage) = TransceiverStructs - .parseTransceiverAndNttManagerMessage(TEST_TRANSCEIVER_PAYLOAD_PREFIX, encodedMessage); - _deliverToNttManager( - SENDING_CHAIN_ID, - parsedTransceiverMessage.sourceNttManagerAddress, - parsedTransceiverMessage.recipientNttManagerAddress, - parsedNttManagerMessage + IEndpointAdapter(endpoint).attestMessage( + m.srcChain, m.srcAddr, m.sequence, m.dstChain, m.dstAddr, m.payloadHash ); } - - function parseMessageFromLogs( - Vm.Log[] memory logs - ) public pure returns (uint16 recipientChain, bytes memory payload) {} } diff --git a/evm/test/mocks/MockEndpoint.sol b/evm/test/mocks/MockEndpoint.sol new file mode 100644 index 000000000..b19b03feb --- /dev/null +++ b/evm/test/mocks/MockEndpoint.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.19; + +import "example-messaging-endpoint/evm/src/Endpoint.sol"; +import "example-messaging-endpoint/evm/src/libraries/AdapterInstructions.sol"; + +contract MockEndpoint is Endpoint { + constructor( + uint16 _chainId + ) Endpoint(_chainId) {} + + function createAdapterInstructions() public pure returns (bytes memory encoded) { + AdapterInstructions.Instruction[] memory insts = new AdapterInstructions.Instruction[](0); + encoded = AdapterInstructions.encodeInstructions(insts); + } +} diff --git a/evm/test/mocks/MockExecutor.sol b/evm/test/mocks/MockExecutor.sol new file mode 100644 index 000000000..213033fd0 --- /dev/null +++ b/evm/test/mocks/MockExecutor.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.19; + +import "example-messaging-executor/evm/src/Executor.sol"; + +contract MockExecutor is Executor { + constructor( + uint16 _chainId + ) Executor(_chainId) {} + + function chainId() public view returns (uint16) { + return ourChain; + } + + // NOTE: This was copied from the tests in the executor repo. + function encodeSignedQuoteHeader( + Executor.SignedQuoteHeader memory signedQuote + ) public pure returns (bytes memory) { + return abi.encodePacked( + signedQuote.prefix, + signedQuote.quoterAddress, + signedQuote.payeeAddress, + signedQuote.srcChain, + signedQuote.dstChain, + signedQuote.expiryTime + ); + } + + function createSignedQuote( + uint16 dstChain + ) public view returns (bytes memory) { + return createSignedQuote(dstChain, 60); + } + + function createSignedQuote( + uint16 dstChain, + uint64 quoteLife + ) public view returns (bytes memory) { + Executor.SignedQuoteHeader memory signedQuote = IExecutor.SignedQuoteHeader({ + prefix: "EQ01", + quoterAddress: address(0), + payeeAddress: bytes32(0), + srcChain: ourChain, + dstChain: dstChain, + expiryTime: uint64(block.timestamp + quoteLife) + }); + return encodeSignedQuoteHeader(signedQuote); + } + + function createRelayInstructions() public pure returns (bytes memory) { + return new bytes(0); + } + + function msgValue() public pure returns (uint256) { + return 0; + } +} diff --git a/evm/test/mocks/MockNttManager.sol b/evm/test/mocks/MockNttManager.sol index 6c3fd09fe..baa7fa78b 100644 --- a/evm/test/mocks/MockNttManager.sol +++ b/evm/test/mocks/MockNttManager.sol @@ -7,12 +7,14 @@ import "../../src/NttManager/NttManagerNoRateLimiting.sol"; contract MockNttManagerContract is NttManager { constructor( + address endpoint, + address executor, address token, Mode mode, uint16 chainId, uint64 rateLimitDuration, bool skipRateLimiting - ) NttManager(token, mode, chainId, rateLimitDuration, skipRateLimiting) {} + ) NttManager(endpoint, executor, token, mode, chainId, rateLimitDuration, skipRateLimiting) {} /// We create a dummy storage variable here with standard solidity slot assignment. /// Then we check that its assigned slot is 0, i.e. that the super contract doesn't @@ -25,14 +27,43 @@ contract MockNttManagerContract is NttManager { result := my_slot.slot } } + + function removeChainEnabledForReceive( + uint16 chain + ) public { + _removeChainEnabledForReceive(chain); + } + + function addChainEnabledForReceive( + uint16 chain + ) public { + _addChainEnabledForReceive(chain); + } + + function getChainsEnabledForReceive() public pure returns (uint16[] memory result) { + result = _getChainsEnabledForReceiveStorage(); + } + + function setGasLimitToZero( + uint16 peerChainId + ) external onlyOwner { + _getPeersStorage()[peerChainId].gasLimit = 0; + } + + struct OldNttManagerPeer { + bytes32 peerAddress; + uint8 tokenDecimals; + } } contract MockNttManagerNoRateLimitingContract is NttManagerNoRateLimiting { constructor( + address endpoint, + address executor, address token, Mode mode, uint16 chainId - ) NttManagerNoRateLimiting(token, mode, chainId) {} + ) NttManagerNoRateLimiting(endpoint, executor, token, mode, chainId) {} /// We create a dummy storage variable here with standard solidity slot assignment. /// Then we check that its assigned slot is 0, i.e. that the super contract doesn't @@ -50,16 +81,17 @@ contract MockNttManagerNoRateLimitingContract is NttManagerNoRateLimiting { contract MockNttManagerMigrateBasic is NttManager { // Call the parents constructor constructor( + address endpoint, + address executor, address token, Mode mode, uint16 chainId, uint64 rateLimitDuration, bool skipRateLimiting - ) NttManager(token, mode, chainId, rateLimitDuration, skipRateLimiting) {} + ) NttManager(endpoint, executor, token, mode, chainId, rateLimitDuration, skipRateLimiting) {} function _migrate() internal view override { _checkThresholdInvariants(); - _checkTransceiversInvariants(); revert("Proper migrate called"); } } @@ -67,23 +99,27 @@ contract MockNttManagerMigrateBasic is NttManager { contract MockNttManagerImmutableCheck is NttManager { // Call the parents constructor constructor( + address endpoint, + address executor, address token, Mode mode, uint16 chainId, uint64 rateLimitDuration, bool skipRateLimiting - ) NttManager(token, mode, chainId, rateLimitDuration, skipRateLimiting) {} + ) NttManager(endpoint, executor, token, mode, chainId, rateLimitDuration, skipRateLimiting) {} } contract MockNttManagerImmutableRemoveCheck is NttManager { // Call the parents constructor constructor( + address endpoint, + address executor, address token, Mode mode, uint16 chainId, uint64 rateLimitDuration, bool skipRateLimiting - ) NttManager(token, mode, chainId, rateLimitDuration, skipRateLimiting) {} + ) NttManager(endpoint, executor, token, mode, chainId, rateLimitDuration, skipRateLimiting) {} // Turns on the capability to EDIT the immutables function _migrate() internal override { @@ -98,12 +134,14 @@ contract MockNttManagerStorageLayoutChange is NttManager { // Call the parents constructor constructor( + address endpoint, + address executor, address token, Mode mode, uint16 chainId, uint64 rateLimitDuration, bool skipRateLimiting - ) NttManager(token, mode, chainId, rateLimitDuration, skipRateLimiting) {} + ) NttManager(endpoint, executor, token, mode, chainId, rateLimitDuration, skipRateLimiting) {} function setData() public { a = address(0x1); diff --git a/evm/test/mocks/MockNttManagerAdditionalPayload.sol b/evm/test/mocks/MockNttManagerAdditionalPayload.sol index a313c3d86..8ef9a9de8 100644 --- a/evm/test/mocks/MockNttManagerAdditionalPayload.sol +++ b/evm/test/mocks/MockNttManagerAdditionalPayload.sol @@ -6,10 +6,12 @@ import "../../src/NttManager/NttManagerNoRateLimiting.sol"; contract MockNttManagerAdditionalPayloadContract is NttManagerNoRateLimiting { constructor( + address endpoint, + address executor, address token, Mode mode, uint16 chainId - ) NttManagerNoRateLimiting(token, mode, chainId) {} + ) NttManagerNoRateLimiting(endpoint, executor, token, mode, chainId) {} event AdditionalPayloadSent(bytes payload); event AdditionalPayloadReceived(bytes payload); diff --git a/evm/test/mocks/MockTransceivers.sol b/evm/test/mocks/MockTransceivers.sol deleted file mode 100644 index 742270a18..000000000 --- a/evm/test/mocks/MockTransceivers.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: Apache 2 - -pragma solidity >=0.8.8 <0.9.0; - -import "../../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; - -contract MockWormholeTransceiverContract is WormholeTransceiver { - constructor( - address nttManager, - address wormholeCoreBridge, - address wormholeRelayerAddr, - address specialRelayerAddr, - uint8 _consistencyLevel, - uint256 _gasLimit - ) - WormholeTransceiver( - nttManager, - wormholeCoreBridge, - wormholeRelayerAddr, - specialRelayerAddr, - _consistencyLevel, - _gasLimit - ) - {} - - /// @dev Override the [`transferOwnership`] method from OwnableUpgradeable - /// to ensure owner of this contract is in sync with the onwer of the NttManager contract. - function transferOwnership( - address newOwner - ) public view override onlyOwner { - revert CannotTransferTransceiverOwnership(owner(), newOwner); - } -} - -contract MockWormholeTransceiverMigrateBasic is WormholeTransceiver { - constructor( - address nttManager, - address wormholeCoreBridge, - address wormholeRelayerAddr, - address specialRelayerAddr, - uint8 _consistencyLevel, - uint256 _gasLimit - ) - WormholeTransceiver( - nttManager, - wormholeCoreBridge, - wormholeRelayerAddr, - specialRelayerAddr, - _consistencyLevel, - _gasLimit - ) - {} - - function _migrate() internal pure override { - revert("Proper migrate called"); - } -} - -contract MockWormholeTransceiverImmutableAllow is WormholeTransceiver { - constructor( - address nttManager, - address wormholeCoreBridge, - address wormholeRelayerAddr, - address specialRelayerAddr, - uint8 _consistencyLevel, - uint256 _gasLimit - ) - WormholeTransceiver( - nttManager, - wormholeCoreBridge, - wormholeRelayerAddr, - specialRelayerAddr, - _consistencyLevel, - _gasLimit - ) - {} - - // Allow for the immutables to be migrated - function _migrate() internal override { - _setMigratesImmutables(true); - } -} - -contract MockWormholeTransceiverLayoutChange is WormholeTransceiver { - address a; - address b; - address c; - - // Call the parents constructor - constructor( - address nttManager, - address wormholeCoreBridge, - address wormholeRelayerAddr, - address specialRelayerAddr, - uint8 _consistencyLevel, - uint256 _gasLimit - ) - WormholeTransceiver( - nttManager, - wormholeCoreBridge, - wormholeRelayerAddr, - specialRelayerAddr, - _consistencyLevel, - _gasLimit - ) - {} - - function setData() public { - a = address(0x1); - b = address(0x2); - c = address(0x3); - } -}