From 62e866884296128f815f7fc9ff91df0c2e695f6c Mon Sep 17 00:00:00 2001 From: gator-boi Date: Thu, 21 Mar 2024 09:46:05 -0500 Subject: [PATCH] evm: separate manager execution cost --- .../NativeTransfers/NonFungibleNttManager.sol | 17 +- evm/src/NativeTransfers/NttManager.sol | 17 +- .../NativeTransfers/shared/ManagerBase.sol | 23 +- evm/src/Transceiver/Transceiver.sol | 13 +- .../WormholeTransceiver.sol | 14 +- .../WormholeTransceiverState.sol | 6 +- evm/src/interfaces/IManagerBase.sol | 9 - evm/src/interfaces/INonFungibleNttManager.sol | 9 + evm/src/interfaces/INttManager.sol | 9 + evm/src/interfaces/ITransceiver.sol | 12 +- evm/test/IntegrationRelayer.t.sol | 10 +- evm/test/IntegrationStandalone.t.sol | 1 - .../NonFungibleNttIntegrationRelayer.t.sol | 132 +++++++ evm/test/NonFungibleNttManager.t.sol | 347 ++++-------------- evm/test/RateLimit.t.sol | 8 +- .../NonFungibleNttManagerHelpers.sol | 241 ++++++++++++ evm/test/mocks/DummyTransceiver.sol | 4 +- 17 files changed, 558 insertions(+), 314 deletions(-) create mode 100755 evm/test/NonFungibleNttIntegrationRelayer.t.sol create mode 100644 evm/test/libraries/NonFungibleNttManagerHelpers.sol diff --git a/evm/src/NativeTransfers/NonFungibleNttManager.sol b/evm/src/NativeTransfers/NonFungibleNttManager.sol index e9304d864..eee9a56e2 100644 --- a/evm/src/NativeTransfers/NonFungibleNttManager.sol +++ b/evm/src/NativeTransfers/NonFungibleNttManager.sol @@ -110,6 +110,19 @@ contract NonFungibleNttManager is INonFungibleNttManager, ManagerBase { // =============== External Interface ================================================== + function quoteDeliveryPrice( + uint16 recipientChain, + bytes memory transceiverInstructions + ) public view virtual returns (uint256[] memory, uint256) { + address[] memory enabledTransceivers = _getEnabledTransceiversStorage(); + + TransceiverStructs.TransceiverInstruction[] memory instructions = TransceiverStructs + .parseTransceiverInstructions(transceiverInstructions, enabledTransceivers.length); + + // TODO: Compute execution cost here. + return _quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers, 0); + } + function transfer( uint256[] memory tokenIds, uint16 recipientChain, @@ -213,12 +226,13 @@ contract NonFungibleNttManager is INonFungibleNttManager, ManagerBase { } // Fetch quotes and prepare for transfer. + // TODO: compute execution cost here. ( address[] memory enabledTransceivers, TransceiverStructs.TransceiverInstruction[] memory instructions, uint256[] memory priceQuotes, uint256 totalPriceQuote - ) = _prepareForTransfer(recipientChain, transceiverInstructions); + ) = _prepareForTransfer(recipientChain, transceiverInstructions, 0); uint64 sequence = _useMessageSequence(); @@ -242,6 +256,7 @@ contract NonFungibleNttManager is INonFungibleNttManager, ManagerBase { recipientChain, _getPeersStorage()[recipientChain].peerAddress, priceQuotes, + 0, instructions, enabledTransceivers, encodedNttManagerPayload diff --git a/evm/src/NativeTransfers/NttManager.sol b/evm/src/NativeTransfers/NttManager.sol index ad0e22e3e..f783d21e8 100644 --- a/evm/src/NativeTransfers/NttManager.sol +++ b/evm/src/NativeTransfers/NttManager.sol @@ -142,6 +142,19 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { // ==================== External Interface =============================================== + /// @inheritdoc INttManager + function quoteDeliveryPrice( + uint16 recipientChain, + bytes memory transceiverInstructions + ) public view virtual returns (uint256[] memory, uint256) { + address[] memory enabledTransceivers = _getEnabledTransceiversStorage(); + + TransceiverStructs.TransceiverInstruction[] memory instructions = TransceiverStructs + .parseTransceiverInstructions(transceiverInstructions, enabledTransceivers.length); + + return _quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers, 0); + } + /// @inheritdoc INttManager function transfer( uint256 amount, @@ -394,12 +407,13 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { address sender, bytes memory transceiverInstructions ) internal returns (uint64 msgSequence) { + // TODO: compute execution cost here. ( address[] memory enabledTransceivers, TransceiverStructs.TransceiverInstruction[] memory instructions, uint256[] memory priceQuotes, uint256 totalPriceQuote - ) = _prepareForTransfer(recipientChain, transceiverInstructions); + ) = _prepareForTransfer(recipientChain, transceiverInstructions, 0); // push it on the stack again to avoid a stack too deep error uint64 seq = sequence; @@ -422,6 +436,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase { recipientChain, _getPeersStorage()[recipientChain].peerAddress, priceQuotes, + 0, instructions, enabledTransceivers, encodedNttManagerPayload diff --git a/evm/src/NativeTransfers/shared/ManagerBase.sol b/evm/src/NativeTransfers/shared/ManagerBase.sol index 5ba9f62da..070e05753 100644 --- a/evm/src/NativeTransfers/shared/ManagerBase.sol +++ b/evm/src/NativeTransfers/shared/ManagerBase.sol @@ -84,14 +84,14 @@ abstract contract ManagerBase is } } - // =============== External Logic ============================================================= + // =============== Internal Logic =========================================================== - /// @inheritdoc IManagerBase - function quoteDeliveryPrice( + function _quoteDeliveryPrice( uint16 recipientChain, TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions, - address[] memory enabledTransceivers - ) public view returns (uint256[] memory, uint256) { + address[] memory enabledTransceivers, + uint256 managerExecutionCost + ) internal view returns (uint256[] memory, uint256) { uint256 numEnabledTransceivers = enabledTransceivers.length; mapping(address => TransceiverInfo) storage transceiverInfos = _getTransceiverInfosStorage(); @@ -101,7 +101,7 @@ abstract contract ManagerBase is address transceiverAddr = enabledTransceivers[i]; uint8 registeredTransceiverIndex = transceiverInfos[transceiverAddr].index; uint256 transceiverPriceQuote = ITransceiver(transceiverAddr).quoteDeliveryPrice( - recipientChain, transceiverInstructions[registeredTransceiverIndex] + recipientChain, transceiverInstructions[registeredTransceiverIndex], managerExecutionCost ); priceQuotes[i] = transceiverPriceQuote; totalPriceQuote += transceiverPriceQuote; @@ -109,8 +109,6 @@ abstract contract ManagerBase is return (priceQuotes, totalPriceQuote); } - // =============== Internal Logic =========================================================== - function _recordTransceiverAttestation( uint16 sourceChainId, TransceiverStructs.ManagerMessage memory payload @@ -160,6 +158,7 @@ abstract contract ManagerBase is uint16 recipientChain, bytes32 peerAddress, uint256[] memory priceQuotes, + uint256 managerExecutionCost, TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions, address[] memory enabledTransceivers, bytes memory ManagerMessage @@ -179,14 +178,16 @@ abstract contract ManagerBase is recipientChain, transceiverInstructions[transceiverInfos[transceiverAddr].index], ManagerMessage, - peerAddress + peerAddress, + managerExecutionCost ); } } function _prepareForTransfer( uint16 recipientChain, - bytes memory transceiverInstructions + bytes memory transceiverInstructions, + uint256 managerExecutionCost ) internal returns ( @@ -214,7 +215,7 @@ abstract contract ManagerBase is } (uint256[] memory priceQuotes, uint256 totalPriceQuote) = - quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers); + _quoteDeliveryPrice(recipientChain, instructions, enabledTransceivers, managerExecutionCost); { // check up front that msg.value will cover the delivery price if (msg.value < totalPriceQuote) { diff --git a/evm/src/Transceiver/Transceiver.sol b/evm/src/Transceiver/Transceiver.sol index 3a7197ffc..37b934818 100644 --- a/evm/src/Transceiver/Transceiver.sol +++ b/evm/src/Transceiver/Transceiver.sol @@ -95,9 +95,10 @@ abstract contract Transceiver is /// @inheritdoc ITransceiver function quoteDeliveryPrice( uint16 targetChain, - TransceiverStructs.TransceiverInstruction memory instruction + TransceiverStructs.TransceiverInstruction memory instruction, + uint256 managerExecutionCost ) external view returns (uint256) { - return _quoteDeliveryPrice(targetChain, instruction); + return _quoteDeliveryPrice(targetChain, instruction, managerExecutionCost); } /// @inheritdoc ITransceiver @@ -105,11 +106,13 @@ abstract contract Transceiver is uint16 recipientChain, TransceiverStructs.TransceiverInstruction memory instruction, bytes memory ManagerMessage, - bytes32 recipientNttManagerAddress + bytes32 recipientNttManagerAddress, + uint256 managerExecutionCost ) external payable nonReentrant onlyNttManager { _sendMessage( recipientChain, msg.value, + managerExecutionCost, msg.sender, recipientNttManagerAddress, instruction, @@ -122,6 +125,7 @@ abstract contract Transceiver is function _sendMessage( uint16 recipientChain, uint256 deliveryPayment, + uint256 managerExecutionCost, address caller, bytes32 recipientNttManagerAddress, TransceiverStructs.TransceiverInstruction memory transceiverInstruction, @@ -147,6 +151,7 @@ abstract contract Transceiver is function _quoteDeliveryPrice( uint16 targetChain, - TransceiverStructs.TransceiverInstruction memory transceiverInstruction + TransceiverStructs.TransceiverInstruction memory transceiverInstruction, + uint256 managerExecutionCost ) internal view virtual returns (uint256); } diff --git a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol index 37b7180f8..51f70ea89 100644 --- a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol +++ b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol @@ -39,7 +39,7 @@ contract WormholeTransceiver is address wormholeRelayerAddr, address specialRelayerAddr, uint8 _consistencyLevel, - uint256 _gasLimit, + uint256 _attestationGasLimit, IWormholeTransceiverState.ManagerType _managerType ) WormholeTransceiverState( @@ -48,7 +48,7 @@ contract WormholeTransceiver is wormholeRelayerAddr, specialRelayerAddr, _consistencyLevel, - _gasLimit, + _attestationGasLimit, _managerType ) {} @@ -148,7 +148,8 @@ contract WormholeTransceiver is function _quoteDeliveryPrice( uint16 targetChain, - TransceiverStructs.TransceiverInstruction memory instruction + TransceiverStructs.TransceiverInstruction memory instruction, + uint256 managerExecutionCost ) 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 = @@ -163,7 +164,9 @@ contract WormholeTransceiver is } if (_shouldRelayViaStandardRelaying(targetChain)) { - (uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, 0, gasLimit); + (uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice( + targetChain, 0, attestationGasLimit + managerExecutionCost + ); return cost; } else if (isSpecialRelayingEnabled(targetChain)) { uint256 cost = specialRelayer.quoteDeliveryPrice(getNttManagerToken(), targetChain, 0); @@ -176,6 +179,7 @@ contract WormholeTransceiver is function _sendMessage( uint16 recipientChain, uint256 deliveryPayment, + uint256 managerExecutionCost, address caller, bytes32 recipientNttManagerAddress, TransceiverStructs.TransceiverInstruction memory instruction, @@ -206,7 +210,7 @@ contract WormholeTransceiver is fromWormholeFormat(getWormholePeer(recipientChain)), encodedTransceiverPayload, 0, - gasLimit + attestationGasLimit + managerExecutionCost ); emit RelayingInfo(uint8(RelayingType.Standard), deliveryPayment); diff --git a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol index ccc1f70c1..6db30b173 100644 --- a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol +++ b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol @@ -33,7 +33,7 @@ abstract contract WormholeTransceiverState is IWormholeTransceiverState, Transce IWormholeRelayer public immutable wormholeRelayer; ISpecialRelayer public immutable specialRelayer; uint256 immutable wormholeTransceiver_evmChainId; - uint256 public immutable gasLimit; + uint256 public immutable attestationGasLimit; ManagerType public immutable managerType; // ==================== Constants ================================================ @@ -58,7 +58,7 @@ abstract contract WormholeTransceiverState is IWormholeTransceiverState, Transce address wormholeRelayerAddr, address specialRelayerAddr, uint8 _consistencyLevel, - uint256 _gasLimit, + uint256 _attestationGasLimit, ManagerType _managerType ) Transceiver(nttManager) { wormhole = IWormhole(wormholeCoreBridge); @@ -66,7 +66,7 @@ abstract contract WormholeTransceiverState is IWormholeTransceiverState, Transce specialRelayer = ISpecialRelayer(specialRelayerAddr); wormholeTransceiver_evmChainId = block.chainid; consistencyLevel = _consistencyLevel; - gasLimit = _gasLimit; + attestationGasLimit = _attestationGasLimit; managerType = _managerType; } diff --git a/evm/src/interfaces/IManagerBase.sol b/evm/src/interfaces/IManagerBase.sol index 9c6826a19..f66390a8a 100644 --- a/evm/src/interfaces/IManagerBase.sol +++ b/evm/src/interfaces/IManagerBase.sol @@ -109,15 +109,6 @@ interface IManagerBase { /// @param chainId The target chain id error PeerNotRegistered(uint16 chainId); - /// @notice Fetch the delivery price for a given recipient chain transfer. - /// @param recipientChain The chain ID of the transfer destination. - /// @return - The delivery prices associated with each endpoint and the total price. - function quoteDeliveryPrice( - uint16 recipientChain, - TransceiverStructs.TransceiverInstruction[] memory transceiverInstructions, - address[] memory enabledTransceivers - ) external view returns (uint256[] memory, uint256); - /// @notice Sets the threshold for the number of attestations required for a message /// to be considered valid. /// @param threshold The new threshold. diff --git a/evm/src/interfaces/INonFungibleNttManager.sol b/evm/src/interfaces/INonFungibleNttManager.sol index 72a381ee3..8bd3ede59 100644 --- a/evm/src/interfaces/INonFungibleNttManager.sol +++ b/evm/src/interfaces/INonFungibleNttManager.sol @@ -80,6 +80,15 @@ interface INonFungibleNttManager is IManagerBase { function getMaxBatchSize() external pure returns (uint8); + /// @notice Fetch the delivery price for a given recipient chain transfer. + /// @param recipientChain The chain ID of the transfer destination. + /// @param transceiverInstructions The transceiver specific instructions for quoting and sending + /// @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); + function transfer( uint256[] memory tokenIds, uint16 recipientChain, diff --git a/evm/src/interfaces/INttManager.sol b/evm/src/interfaces/INttManager.sol index 5a874f913..a9178702c 100644 --- a/evm/src/interfaces/INttManager.sol +++ b/evm/src/interfaces/INttManager.sol @@ -98,6 +98,15 @@ interface INttManager is IManagerBase { /// @notice Peer cannot have zero decimals. error InvalidPeerDecimals(); + /// @notice Fetch the delivery price for a given recipient chain transfer. + /// @param recipientChain The chain ID of the transfer destination. + /// @param transceiverInstructions The transceiver specific instructions for quoting and sending + /// @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); + /// @notice Transfer a given amount to a recipient on a given chain. This function is called /// by the user to send the token cross-chain. This function will either lock or burn the /// sender's tokens. Finally, this function will call into registered `Endpoint` contracts diff --git a/evm/src/interfaces/ITransceiver.sol b/evm/src/interfaces/ITransceiver.sol index 3345783c3..76a01e9f2 100644 --- a/evm/src/interfaces/ITransceiver.sol +++ b/evm/src/interfaces/ITransceiver.sol @@ -41,11 +41,15 @@ interface ITransceiver { /// @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. + /// @param managerExecutionCost The cost of executing the manager message on the recipient chain. + /// Depending on the target chain, the units may vary. For example, on Ethereum, this is + /// the additional gas cost (in excess of the attestation cost) for executing the manager message. /// @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 + TransceiverStructs.TransceiverInstruction memory instruction, + uint256 managerExecutionCost ) external view returns (uint256); /// @dev Send a message to another chain. @@ -53,11 +57,15 @@ interface ITransceiver { /// @param instruction An additional Instruction provided by the Transceiver to be /// executed on the recipient chain. /// @param ManagerMessage A message to be sent to the nttManager on the recipient chain. + /// @param managerExecutionCost The cost of executing the manager message on the recipient chain. + /// Depending on the target chain, the units may vary. For example, on Ethereum, this is + /// the additional gas cost (in excess of the attestation cost) for executing the manager message. function sendMessage( uint16 recipientChain, TransceiverStructs.TransceiverInstruction memory instruction, bytes memory ManagerMessage, - bytes32 recipientNttManagerAddress + bytes32 recipientNttManagerAddress, + uint256 managerExecutionCost ) external payable; /// @notice Upgrades the transceiver to a new implementation. diff --git a/evm/test/IntegrationRelayer.t.sol b/evm/test/IntegrationRelayer.t.sol index 42b202b75..4fef58691 100755 --- a/evm/test/IntegrationRelayer.t.sol +++ b/evm/test/IntegrationRelayer.t.sol @@ -204,7 +204,7 @@ contract TestEndToEndRelayer is uint256 userBalanceBefore = token1.balanceOf(address(userA)); uint256 priceQuote1 = wormholeTransceiverChain1.quoteDeliveryPrice( - chainId2, buildTransceiverInstruction(false) + chainId2, buildTransceiverInstruction(false), 0 ); bytes memory instructions = encodeTransceiverInstruction(false); @@ -341,7 +341,7 @@ contract TestEndToEndRelayer is nttManagerChain1.transfer{ value: wormholeTransceiverChain1.quoteDeliveryPrice( - chainId2, buildTransceiverInstruction(false) + chainId2, buildTransceiverInstruction(false), 0 ) }( sendingAmount, @@ -393,7 +393,7 @@ contract TestEndToEndRelayer is supplyBefore = token2.totalSupply(); nttManagerChain2.transfer{ value: wormholeTransceiverChain2.quoteDeliveryPrice( - chainId1, buildTransceiverInstruction(false) + chainId1, buildTransceiverInstruction(false), 0 ) }( sendingAmount, @@ -574,7 +574,7 @@ contract TestRelayerEndToEndManual is TestEndToEndRelayerBase, IRateLimiterEvent vm.deal(userA, 1 ether); nttManagerChain1.transfer{ value: wormholeTransceiverChain1.quoteDeliveryPrice( - chainId2, buildTransceiverInstruction(false) + chainId2, buildTransceiverInstruction(false), 0 ) }( sendingAmount, @@ -705,7 +705,7 @@ contract TestRelayerEndToEndManual is TestEndToEndRelayerBase, IRateLimiterEvent vm.deal(userA, 1 ether); nttManagerChain1.transfer{ value: wormholeTransceiverChain1.quoteDeliveryPrice( - chainId2, buildTransceiverInstruction(false) + chainId2, buildTransceiverInstruction(false), 0 ) }( sendingAmount, diff --git a/evm/test/IntegrationStandalone.t.sol b/evm/test/IntegrationStandalone.t.sol index 80b8cedb0..c46545481 100755 --- a/evm/test/IntegrationStandalone.t.sol +++ b/evm/test/IntegrationStandalone.t.sol @@ -25,7 +25,6 @@ import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "wormhole-solidity-sdk/interfaces/IWormhole.sol"; import "wormhole-solidity-sdk/testing/helpers/WormholeSimulator.sol"; import "wormhole-solidity-sdk/Utils.sol"; -//import "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; contract TestEndToEndBase is Test, IRateLimiterEvents { NttManager nttManagerChain1; diff --git a/evm/test/NonFungibleNttIntegrationRelayer.t.sol b/evm/test/NonFungibleNttIntegrationRelayer.t.sol new file mode 100755 index 000000000..91218f5e3 --- /dev/null +++ b/evm/test/NonFungibleNttIntegrationRelayer.t.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity 0.8.19; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import "../src/interfaces/INonFungibleNttManager.sol"; +import "../src/interfaces/IManagerBase.sol"; +import "../src/interfaces/IWormholeTransceiverState.sol"; + +import "../src/NativeTransfers/NonFungibleNttManager.sol"; +import "../src/NativeTransfers/shared/TransceiverRegistry.sol"; +import "../src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol"; +import "../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; +import "./interfaces/ITransceiverReceiver.sol"; + +import "wormhole-solidity-sdk/interfaces/IWormhole.sol"; +import "wormhole-solidity-sdk/testing/helpers/WormholeSimulator.sol"; +import "wormhole-solidity-sdk/Utils.sol"; + +import "./libraries/TransceiverHelpers.sol"; +import "./libraries/NttManagerHelpers.sol"; +import "./libraries/NonFungibleNttManagerHelpers.sol"; +import {Utils} from "./libraries/Utils.sol"; +import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "../src/libraries/external/OwnableUpgradeable.sol"; +import "wormhole-solidity-sdk/libraries/BytesParsing.sol"; + +import "./mocks/MockTransceivers.sol"; +import "../src/mocks/DummyNft.sol"; + +import {WormholeRelayerBasicTest} from "wormhole-solidity-sdk/testing/WormholeRelayerTest.sol"; + +contract TestNonFungibleNttManagerWithRelayer is NonFungibleNttHelpers, WormholeRelayerBasicTest { + uint16 constant SOURCE_CHAIN_ID = 6; + uint16 constant TARGET_CHAIN_ID = 5; + uint8 constant FAST_CONSISTENCY_LEVEL = 200; + uint256 constant GAS_LIMIT = 500000; + uint8 constant TOKEN_ID_WIDTH = 2; + + DummyNftMintAndBurn sourceNft; + DummyNftMintAndBurn targetNft; + INonFungibleNttManager sourceManager; + INonFungibleNttManager targetManager; + WormholeTransceiver sourceTransceiver; + WormholeTransceiver targetTransceiver; + + address sender = makeAddr("sender"); + address recipient = makeAddr("recipient"); + + constructor() { + setTestnetForkChains(SOURCE_CHAIN_ID, TARGET_CHAIN_ID); + } + + function setUpSource() public override { + guardianSource = new WormholeSimulator(address(wormholeSource), DEVNET_GUARDIAN_PK); + sourceNft = new DummyNftMintAndBurn(bytes("https://metadata.dn69.com/y/")); + sourceManager = deployNonFungibleManager( + address(sourceNft), IManagerBase.Mode.LOCKING, SOURCE_CHAIN_ID, true, TOKEN_ID_WIDTH + ); + sourceTransceiver = deployWormholeTransceiver( + guardianSource, + address(sourceManager), + address(relayerSource), + FAST_CONSISTENCY_LEVEL, + GAS_LIMIT + ); + + sourceManager.setTransceiver(address(sourceTransceiver)); + sourceManager.setThreshold(1); + + vm.deal(sender, 10 ether); + } + + function setUpTarget() public override { + guardianTarget = new WormholeSimulator(address(wormholeTarget), DEVNET_GUARDIAN_PK); + targetNft = new DummyNftMintAndBurn(bytes("https://metadata.dn420.com/y/")); + targetManager = deployNonFungibleManager( + address(targetNft), IManagerBase.Mode.BURNING, TARGET_CHAIN_ID, true, TOKEN_ID_WIDTH + ); + targetTransceiver = deployWormholeTransceiver( + guardianTarget, + address(targetManager), + address(relayerTarget), + FAST_CONSISTENCY_LEVEL, + GAS_LIMIT + ); + + targetManager.setTransceiver(address(targetTransceiver)); + targetManager.setThreshold(1); + + vm.deal(recipient, 10 ether); + } + + /// TODO: Fuzz test this in the same way as `test_lockAndMint` in `NonFungibleNttManager.t.sol`. + function testRoundTripEvm() public { + // Cross-register the contracts. + { + vm.selectFork(sourceFork); + sourceTransceiver.setWormholePeer( + TARGET_CHAIN_ID, toWormholeFormat(address(targetTransceiver)) + ); + sourceTransceiver.setIsWormholeRelayingEnabled(TARGET_CHAIN_ID, true); + sourceTransceiver.setIsWormholeEvmChain(TARGET_CHAIN_ID, true); + sourceManager.setPeer(TARGET_CHAIN_ID, toWormholeFormat(address(targetManager))); + + vm.selectFork(targetFork); + targetTransceiver.setWormholePeer( + SOURCE_CHAIN_ID, toWormholeFormat(address(sourceTransceiver)) + ); + targetTransceiver.setIsWormholeRelayingEnabled(SOURCE_CHAIN_ID, true); + targetTransceiver.setIsWormholeEvmChain(SOURCE_CHAIN_ID, true); + targetManager.setPeer(SOURCE_CHAIN_ID, toWormholeFormat(address(sourceManager))); + } + + vm.selectFork(sourceFork); + vm.recordLogs(); + + // Mint a token on the source chain. + uint16 nftCount = 1; + uint256[] memory tokenIds = _mintNftBatch(sourceNft, sender, nftCount, 0); + + // Fetch quote and execute the transfer. + { + bytes memory transceiverInstructions = + _encodeTransceiverInstruction(false, sourceTransceiver); + (, uint256 quote) = + sourceManager.quoteDeliveryPrice(TARGET_CHAIN_ID, transceiverInstructions); + console.log("Quote: ", quote); + } + } +} diff --git a/evm/test/NonFungibleNttManager.t.sol b/evm/test/NonFungibleNttManager.t.sol index 5e308a5ca..0f8f63cfd 100644 --- a/evm/test/NonFungibleNttManager.t.sol +++ b/evm/test/NonFungibleNttManager.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache 2 pragma solidity >=0.8.8 <0.9.0; -import "forge-std/Test.sol"; import "forge-std/console.sol"; import "forge-std/Vm.sol"; @@ -21,6 +20,7 @@ import "wormhole-solidity-sdk/Utils.sol"; import "./libraries/TransceiverHelpers.sol"; import "./libraries/NttManagerHelpers.sol"; +import "./libraries/NonFungibleNttManagerHelpers.sol"; import {Utils} from "./libraries/Utils.sol"; import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "../src/libraries/external/OwnableUpgradeable.sol"; @@ -29,18 +29,17 @@ import "wormhole-solidity-sdk/libraries/BytesParsing.sol"; import "./mocks/MockTransceivers.sol"; import "../src/mocks/DummyNft.sol"; -contract TestNonFungibleNttManager is Test { +contract TestNonFungibleNttManager is NonFungibleNttHelpers { using BytesParsing for bytes; uint16 constant chainIdOne = 2; uint16 constant chainIdTwo = 6; uint16 constant chainIdThree = 10; - bytes4 constant WH_TRANSCEIVER_PAYLOAD_PREFIX = 0x9945FF10; uint256 constant DEVNET_GUARDIAN_PK = 0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0; WormholeSimulator guardian; - uint256 initialBlockTimestamp; - address relayer = 0x7B1bD7a6b4E61c2a123AC6BC2cbfC614437D0470; + + // Constructor args. uint8 consistencyLevel = 1; uint256 baseGasLimit = 500000; uint8 tokenIdWidth = 2; @@ -57,51 +56,10 @@ contract TestNonFungibleNttManager is Test { WormholeTransceiver transceiverTwo; WormholeTransceiver transceiverThree; - function deployNonFungibleManager( - address nft, - IManagerBase.Mode _mode, - uint16 _chainId, - bool shouldInitialize, - uint8 _tokenIdWidth - ) internal returns (INonFungibleNttManager) { - NonFungibleNttManager implementation = - new NonFungibleNttManager(address(nft), _tokenIdWidth, _mode, _chainId); - - NonFungibleNttManager proxy = - NonFungibleNttManager(address(new ERC1967Proxy(address(implementation), ""))); - - if (shouldInitialize) { - proxy.initialize(); - } - - return INonFungibleNttManager(address(proxy)); - } - - function deployWormholeTranceiver(address manager) internal returns (WormholeTransceiver) { - // Wormhole Transceivers. - WormholeTransceiver implementation = new WormholeTransceiver( - manager, - address(guardian.wormhole()), - relayer, - address(0), - consistencyLevel, - baseGasLimit, - IWormholeTransceiverState.ManagerType.ERC721 - ); - - WormholeTransceiver transceiverProxy = - WormholeTransceiver(address(new ERC1967Proxy(address(implementation), ""))); - - transceiverProxy.initialize(); - - return transceiverProxy; - } - function setUp() public { string memory url = "https://ethereum-sepolia-rpc.publicnode.com"; IWormhole wormhole = IWormhole(0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78); vm.createSelectFork(url); - initialBlockTimestamp = vm.getBlockTimestamp(); guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK); @@ -124,9 +82,15 @@ contract TestNonFungibleNttManager is Test { ); // Wormhole Transceivers. - transceiverOne = deployWormholeTranceiver(address(managerOne)); - transceiverTwo = deployWormholeTranceiver(address(managerTwo)); - transceiverThree = deployWormholeTranceiver(address(managerThree)); + transceiverOne = deployWormholeTransceiver( + guardian, address(managerOne), address(0), consistencyLevel, baseGasLimit + ); + transceiverTwo = deployWormholeTransceiver( + guardian, address(managerTwo), address(0), consistencyLevel, baseGasLimit + ); + transceiverThree = deployWormholeTransceiver( + guardian, address(managerThree), address(0), consistencyLevel, baseGasLimit + ); transceiverOne.setWormholePeer(chainIdTwo, toWormholeFormat(address(transceiverTwo))); transceiverTwo.setWormholePeer(chainIdOne, toWormholeFormat(address(transceiverOne))); @@ -154,8 +118,12 @@ contract TestNonFungibleNttManager is Test { vm.startPrank(owner); // Deploy two new transceivers. - WormholeTransceiver sourceTransceiver = deployWormholeTranceiver(address(sourceManager)); - WormholeTransceiver targetTransceiver = deployWormholeTranceiver(address(targetManager)); + WormholeTransceiver sourceTransceiver = deployWormholeTransceiver( + guardian, address(sourceManager), address(0), consistencyLevel, baseGasLimit + ); + WormholeTransceiver targetTransceiver = deployWormholeTransceiver( + guardian, address(targetManager), address(0), consistencyLevel, baseGasLimit + ); // Register transceivers and peers. sourceManager.setTransceiver(address(sourceTransceiver)); @@ -189,7 +157,7 @@ contract TestNonFungibleNttManager is Test { INonFungibleNttManager.InvalidTokenIdWidth.selector, _tokenIdWidth ) ); - NonFungibleNttManager implementation = new NonFungibleNttManager( + new NonFungibleNttManager( address(nftOne), _tokenIdWidth, IManagerBase.Mode.BURNING, chainIdOne ); } @@ -379,17 +347,19 @@ contract TestNonFungibleNttManager is Test { // Lock the NFTs on managerOne. bytes memory encodedVm = _approveAndTransferBatch( - managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true + guardian, managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true )[0]; - _verifyTransferPayload(encodedVm, managerOne, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, encodedVm, managerOne, recipient, chainIdTwo, tokenIds); // Receive the message and mint the NFTs on managerTwo. transceiverTwo.receiveMessage(encodedVm); // Verify state changes. The NFTs should still be locked on managerOne, and a new // batch of NFTs should be minted on managerTwo. - assertTrue(managerTwo.isMessageExecuted(_computeMessageDigest(chainIdOne, encodedVm))); + assertTrue( + managerTwo.isMessageExecuted(_computeMessageDigest(guardian, chainIdOne, encodedVm)) + ); assertTrue(_isBatchOwner(nftTwo, tokenIds, recipient), "Recipient should own NFTs"); assertTrue(_isBatchOwner(nftOne, tokenIds, address(managerOne)), "Manager should own NFTs"); } @@ -406,21 +376,25 @@ contract TestNonFungibleNttManager is Test { // Lock the NFTs on managerOne. bytes[] memory vms = _approveAndTransferBatch( - managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true + guardian, managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true ); assertEq(vms.length, 2); - _verifyTransferPayload(vms[0], managerOne, recipient, chainIdTwo, tokenIds); - _verifyTransferPayload(vms[1], managerOne, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, vms[0], managerOne, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, vms[1], managerOne, recipient, chainIdTwo, tokenIds); // Receive the message and mint the NFTs on managerTwo. transceiverTwo.receiveMessage(vms[0]); - assertFalse(managerTwo.isMessageExecuted(_computeMessageDigest(chainIdOne, vms[0]))); + assertFalse( + managerTwo.isMessageExecuted(_computeMessageDigest(guardian, chainIdOne, vms[0])) + ); multiTransceiverTwo.receiveMessage(vms[1]); // Verify state changes. The NFTs should still be locked on managerOne, and a new // batch of NFTs should be minted on managerTwo. - assertTrue(managerTwo.isMessageExecuted(_computeMessageDigest(chainIdOne, vms[0]))); + assertTrue( + managerTwo.isMessageExecuted(_computeMessageDigest(guardian, chainIdOne, vms[0])) + ); assertTrue(_isBatchOwner(nftTwo, tokenIds, recipient), "Recipient should own NFTs"); assertTrue(_isBatchOwner(nftOne, tokenIds, address(managerOne)), "Manager should own NFTs"); } @@ -445,16 +419,18 @@ contract TestNonFungibleNttManager is Test { // Burn the NFTs on managerTwo. bytes memory encodedVm = _approveAndTransferBatch( - managerTwo, transceiverTwo, nftTwo, tokenIds, recipient, chainIdOne, true + guardian, managerTwo, transceiverTwo, nftTwo, tokenIds, recipient, chainIdOne, true )[0]; - _verifyTransferPayload(encodedVm, managerTwo, recipient, chainIdOne, tokenIds); + _verifyTransferPayload(guardian, encodedVm, managerTwo, recipient, chainIdOne, tokenIds); // Receive the message and unlock the NFTs on managerOne. transceiverOne.receiveMessage(encodedVm); // Verify state changes. - assertTrue(managerOne.isMessageExecuted(_computeMessageDigest(chainIdTwo, encodedVm))); + assertTrue( + managerOne.isMessageExecuted(_computeMessageDigest(guardian, chainIdTwo, encodedVm)) + ); assertTrue(_isBatchBurned(nftTwo, tokenIds), "NFTs should be burned"); assertTrue(_isBatchOwner(nftOne, tokenIds, recipient), "Recipient should own NFTs"); } @@ -482,20 +458,24 @@ contract TestNonFungibleNttManager is Test { // Burn the NFTs on managerTwo. bytes[] memory vms = _approveAndTransferBatch( - managerTwo, transceiverTwo, nftTwo, tokenIds, recipient, chainIdOne, true + guardian, managerTwo, transceiverTwo, nftTwo, tokenIds, recipient, chainIdOne, true ); assertEq(vms.length, 2); - _verifyTransferPayload(vms[0], managerTwo, recipient, chainIdOne, tokenIds); - _verifyTransferPayload(vms[1], managerTwo, recipient, chainIdOne, tokenIds); + _verifyTransferPayload(guardian, vms[0], managerTwo, recipient, chainIdOne, tokenIds); + _verifyTransferPayload(guardian, vms[1], managerTwo, recipient, chainIdOne, tokenIds); // Receive the message and unlock the NFTs on managerOne. transceiverOne.receiveMessage(vms[0]); - assertFalse(managerOne.isMessageExecuted(_computeMessageDigest(chainIdTwo, vms[0]))); + assertFalse( + managerOne.isMessageExecuted(_computeMessageDigest(guardian, chainIdTwo, vms[0])) + ); multiTransceiverOne.receiveMessage(vms[1]); // Verify state changes. - assertTrue(managerOne.isMessageExecuted(_computeMessageDigest(chainIdTwo, vms[0]))); + assertTrue( + managerOne.isMessageExecuted(_computeMessageDigest(guardian, chainIdTwo, vms[0])) + ); assertTrue(_isBatchBurned(nftTwo, tokenIds), "NFTs should be burned"); assertTrue(_isBatchOwner(nftOne, tokenIds, recipient), "Recipient should own NFTs"); } @@ -509,17 +489,19 @@ contract TestNonFungibleNttManager is Test { // Burn the NFTs on managerThree. bytes memory encodedVm = _approveAndTransferBatch( - managerThree, transceiverThree, nftOne, tokenIds, recipient, chainIdTwo, true + guardian, managerThree, transceiverThree, nftOne, tokenIds, recipient, chainIdTwo, true )[0]; - _verifyTransferPayload(encodedVm, managerThree, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, encodedVm, managerThree, recipient, chainIdTwo, tokenIds); // Receive the message and mint the NFTs on managerTwo. transceiverTwo.receiveMessage(encodedVm); // Verify state changes. The NFTs should've been burned on managerThree, and a new // batch of NFTs should be minted on managerTwo. - assertTrue(managerTwo.isMessageExecuted(_computeMessageDigest(chainIdThree, encodedVm))); + assertTrue( + managerTwo.isMessageExecuted(_computeMessageDigest(guardian, chainIdThree, encodedVm)) + ); assertTrue(_isBatchBurned(nftOne, tokenIds), "NFTs should be burned"); assertTrue(_isBatchOwner(nftTwo, tokenIds, recipient), "Recipient should own NFTs"); } @@ -536,21 +518,25 @@ contract TestNonFungibleNttManager is Test { // Burn the NFTs on managerThree. bytes[] memory vms = _approveAndTransferBatch( - managerThree, transceiverThree, nftOne, tokenIds, recipient, chainIdTwo, true + guardian, managerThree, transceiverThree, nftOne, tokenIds, recipient, chainIdTwo, true ); assertEq(vms.length, 2); - _verifyTransferPayload(vms[0], managerThree, recipient, chainIdTwo, tokenIds); - _verifyTransferPayload(vms[1], managerThree, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, vms[0], managerThree, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, vms[1], managerThree, recipient, chainIdTwo, tokenIds); // Receive the message and mint the NFTs on managerTwo. transceiverTwo.receiveMessage(vms[0]); - assertFalse(managerTwo.isMessageExecuted(_computeMessageDigest(chainIdThree, vms[0]))); + assertFalse( + managerTwo.isMessageExecuted(_computeMessageDigest(guardian, chainIdThree, vms[0])) + ); multiTransceiverTwo.receiveMessage(vms[1]); // Verify state changes. The NFTs should've been burned on managerThree, and a new // batch of NFTs should be minted on managerTwo. - assertTrue(managerTwo.isMessageExecuted(_computeMessageDigest(chainIdThree, vms[0]))); + assertTrue( + managerTwo.isMessageExecuted(_computeMessageDigest(guardian, chainIdThree, vms[0])) + ); assertTrue(_isBatchBurned(nftOne, tokenIds), "NFTs should be burned"); assertTrue(_isBatchOwner(nftTwo, tokenIds, recipient), "Recipient should own NFTs"); } @@ -640,7 +626,9 @@ contract TestNonFungibleNttManager is Test { INonFungibleNttManager manager = deployNonFungibleManager( address(nftOne), IManagerBase.Mode.BURNING, chainIdThree, true, 32 ); - WormholeTransceiver transceiver = deployWormholeTranceiver(address(manager)); + WormholeTransceiver transceiver = deployWormholeTransceiver( + guardian, address(manager), address(0), consistencyLevel, baseGasLimit + ); transceiver.setWormholePeer(chainIdTwo, toWormholeFormat(makeAddr("random"))); manager.setTransceiver(address(transceiver)); manager.setPeer(chainIdTwo, toWormholeFormat(makeAddr("random"))); @@ -735,7 +723,7 @@ contract TestNonFungibleNttManager is Test { // Burn the NFTs on managerThree. bytes memory encodedVm = _approveAndTransferBatch( - managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdThree, true + guardian, managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdThree, true )[0]; // Receive the message and mint the NFTs on managerTwo. @@ -757,7 +745,7 @@ contract TestNonFungibleNttManager is Test { uint256[] memory tokenIds = _mintNftBatch(nftOne, recipient, nftCount, startId); bytes memory encodedVm = _approveAndTransferBatch( - managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true + guardian, managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true )[0]; // Parse the manager message. @@ -769,30 +757,29 @@ contract TestNonFungibleNttManager is Test { vm.expectRevert( abi.encodeWithSelector( IManagerBase.MessageNotApproved.selector, - _computeMessageDigest(chainIdOne, encodedVm) + _computeMessageDigest(guardian, chainIdOne, encodedVm) ) ); managerTwo.executeMsg(chainIdOne, toWormholeFormat(address(managerOne)), message); } - function test_cannotExecuteMessageNotApproveMultiTransceiver() public { + function test_cannotExecuteMessageNotApprovedMultiTransceiver() public { uint256 nftCount = 1; uint256 startId = 0; - WormholeTransceiver multiTransceiverTwo = - _setupMultiTransceiverManagers(managerOne, managerTwo); + _setupMultiTransceiverManagers(managerOne, managerTwo); address recipient = makeAddr("recipient"); uint256[] memory tokenIds = _mintNftBatch(nftOne, recipient, nftCount, startId); // Lock the NFTs on managerOne. bytes[] memory vms = _approveAndTransferBatch( - managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true + guardian, managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true ); assertEq(vms.length, 2); - _verifyTransferPayload(vms[0], managerOne, recipient, chainIdTwo, tokenIds); - _verifyTransferPayload(vms[1], managerOne, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, vms[0], managerOne, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, vms[1], managerOne, recipient, chainIdTwo, tokenIds); // Receive the message and mint the NFTs on managerTwo. transceiverTwo.receiveMessage(vms[0]); @@ -805,7 +792,8 @@ contract TestNonFungibleNttManager is Test { // Receive the message and mint the NFTs on managerTwo. vm.expectRevert( abi.encodeWithSelector( - IManagerBase.MessageNotApproved.selector, _computeMessageDigest(chainIdOne, vms[1]) + IManagerBase.MessageNotApproved.selector, + _computeMessageDigest(guardian, chainIdOne, vms[1]) ) ); managerTwo.executeMsg(chainIdOne, toWormholeFormat(address(managerOne)), message); @@ -819,7 +807,7 @@ contract TestNonFungibleNttManager is Test { uint256[] memory tokenIds = _mintNftBatch(nftOne, recipient, nftCount, startId); bytes memory encodedVm = _approveAndTransferBatch( - managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true + guardian, managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true )[0]; // Parse the manager message. @@ -848,7 +836,7 @@ contract TestNonFungibleNttManager is Test { // Send the batch to chainThree, but receive it on chainTwo. bytes memory encodedVm = _approveAndTransferBatch( - managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdThree, true + guardian, managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdThree, true )[0]; vm.expectRevert( @@ -868,7 +856,7 @@ contract TestNonFungibleNttManager is Test { // Lock the NFTs on managerOne. bytes memory encodedVm = _approveAndTransferBatch( - managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true + guardian, managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true )[0]; // Pause managerTwo. @@ -893,12 +881,12 @@ contract TestNonFungibleNttManager is Test { // Lock the NFTs on managerOne. bytes[] memory vms = _approveAndTransferBatch( - managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true + guardian, managerOne, transceiverOne, nftOne, tokenIds, recipient, chainIdTwo, true ); assertEq(vms.length, 2); - _verifyTransferPayload(vms[0], managerOne, recipient, chainIdTwo, tokenIds); - _verifyTransferPayload(vms[1], managerOne, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, vms[0], managerOne, recipient, chainIdTwo, tokenIds); + _verifyTransferPayload(guardian, vms[1], managerOne, recipient, chainIdTwo, tokenIds); // Receive the message and mint the NFTs on managerTwo. transceiverTwo.receiveMessage(vms[0]); @@ -930,177 +918,4 @@ contract TestNonFungibleNttManager is Test { ); nftOne.safeTransferFrom(recipient, address(managerOne), tokenIds[0]); } - - // ==================================== Helpers ======================================= - - function _isBatchOwner( - DummyNftMintAndBurn nft, - uint256[] memory tokenIds, - address _owner - ) internal view returns (bool) { - bool isOwner = true; - for (uint256 i = 0; i < tokenIds.length; i++) { - if (nft.ownerOf(tokenIds[i]) != _owner) { - isOwner = false; - break; - } - } - return isOwner; - } - - function _isBatchBurned( - DummyNftMintAndBurn nft, - uint256[] memory tokenIds - ) internal view returns (bool) { - bool isBurned = true; - for (uint256 i = 0; i < tokenIds.length; i++) { - if (nft.exists(tokenIds[i])) { - isBurned = false; - break; - } - } - return isBurned; - } - - function _verifyTransferPayload( - bytes memory transferMessage, - INonFungibleNttManager manager, - address recipient, - uint16 targetChain, - uint256[] memory tokenIds - ) internal { - // Verify the manager message - bytes memory vmPayload = guardian.wormhole().parseVM(transferMessage).payload; - (, TransceiverStructs.ManagerMessage memory message) = TransceiverStructs - .parseTransceiverAndManagerMessage(WH_TRANSCEIVER_PAYLOAD_PREFIX, vmPayload); - - assertEq(uint256(message.id), manager.nextMessageSequence() - 1); - assertEq(message.sender, toWormholeFormat(recipient)); - - // Verify the non-fungible transfer message. - TransceiverStructs.NonFungibleNativeTokenTransfer memory nftTransfer = - TransceiverStructs.parseNonFungibleNativeTokenTransfer(message.payload); - - assertEq(nftTransfer.to, toWormholeFormat(recipient)); - assertEq(nftTransfer.toChain, targetChain); - assertEq(nftTransfer.payload, new bytes(0)); - assertEq(nftTransfer.tokenIds.length, tokenIds.length); - - for (uint256 i = 0; i < tokenIds.length; i++) { - assertEq(nftTransfer.tokenIds[i], tokenIds[i]); - } - } - - function _computeMessageDigest( - uint16 sourceChain, - bytes memory encodedVm - ) internal view returns (bytes32 digest) { - // Parse the manager message. - bytes memory vmPayload = guardian.wormhole().parseVM(encodedVm).payload; - (, TransceiverStructs.ManagerMessage memory message) = TransceiverStructs - .parseTransceiverAndManagerMessage(WH_TRANSCEIVER_PAYLOAD_PREFIX, vmPayload); - - digest = TransceiverStructs.managerMessageDigest(sourceChain, message); - } - - function _approveAndTransferBatch( - INonFungibleNttManager manager, - WormholeTransceiver transceiver, - DummyNftMintAndBurn nft, - uint256[] memory tokenIds, - address recipient, - uint16 targetChain, - bool relayerOff - ) internal returns (bytes[] memory encodedVms) { - // Transfer NFTs as the owner of the NFTs. - vm.startPrank(recipient); - nft.setApprovalForAll(address(manager), true); - - vm.recordLogs(); - manager.transfer( - tokenIds, - targetChain, - toWormholeFormat(recipient), - _encodeTransceiverInstruction(relayerOff, transceiver) - ); - vm.stopPrank(); - - // Fetch the wormhole message. - encodedVms = _getWormholeMessage(vm.getRecordedLogs(), manager.chainId()); - } - - function _mintNftBatch( - DummyNftMintAndBurn nft, - address recipient, - uint256 len, - uint256 start - ) internal returns (uint256[] memory) { - uint256[] memory arr = new uint256[](len); - for (uint256 i = 0; i < len; i++) { - uint256 tokenId = start + i; - arr[i] = tokenId; - - nft.mint(recipient, tokenId); - } - return arr; - } - - function _createBatchTokenIds( - uint256 len, - uint256 start - ) internal pure returns (uint256[] memory) { - uint256[] memory arr = new uint256[](len); - for (uint256 i = 0; i < len; i++) { - arr[i] = start + i; - } - return arr; - } - - function _getWormholeMessage( - 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 _encodeTransceiverInstruction( - bool relayerOff, - WormholeTransceiver transceiver - ) internal view returns (bytes memory) { - WormholeTransceiver.WormholeTransceiverInstruction memory instruction = - IWormholeTransceiver.WormholeTransceiverInstruction(relayerOff); - bytes memory encodedInstructionWormhole = - transceiver.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); - } - - function _getMaxFromTokenIdWidth(uint8 tokenIdWidth) internal pure returns (uint256) { - if (tokenIdWidth == 1) { - return type(uint8).max; - } else if (tokenIdWidth == 2) { - return type(uint16).max; - } else if (tokenIdWidth == 4) { - return type(uint32).max; - } else if (tokenIdWidth == 8) { - return type(uint64).max; - } else if (tokenIdWidth == 16) { - return type(uint128).max; - } else if (tokenIdWidth == 32) { - return type(uint256).max; - } - } } - -// TODO: -// 1) Relayer test diff --git a/evm/test/RateLimit.t.sol b/evm/test/RateLimit.t.sol index adfa5f966..b34e52897 100644 --- a/evm/test/RateLimit.t.sol +++ b/evm/test/RateLimit.t.sol @@ -986,7 +986,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { ITransceiverReceiver[] memory transceivers = new ITransceiverReceiver[](1); transceivers[0] = e1; - TransceiverStructs.NttManagerMessage memory m; + TransceiverStructs.ManagerMessage memory m; bytes memory encodedEm; uint256 inboundLimit = inboundLimitAmt; TrimmedAmount trimmedAmount = packTrimmedAmount(uint64(amount), 8); @@ -1008,7 +1008,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { } bytes32 digest = - TransceiverStructs.nttManagerMessageDigest(TransceiverHelpersLib.SENDING_CHAIN_ID, m); + TransceiverStructs.managerMessageDigest(TransceiverHelpersLib.SENDING_CHAIN_ID, m); // no quorum yet assertEq(token.balanceOf(address(user_B)), 0); @@ -1077,9 +1077,7 @@ contract TestRateLimit is Test, IRateLimiterEvents { assertEq(entries[0].topics[1], toWormholeFormat(address(nttManager))); assertEq( entries[0].topics[2], - TransceiverStructs.nttManagerMessageDigest( - TransceiverHelpersLib.SENDING_CHAIN_ID, m - ) + TransceiverStructs.managerMessageDigest(TransceiverHelpersLib.SENDING_CHAIN_ID, m) ); } } diff --git a/evm/test/libraries/NonFungibleNttManagerHelpers.sol b/evm/test/libraries/NonFungibleNttManagerHelpers.sol new file mode 100644 index 000000000..32f568afc --- /dev/null +++ b/evm/test/libraries/NonFungibleNttManagerHelpers.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: Apache 2 + +pragma solidity >=0.8.8 <0.9.0; + +import "forge-std/Test.sol"; +import "forge-std/Vm.sol"; + +import "../../src/interfaces/INonFungibleNttManager.sol"; +import "../../src/interfaces/IManagerBase.sol"; +import "../../src/interfaces/IWormholeTransceiverState.sol"; + +import "wormhole-solidity-sdk/testing/helpers/WormholeSimulator.sol"; +import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +import "../../src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol"; +import "../../src/NativeTransfers/NonFungibleNttManager.sol"; + +import "../mocks/MockTransceivers.sol"; +import "../../src/mocks/DummyNft.sol"; + +contract NonFungibleNttHelpers is Test { + bytes4 constant WH_TRANSCEIVER_PAYLOAD_PREFIX = 0x9945FF10; + + function deployNonFungibleManager( + address nft, + IManagerBase.Mode _mode, + uint16 _chainId, + bool shouldInitialize, + uint8 _tokenIdWidth + ) public returns (INonFungibleNttManager) { + NonFungibleNttManager implementation = + new NonFungibleNttManager(address(nft), _tokenIdWidth, _mode, _chainId); + + NonFungibleNttManager proxy = + NonFungibleNttManager(address(new ERC1967Proxy(address(implementation), ""))); + + if (shouldInitialize) { + proxy.initialize(); + } + + return INonFungibleNttManager(address(proxy)); + } + + function deployWormholeTransceiver( + WormholeSimulator guardian, + address manager, + address relayer, + uint8 consistencyLevel, + uint256 baseGasLimit + ) public returns (WormholeTransceiver) { + // Wormhole Transceivers. + WormholeTransceiver implementation = new WormholeTransceiver( + manager, + address(guardian.wormhole()), + relayer, + address(0), + consistencyLevel, + baseGasLimit, + IWormholeTransceiverState.ManagerType.ERC721 + ); + + WormholeTransceiver transceiverProxy = + WormholeTransceiver(address(new ERC1967Proxy(address(implementation), ""))); + + transceiverProxy.initialize(); + + return transceiverProxy; + } + + function _isBatchOwner( + DummyNftMintAndBurn nft, + uint256[] memory tokenIds, + address _owner + ) internal view returns (bool) { + bool isOwner = true; + for (uint256 i = 0; i < tokenIds.length; i++) { + if (nft.ownerOf(tokenIds[i]) != _owner) { + isOwner = false; + break; + } + } + return isOwner; + } + + function _isBatchBurned( + DummyNftMintAndBurn nft, + uint256[] memory tokenIds + ) internal view returns (bool) { + bool isBurned = true; + for (uint256 i = 0; i < tokenIds.length; i++) { + if (nft.exists(tokenIds[i])) { + isBurned = false; + break; + } + } + return isBurned; + } + + function _verifyTransferPayload( + WormholeSimulator guardian, + bytes memory transferMessage, + INonFungibleNttManager manager, + address recipient, + uint16 targetChain, + uint256[] memory tokenIds + ) internal { + // Verify the manager message + bytes memory vmPayload = guardian.wormhole().parseVM(transferMessage).payload; + (, TransceiverStructs.ManagerMessage memory message) = TransceiverStructs + .parseTransceiverAndManagerMessage(WH_TRANSCEIVER_PAYLOAD_PREFIX, vmPayload); + + assertEq(uint256(message.id), manager.nextMessageSequence() - 1); + assertEq(message.sender, toWormholeFormat(recipient)); + + // Verify the non-fungible transfer message. + TransceiverStructs.NonFungibleNativeTokenTransfer memory nftTransfer = + TransceiverStructs.parseNonFungibleNativeTokenTransfer(message.payload); + + assertEq(nftTransfer.to, toWormholeFormat(recipient)); + assertEq(nftTransfer.toChain, targetChain); + assertEq(nftTransfer.payload, new bytes(0)); + assertEq(nftTransfer.tokenIds.length, tokenIds.length); + + for (uint256 i = 0; i < tokenIds.length; i++) { + assertEq(nftTransfer.tokenIds[i], tokenIds[i]); + } + } + + function _computeMessageDigest( + WormholeSimulator guardian, + uint16 sourceChain, + bytes memory encodedVm + ) internal view returns (bytes32 digest) { + // Parse the manager message. + bytes memory vmPayload = guardian.wormhole().parseVM(encodedVm).payload; + (, TransceiverStructs.ManagerMessage memory message) = TransceiverStructs + .parseTransceiverAndManagerMessage(WH_TRANSCEIVER_PAYLOAD_PREFIX, vmPayload); + + digest = TransceiverStructs.managerMessageDigest(sourceChain, message); + } + + function _approveAndTransferBatch( + WormholeSimulator guardian, + INonFungibleNttManager manager, + WormholeTransceiver transceiver, + DummyNftMintAndBurn nft, + uint256[] memory tokenIds, + address recipient, + uint16 targetChain, + bool relayerOff + ) internal returns (bytes[] memory encodedVms) { + // Transfer NFTs as the owner of the NFTs. + vm.startPrank(recipient); + nft.setApprovalForAll(address(manager), true); + + vm.recordLogs(); + manager.transfer( + tokenIds, + targetChain, + toWormholeFormat(recipient), + _encodeTransceiverInstruction(relayerOff, transceiver) + ); + vm.stopPrank(); + + // Fetch the wormhole message. + encodedVms = _getWormholeMessage(guardian, vm.getRecordedLogs(), manager.chainId()); + } + + function _mintNftBatch( + DummyNftMintAndBurn nft, + address recipient, + uint256 len, + uint256 start + ) internal returns (uint256[] memory) { + uint256[] memory arr = new uint256[](len); + for (uint256 i = 0; i < len; i++) { + uint256 tokenId = start + i; + arr[i] = tokenId; + + nft.mint(recipient, tokenId); + } + return arr; + } + + function _createBatchTokenIds( + uint256 len, + uint256 start + ) internal pure returns (uint256[] memory) { + uint256[] memory arr = new uint256[](len); + for (uint256 i = 0; i < len; i++) { + arr[i] = start + i; + } + return arr; + } + + 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 _encodeTransceiverInstruction( + bool relayerOff, + WormholeTransceiver transceiver + ) internal pure returns (bytes memory) { + WormholeTransceiver.WormholeTransceiverInstruction memory instruction = + IWormholeTransceiver.WormholeTransceiverInstruction(relayerOff); + bytes memory encodedInstructionWormhole = + transceiver.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); + } + + function _getMaxFromTokenIdWidth(uint8 tokenIdWidth) internal pure returns (uint256) { + if (tokenIdWidth == 1) { + return type(uint8).max; + } else if (tokenIdWidth == 2) { + return type(uint16).max; + } else if (tokenIdWidth == 4) { + return type(uint32).max; + } else if (tokenIdWidth == 8) { + return type(uint64).max; + } else if (tokenIdWidth == 16) { + return type(uint128).max; + } else if (tokenIdWidth == 32) { + return type(uint256).max; + } + } +} diff --git a/evm/test/mocks/DummyTransceiver.sol b/evm/test/mocks/DummyTransceiver.sol index 137b4067e..3c6829885 100644 --- a/evm/test/mocks/DummyTransceiver.sol +++ b/evm/test/mocks/DummyTransceiver.sol @@ -14,7 +14,8 @@ contract DummyTransceiver is Transceiver, ITransceiverReceiver { function _quoteDeliveryPrice( uint16, /* recipientChain */ - TransceiverStructs.TransceiverInstruction memory /* transceiverInstruction */ + TransceiverStructs.TransceiverInstruction memory, /* transceiverInstruction */ + uint256 /* managerExecutionCost */ ) internal pure override returns (uint256) { return 0; } @@ -22,6 +23,7 @@ contract DummyTransceiver is Transceiver, ITransceiverReceiver { function _sendMessage( uint16, /* recipientChain */ uint256, /* deliveryPayment */ + uint256, /* managerExecutionCost */ address, /* caller */ bytes32, /* recipientNttManagerAddress */ TransceiverStructs.TransceiverInstruction memory, /* instruction */