From e9e11deb2f74e058a835003cd2ffdee74e29e179 Mon Sep 17 00:00:00 2001 From: sujithsomraaj Date: Mon, 3 Jun 2024 00:00:12 +0530 Subject: [PATCH 1/3] feat: add support for wormhole + cctp --- .../automatic-relayer/WormholeHelper.sol | 91 ++++++++++++++ test/Wormhole.AutomaticRelayer.t.sol | 114 ++++++++++++++++++ 2 files changed, 205 insertions(+) diff --git a/src/wormhole/automatic-relayer/WormholeHelper.sol b/src/wormhole/automatic-relayer/WormholeHelper.sol index c66733b..b449587 100644 --- a/src/wormhole/automatic-relayer/WormholeHelper.sol +++ b/src/wormhole/automatic-relayer/WormholeHelper.sol @@ -24,12 +24,20 @@ interface IWormholeReceiver { ) external payable; } +interface IMessageTransmitter { + function attesterManager() external view returns (address); + function enableAttester(address newAttester) external; + function setSignatureThreshold(uint256 newSignatureThreshold) external; + function receiveMessage(bytes calldata message, bytes calldata attestation) external; +} + /// @title WormholeHelper /// @notice supports only automatic relayer (not specialized relayers) /// MORE INFO: https://docs.wormhole.com/wormhole/quick-start/cross-chain-dev/automatic-relayer contract WormholeHelper is Test { /// @dev is the default event selector if not specified by the user bytes32 constant MESSAGE_EVENT_SELECTOR = 0x6eb224fb001ed210e379b335e35efe88672a8ce935d981a6896b27ffdf52a3b2; + bytes32 constant CCTP_MESSAGE_EVENT_SELECTOR = 0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036; ////////////////////////////////////////////////////////////// // EXTERNAL FUNCTIONS // @@ -128,6 +136,89 @@ contract WormholeHelper is Test { } } + struct LocalCCTPVars { + uint256 prevForkId; + bytes cctpMessage; + bytes[] additionalMessage; + bytes32 digest; + uint8 v; + bytes32 r; + bytes32 s; + Vm.Log log; + uint64 sequence; + uint32 nonce; + bytes payload; + address dstAddress; + } + + /// @dev is a helper for https://docs.wormhole.com/wormhole/quick-start/tutorials/cctp + /// @param srcChainId represents the wormhole identifier for the source chain + /// @param dstForkId represents the dst fork id to deliver the message + /// @param expDstAddress represents the expected dst chain receiver of wormhole message + /// @param dstRelayer represents the wormhole dst relayer address + /// @param dstTransmitter represents the cctp dst transmitter address + /// @param logs represents the logs after message dispatch using sendToEvm + /// @notice supports only one CCTP transfer and sendToEvm per log + function helpWithCctpAndWormhole( + uint16 srcChainId, + uint256 dstForkId, + address expDstAddress, + address dstRelayer, + address dstTransmitter, + Vm.Log[] calldata logs + ) external { + LocalCCTPVars memory v; + v.prevForkId = vm.activeFork(); + v.additionalMessage = new bytes[](1); + vm.selectFork(dstForkId); + + /// @dev identifies the cctp transfer + for (uint256 i; i < logs.length; ++i) { + v.log = logs[i]; + if (v.log.topics[0] == CCTP_MESSAGE_EVENT_SELECTOR) { + v.cctpMessage = abi.decode(logs[i].data, (bytes)); + /// @dev prepare circle transmitter on dst chain + IMessageTransmitter messageTransmitter = IMessageTransmitter(dstTransmitter); + + vm.startPrank(messageTransmitter.attesterManager()); + messageTransmitter.enableAttester(vm.addr(420)); + messageTransmitter.setSignatureThreshold(1); + vm.stopPrank(); + + v.digest = keccak256(v.cctpMessage); + (v.v, v.r, v.s) = vm.sign(420, v.digest); + v.additionalMessage[0] = abi.encode(v.cctpMessage, abi.encodePacked(v.r, v.s, v.v)); + } + } + + /// @dev identifies and delivers the wormhole message + vm.startBroadcast(dstRelayer); + for (uint256 j; j < logs.length; ++j) { + v.log = logs[j]; + + if (v.log.topics[0] == MESSAGE_EVENT_SELECTOR) { + (v.sequence, v.nonce, v.payload,) = abi.decode(v.log.data, (uint64, uint32, bytes, uint8)); + + DeliveryInstruction memory instruction = PayloadDecoder.decodeDeliveryInstruction(v.payload); + + v.dstAddress = TypeCasts.bytes32ToAddress(instruction.targetAddress); + + if (expDstAddress == address(0) || expDstAddress == v.dstAddress) { + IWormholeReceiver(v.dstAddress).receiveWormholeMessages( + instruction.payload, + v.additionalMessage, + instruction.senderAddress, + srcChainId, + /// @dev generating some random hash + keccak256(abi.encodePacked(v.sequence, v.nonce)) + ); + } + } + } + vm.stopBroadcast(); + vm.selectFork(v.prevForkId); + } + /// @dev helps find logs of `length` for default event selector /// @param logs represents the logs after message dispatch on src chain /// @param length represents the expected number of logs diff --git a/test/Wormhole.AutomaticRelayer.t.sol b/test/Wormhole.AutomaticRelayer.t.sol index 6a9ba87..000677d 100644 --- a/test/Wormhole.AutomaticRelayer.t.sol +++ b/test/Wormhole.AutomaticRelayer.t.sol @@ -7,8 +7,14 @@ import "forge-std/Test.sol"; /// local imports import "src/wormhole/automatic-relayer/WormholeHelper.sol"; import "src/wormhole/specialized-relayer/lib/IWormhole.sol"; +import "solady/src/tokens/ERC20.sol"; interface IWormholeRelayerSend { + struct MessageKey { + uint8 keyType; + bytes encodedKey; + } + function sendPayloadToEvm( uint16 targetChain, address targetAddress, @@ -36,14 +42,40 @@ interface IWormholeRelayerSend { VaaKey[] memory vaaKeys ) external payable returns (uint64 sequence); + function sendToEvm( + uint16 targetChain, + address targetAddress, + bytes memory payload, + uint256 receiverValue, + uint256 paymentForExtraReceiverValue, + uint256 gasLimit, + uint16 refundChain, + address refundAddress, + address deliveryProviderAddress, + MessageKey[] memory messageKeys, + uint8 consistencyLevel + ) external payable returns (uint64 sequence); + function quoteEVMDeliveryPrice(uint16 targetChain, uint256 receiverValue, uint256 gasLimit) external view returns (uint256 nativePriceQuote, uint256 targetChainRefundPerGasUnused); + + function getDefaultDeliveryProvider() external view returns (address); } interface IWormholeRelayer is IWormholeRelayerSend {} +interface ITokenManager { + function depositForBurnWithCaller( + uint256 amount, + uint32 destinationDomain, + bytes32 mintRecipient, + address burnToken, + bytes32 destinationCaller + ) external returns (uint64 nonce); +} + contract Target is IWormholeReceiver { uint256 public value; @@ -97,11 +129,35 @@ contract AnotherTarget { } } +contract CCTPTarget { + IMessageTransmitter transmitter; + + constructor(IMessageTransmitter transmitter_) { + transmitter = transmitter_; + } + + function receiveWormholeMessages( + bytes memory payload, + bytes[] memory additionalVaas, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) external payable { + (bytes memory message, bytes memory attestation) = abi.decode(additionalVaas[0], (bytes, bytes)); + transmitter.receiveMessage(message, attestation); + } +} + contract WormholeAutomaticRelayerHelperTest is Test { IWormhole wormhole = IWormhole(0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B); + ITokenManager tokenMessenger = ITokenManager(0xBd3fa81B58Ba92a82136038B25aDec7066af3155); + ERC20 USDC = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + ERC20 USDC_POLYGON = ERC20(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359); + WormholeHelper wormholeHelper; Target target; Target altTarget; + CCTPTarget cctpTarget; AnotherTarget anotherTarget; AdditionalVAATarget addVaaTarget; @@ -120,6 +176,8 @@ contract WormholeAutomaticRelayerHelperTest is Test { address constant L2_1_RELAYER = 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911; address constant L2_2_RELAYER = 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911; + address constant MESSAGE_TRANSMITTER_POLYGON = 0xF3be9355363857F3e001be68856A2f96b4C39Ba9; + address[] public allDstRelayers; uint16[] public allDstChainIds; uint256[] public allDstForks; @@ -139,6 +197,7 @@ contract WormholeAutomaticRelayerHelperTest is Test { target = new Target(); addVaaTarget = new AdditionalVAATarget(); anotherTarget = new AnotherTarget(L1_CHAIN_ID); + cctpTarget = new CCTPTarget(IMessageTransmitter(MESSAGE_TRANSMITTER_POLYGON)); ARBITRUM_FORK_ID = vm.createSelectFork(RPC_ARBITRUM_MAINNET, 38063686); altTarget = new Target(); @@ -258,6 +317,61 @@ contract WormholeAutomaticRelayerHelperTest is Test { assertEq(addVaaTarget.vaalen(), 1); } + /// @dev test single dst cctp transfers with wormhole + function testCctpWormhole() external { + vm.selectFork(L1_FORK_ID); + address bridgoor = address(32145); + + vm.deal(bridgoor, 2 ether); + deal(address(USDC), bridgoor, 100e6); + vm.startPrank(bridgoor); + + USDC.approve(address(tokenMessenger), 100e6); + + vm.recordLogs(); + uint64 nonce = tokenMessenger.depositForBurnWithCaller( + 100e6, + 7, + bytes32(uint256(uint160(address(cctpTarget)))), + address(USDC), + bytes32(uint256(uint160(address(cctpTarget)))) + ); + + IWormholeRelayer relayer = IWormholeRelayer(L1_RELAYER); + + IWormholeRelayerSend.MessageKey[] memory messageKeys = new IWormholeRelayerSend.MessageKey[](1); + messageKeys[0] = IWormholeRelayerSend.MessageKey(2, abi.encodePacked(uint32(7), nonce)); + + (uint256 msgValue,) = relayer.quoteEVMDeliveryPrice(L2_1_CHAIN_ID, 0, 500000); + + relayer.sendToEvm{value: msgValue}( + L2_1_CHAIN_ID, + address(cctpTarget), + bytes(""), + 0, + 0, + 500000, + L2_1_CHAIN_ID, + address(0), + relayer.getDefaultDeliveryProvider(), + messageKeys, + 1 + ); + + wormholeHelper.helpWithCctpAndWormhole( + L1_CHAIN_ID, + POLYGON_FORK_ID, + address(cctpTarget), + L2_1_RELAYER, + 0xF3be9355363857F3e001be68856A2f96b4C39Ba9, + vm.getRecordedLogs() + ); + vm.stopPrank(); + + vm.selectFork(POLYGON_FORK_ID); + assertEq(USDC_POLYGON.balanceOf(address(cctpTarget)), 100e6); + } + function _aMostFancyCrossChainFunctionInYourContract(uint16 dstChainId, address receiver) internal { IWormholeRelayer relayer = IWormholeRelayer(L1_RELAYER); From f4ad74b86f3538efc72eef2475da4927595079e8 Mon Sep 17 00:00:00 2001 From: sujithsomraaj Date: Mon, 3 Jun 2024 00:02:01 +0530 Subject: [PATCH 2/3] chore: run gas snapshot --- .gas-snapshot | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index feab486..251b6e7 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -3,10 +3,10 @@ AxelarHelperTest:testFancyAxelar() (gas: 236169) AxelarHelperTest:testMultiDstAxelar() (gas: 391397) AxelarHelperTest:testSimpleAxelar() (gas: 189999) CelerHelperTest:testCustomOrderingCeler() (gas: 108685) -CelerHelperTest:testFancyCeler() (gas: 118987) +CelerHelperTest:testFancyCeler() (gas: 123722) CelerHelperTest:testMultiDstCeler() (gas: 151884) CelerHelperTest:testSimpleCeler() (gas: 72229) -CelerHelperTest:testSimpleCelerWithEstimates() (gas: 72704) +CelerHelperTest:testSimpleCelerWithEstimates() (gas: 77270) HyperlaneHelperTest:testCustomOrderingHL() (gas: 145598) HyperlaneHelperTest:testFancyHL() (gas: 147384) HyperlaneHelperTest:testMultiDstHL() (gas: 194040) @@ -16,7 +16,7 @@ LayerZeroHelperTest:testCustomOrderingLZ() (gas: 340154) LayerZeroHelperTest:testFancyLZ() (gas: 296242) LayerZeroHelperTest:testMultiDstLZ() (gas: 476527) LayerZeroHelperTest:testSimpleLZ() (gas: 245817) -LayerZeroHelperTest:testSimpleLZWithEstimates() (gas: 246414) +LayerZeroHelperTest:testSimpleLZWithEstimates() (gas: 310319) LayerZeroV2HelperTest:testCustomEventSelector() (gas: 341769) LayerZeroV2HelperTest:testMultipleDestinations() (gas: 649473) LayerZeroV2HelperTest:testMultipleDestinationsCustomEventSelector() (gas: 649584) @@ -24,12 +24,13 @@ LayerZeroV2HelperTest:testSingleDestination() (gas: 344507) StargateHelperTest:testCustomOrderingSG() (gas: 798922) StargateHelperTest:testFancySG() (gas: 591061) StargateHelperTest:testSimpleSG() (gas: 538333) -StargateHelperTest:testSimpleSGWithEstimates() (gas: 538863) -WormholeAutomaticRelayerHelperTest:testCustomOrderingWormhole() (gas: 281916) -WormholeAutomaticRelayerHelperTest:testFancyWormhole() (gas: 224571) -WormholeAutomaticRelayerHelperTest:testMultiDstWormhole() (gas: 345623) -WormholeAutomaticRelayerHelperTest:testMultiDstWormholeWithAdditionalVAA() (gas: 372929) -WormholeAutomaticRelayerHelperTest:testSimpleWormhole() (gas: 177400) +StargateHelperTest:testSimpleSGWithEstimates() (gas: 604010) +WormholeAutomaticRelayerHelperTest:testCctpWormhole() (gas: 680853) +WormholeAutomaticRelayerHelperTest:testCustomOrderingWormhole() (gas: 282349) +WormholeAutomaticRelayerHelperTest:testFancyWormhole() (gas: 224754) +WormholeAutomaticRelayerHelperTest:testMultiDstWormhole() (gas: 345999) +WormholeAutomaticRelayerHelperTest:testMultiDstWormholeWithAdditionalVAA() (gas: 374447) +WormholeAutomaticRelayerHelperTest:testSimpleWormhole() (gas: 177619) WormholeSpecializedRelayerHelperTest:testCustomOrderingWormhole() (gas: 599240) WormholeSpecializedRelayerHelperTest:testFancyWormhole() (gas: 332394) WormholeSpecializedRelayerHelperTest:testMultiDstWormhole() (gas: 635874) From 293a87ecccd1d16a9ee0fe23eab66ae19c012c5f Mon Sep 17 00:00:00 2001 From: sujithsomraaj Date: Mon, 3 Jun 2024 00:03:27 +0530 Subject: [PATCH 3/3] chore: run gas snapshot --- .gas-snapshot | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 251b6e7..2cc94e3 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -3,10 +3,10 @@ AxelarHelperTest:testFancyAxelar() (gas: 236169) AxelarHelperTest:testMultiDstAxelar() (gas: 391397) AxelarHelperTest:testSimpleAxelar() (gas: 189999) CelerHelperTest:testCustomOrderingCeler() (gas: 108685) -CelerHelperTest:testFancyCeler() (gas: 123722) +CelerHelperTest:testFancyCeler() (gas: 118987) CelerHelperTest:testMultiDstCeler() (gas: 151884) CelerHelperTest:testSimpleCeler() (gas: 72229) -CelerHelperTest:testSimpleCelerWithEstimates() (gas: 77270) +CelerHelperTest:testSimpleCelerWithEstimates() (gas: 72704) HyperlaneHelperTest:testCustomOrderingHL() (gas: 145598) HyperlaneHelperTest:testFancyHL() (gas: 147384) HyperlaneHelperTest:testMultiDstHL() (gas: 194040) @@ -16,7 +16,7 @@ LayerZeroHelperTest:testCustomOrderingLZ() (gas: 340154) LayerZeroHelperTest:testFancyLZ() (gas: 296242) LayerZeroHelperTest:testMultiDstLZ() (gas: 476527) LayerZeroHelperTest:testSimpleLZ() (gas: 245817) -LayerZeroHelperTest:testSimpleLZWithEstimates() (gas: 310319) +LayerZeroHelperTest:testSimpleLZWithEstimates() (gas: 246414) LayerZeroV2HelperTest:testCustomEventSelector() (gas: 341769) LayerZeroV2HelperTest:testMultipleDestinations() (gas: 649473) LayerZeroV2HelperTest:testMultipleDestinationsCustomEventSelector() (gas: 649584) @@ -24,7 +24,7 @@ LayerZeroV2HelperTest:testSingleDestination() (gas: 344507) StargateHelperTest:testCustomOrderingSG() (gas: 798922) StargateHelperTest:testFancySG() (gas: 591061) StargateHelperTest:testSimpleSG() (gas: 538333) -StargateHelperTest:testSimpleSGWithEstimates() (gas: 604010) +StargateHelperTest:testSimpleSGWithEstimates() (gas: 538863) WormholeAutomaticRelayerHelperTest:testCctpWormhole() (gas: 680853) WormholeAutomaticRelayerHelperTest:testCustomOrderingWormhole() (gas: 282349) WormholeAutomaticRelayerHelperTest:testFancyWormhole() (gas: 224754)