diff --git a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol index ee900001e..37b7180f8 100644 --- a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol +++ b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol @@ -192,6 +192,11 @@ contract WormholeTransceiver is new bytes(0) ); + // Verify that the transceiver message is small enough to be posted on Solana. + if (encodedTransceiverPayload.length > MAX_PAYLOAD_SIZE) { + revert ExceedsMaxPayloadSize(encodedTransceiverPayload.length, MAX_PAYLOAD_SIZE); + } + WormholeTransceiverInstruction memory weIns = parseWormholeTransceiverInstruction(instruction.payload); diff --git a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol index d0ec1a5e2..ccc1f70c1 100644 --- a/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol +++ b/evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol @@ -22,6 +22,12 @@ abstract contract WormholeTransceiverState is IWormholeTransceiverState, Transce using BooleanFlagLib for BooleanFlag; // ==================== Immutables =============================================== + + /// @dev Maximum payload size for any message. Since posting a message on Solana has a + /// maximum size, all messages are restricted to this size. If this program is + /// only used on EVM chains, this restriction can be removed. + uint16 public constant MAX_PAYLOAD_SIZE = 850; + uint8 public immutable consistencyLevel; IWormhole public immutable wormhole; IWormholeRelayer public immutable wormholeRelayer; @@ -34,7 +40,7 @@ abstract contract WormholeTransceiverState is IWormholeTransceiverState, Transce /// @dev Prefix for all TransceiverMessage payloads /// This is 0x99'E''W''H' - /// @notice Magic string (constant value set by messaging provider) that idenfies the payload as an transceiver-emitted payload. + /// @notice Magic string (constant value set by messaging provider) that identifies 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; diff --git a/evm/src/interfaces/IWormholeTransceiver.sol b/evm/src/interfaces/IWormholeTransceiver.sol index ba2de4dd2..c81c650ef 100644 --- a/evm/src/interfaces/IWormholeTransceiver.sol +++ b/evm/src/interfaces/IWormholeTransceiver.sol @@ -56,6 +56,12 @@ interface IWormholeTransceiver is IWormholeTransceiverState { /// @param vaaHash The hash of the VAA. error TransferAlreadyCompleted(bytes32 vaaHash); + /// @notice Error when the payload size exceeds the maximum allowed size. + /// @dev Selector: 0xf39ac4ba. + /// @param payloadSize The size of the payload. + /// @param maxPayloadSize The maximum allowed size. + error ExceedsMaxPayloadSize(uint256 payloadSize, uint256 maxPayloadSize); + /// @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. diff --git a/evm/src/interfaces/IWormholeTransceiverState.sol b/evm/src/interfaces/IWormholeTransceiverState.sol index 48ff10f0d..bc4088077 100644 --- a/evm/src/interfaces/IWormholeTransceiverState.sol +++ b/evm/src/interfaces/IWormholeTransceiverState.sol @@ -127,4 +127,7 @@ interface IWormholeTransceiverState { /// @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; + + /// @notice Returns the maximum payload size for a Wormhole Transceiver message. + function MAX_PAYLOAD_SIZE() external view returns (uint16); } diff --git a/evm/test/NonFungibleNttManager.t.sol b/evm/test/NonFungibleNttManager.t.sol index 5e79fa78d..5e308a5ca 100644 --- a/evm/test/NonFungibleNttManager.t.sol +++ b/evm/test/NonFungibleNttManager.t.sol @@ -61,10 +61,11 @@ contract TestNonFungibleNttManager is Test { address nft, IManagerBase.Mode _mode, uint16 _chainId, - bool shouldInitialize + bool shouldInitialize, + uint8 _tokenIdWidth ) internal returns (INonFungibleNttManager) { NonFungibleNttManager implementation = - new NonFungibleNttManager(address(nft), tokenIdWidth, _mode, _chainId); + new NonFungibleNttManager(address(nft), _tokenIdWidth, _mode, _chainId); NonFungibleNttManager proxy = NonFungibleNttManager(address(new ERC1967Proxy(address(implementation), ""))); @@ -112,12 +113,15 @@ contract TestNonFungibleNttManager is Test { nftTwo = new DummyNftMintAndBurn(bytes("https://metadata.dn420.com/y/")); // Managers. - managerOne = - deployNonFungibleManager(address(nftOne), IManagerBase.Mode.LOCKING, chainIdOne, true); - managerTwo = - deployNonFungibleManager(address(nftTwo), IManagerBase.Mode.BURNING, chainIdTwo, true); - managerThree = - deployNonFungibleManager(address(nftOne), IManagerBase.Mode.BURNING, chainIdThree, true); + managerOne = deployNonFungibleManager( + address(nftOne), IManagerBase.Mode.LOCKING, chainIdOne, true, tokenIdWidth + ); + managerTwo = deployNonFungibleManager( + address(nftTwo), IManagerBase.Mode.BURNING, chainIdTwo, true, tokenIdWidth + ); + managerThree = deployNonFungibleManager( + address(nftOne), IManagerBase.Mode.BURNING, chainIdThree, true, tokenIdWidth + ); // Wormhole Transceivers. transceiverOne = deployWormholeTranceiver(address(managerOne)); @@ -214,8 +218,9 @@ contract TestNonFungibleNttManager is Test { function test_cannotInitalizeNotDeployer() public { // Don't initialize. vm.prank(owner); - INonFungibleNttManager dummyManager = - deployNonFungibleManager(address(nftOne), IManagerBase.Mode.LOCKING, chainIdOne, false); + INonFungibleNttManager dummyManager = deployNonFungibleManager( + address(nftOne), IManagerBase.Mode.LOCKING, chainIdOne, false, tokenIdWidth + ); vm.prank(makeAddr("notOwner")); vm.expectRevert( @@ -630,6 +635,38 @@ contract TestNonFungibleNttManager is Test { ); } + function test_cannotTransferPayloadSizeExceeded() public { + // Deploy manager with 32 byte tokenIdWidth. + INonFungibleNttManager manager = deployNonFungibleManager( + address(nftOne), IManagerBase.Mode.BURNING, chainIdThree, true, 32 + ); + WormholeTransceiver transceiver = deployWormholeTranceiver(address(manager)); + transceiver.setWormholePeer(chainIdTwo, toWormholeFormat(makeAddr("random"))); + manager.setTransceiver(address(transceiver)); + manager.setPeer(chainIdTwo, toWormholeFormat(makeAddr("random"))); + + // Since the NonFungibleNtt payload is currently 180 bytes (without the tokenIds), + // we should be able to cause the error by transferring with 21 tokenIds. + // floor((850 - 180) / 33) + 1 (32 bytes per tokenId, 1 byte for length). + uint256 nftCount = 21; + uint256 startId = 0; + + address recipient = makeAddr("recipient"); + uint256[] memory tokenIds = _mintNftBatch(nftOne, recipient, nftCount, startId); + + vm.startPrank(recipient); + nftOne.setApprovalForAll(address(managerOne), true); + + vm.expectRevert( + abi.encodeWithSelector( + IWormholeTransceiver.ExceedsMaxPayloadSize.selector, + 873, + transceiver.MAX_PAYLOAD_SIZE() + ) + ); + manager.transfer(tokenIds, chainIdTwo, toWormholeFormat(recipient), new bytes(1)); + } + function test_cannotTransferDuplicateNfts() public { uint256 nftCount = 2; uint256 startId = 0; @@ -1067,4 +1104,3 @@ contract TestNonFungibleNttManager is Test { // TODO: // 1) Relayer test -// 2) Add max payload size and associated tests