From 844fa2df5990caa043635792ccfde8443ded96ab Mon Sep 17 00:00:00 2001 From: nonergodic Date: Mon, 2 Sep 2024 13:15:29 -0700 Subject: [PATCH] (mostly) format cleanup --- .gitignore | 3 - README.md | 46 +- docs/Optimization.md | 25 + src/proxy/README.md => docs/Proxy.md | 0 docs/WormholeRelayer.md | 37 + src/Utils.sol | 12 +- src/WormholeRelayer/Base.sol | 80 +- src/WormholeRelayer/CCTPAndTokenBase.sol | 828 ++++++------ src/WormholeRelayer/CCTPBase.sol | 415 +++--- src/WormholeRelayer/TokenBase.sol | 442 +++--- src/libraries/BytesParsing.sol | 80 +- src/libraries/WormholeCctpMessages.sol | 371 +++-- src/testing/CctpMessages.sol | 2 +- src/testing/CctpOverride.sol | 2 +- src/testing/ERC20Mock.sol | 2 +- src/testing/LogUtils.sol | 4 +- src/testing/WormholeOverride.sol | 2 +- .../DeliveryInstructionDecoder.sol | 283 ++-- .../WormholeRelayer/ExecutionParameters.sol | 66 +- .../WormholeRelayer/MockOffchainRelayer.sol | 4 +- src/testing/WormholeRelayerTest.sol | 1198 ++++++++--------- test/Proxy.t.sol | 2 - .../CCTPAndTokenBridgeBase.t.sol | 458 +++---- test/WormholeRelayer/CCTPBase.t.sol | 288 ++-- test/WormholeRelayer/ChooseChains.t.sol | 92 +- test/WormholeRelayer/ExtraChains.t.sol | 78 +- test/WormholeRelayer/Fork.t.sol | 554 ++++---- 27 files changed, 2657 insertions(+), 2717 deletions(-) create mode 100644 docs/Optimization.md rename src/proxy/README.md => docs/Proxy.md (100%) create mode 100644 docs/WormholeRelayer.md diff --git a/.gitignore b/.gitignore index 85198aa..3269660 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,5 @@ out/ /broadcast/*/31337/ /broadcast/**/dry-run/ -# Docs -docs/ - # Dotenv file .env diff --git a/README.md b/README.md index b07d96f..e65e6bd 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,10 @@ It is strongly recommended that you run the forge test suite of this SDK with yo This SDK comes with its own IERC20 interface. Given that projects tend to combine different SDKs, there's often this annoying issue of clashes of IERC20 interfaces, even though the are effectively the same. We handle this issue by importing `IERC20/IERC20.sol` which allows remapping the `IERC20/` prefix to whatever directory contains `IERC20.sol` in your project, thus providing an override mechanism that should allow dealing with this problem seamlessly until forge allows remapping of individual files. +## Components + +For additional documentation of components, see the docs directory. + ## Philosophy/Creeds In This House We Believe: @@ -49,42 +53,8 @@ In This House We Believe: * git gud * shut up and suffer -## WormholeRelayer - -### Introduction - -The WormholeRelayer (also sometimes referred to as the automatic or generic relayer) allows integrators to leverage external parties known as delivery providers, to relay messages emitted on a given source chain to the intended target chain. - -This frees integrators, who are building a cross-chain app, from the cumbersome and painful task of having to run relaying infrastructure themselves (and thus e.g. dealing with the headache of having to acquire gas tokens for the target chain). - -Messages include, but aren't limited to: Wormhole attestations (VAAs), Circle attestations (CCTP) - -Delivery providers provide a quote for the cost of a delivery on the source chain and also take payment there. This means the process is not fully trustless (delivery providers can take payment and then fail to perform the delivery), however the state of the respective chains always makes it clear whether a delivery provider has done their duty for a given delivery and delivery providers can't maliciously manipulate the content of a delivery. - -### Example Usage - -[HelloWormhole - Simple cross-chain message sending application](https://github.com/wormhole-foundation/hello-wormhole) - -[HelloToken - Simple cross-chain token sending application](https://github.com/wormhole-foundation/hello-token) - -[HelloUSDC - Simple cross-chain USDC sending application using CCTP](https://github.com/wormhole-foundation/hello-usdc) - -### SDK Summary - -- Includes interfaces to interact with contracts in the Wormhole ecosystem ([src/interfaces](https://github.com/wormhole-foundation/wormhole-solidity-sdk/tree/main/src/interfaces)) -- Includes the base class ‘Base’ with helpers for common actions that will typically need to be done within ‘receiveWormholeMessages’: - - [`onlyWormholeRelayer()`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol#L9): Checking that msg.sender is the wormhole relayer contract - Sometimes, Cross-chain applications may be set up such that there is one ‘spoke’ contract on every chain, which sends messages to the ‘hub’ contract. If so, we’d ideally only want to allow messages to be sent from these spoke contracts. Included are helpers for this: - - - [`setRegisteredSender(uint16 sourceChain, bytes32 sourceAddress)`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol#L45): Setting the specified sender for ‘sourceChain’ to be ‘sourceAddress’ - - [`isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress)`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol#L30): Checking that the sender who requested the delivery is the registered address for that chain - - Look at test/Counter.t.sol for an example usage of Base - -- Included are also: - - The ‘[TokenSender](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/TokenBase.sol#L24)’ and ‘[TokenReceiver](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/TokenBase.sol#L158)’ base classes with helpers for smart contracts that wish to send and receive tokens using Wormhole’s TokenBridge. See ‘[HelloToken](https://github.com/wormhole-foundation/hello-token)’ for example usage. - - The ‘[CCTPSender](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPBase.sol#L59)’ and ‘[CCTPReceiver](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPBase.sol#L177)’ base classes with helpers for smart contracts that wish to send and receive both tokens using Wormhole’s TokenBridge as well as USDC using CCTP. See ‘[HelloUSDC](https://github.com/wormhole-foundation/hello-usdc)’ for example usage. - - Or a combination of both - [CCTPAndTokenBase](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPAndTokenBase.sol). - - Helpers for setting up a local forge testing environment. See ‘[HelloWormhole](https://github.com/wormhole-foundation/hello-wormhole)’ for example usage. +## Notable Solidity Repos -**Note: This code is meant to be used as starter / reference code. Feel free to modify for use in your contracts, and also make sure to audit any code used from here as part of your contracts before deploying to mainnet.** \ No newline at end of file +* [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts) +* [Solmate](https://github.com/transmissions11/solmate) / [Solady](https://github.com/Vectorized/solady) +* [Uniswap Permit2](https://github.com/Uniswap/permit2) + [explanation](https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/permit2) diff --git a/docs/Optimization.md b/docs/Optimization.md new file mode 100644 index 0000000..9df8fed --- /dev/null +++ b/docs/Optimization.md @@ -0,0 +1,25 @@ +# Compiler Optimization + +List of ways to avoid short-comings of the current optimizer which lead to suboptimal byte code + +## for loop array length checking + +``` +function iterate(uint[] memory myArray) { + uint len = myArray.length; + for (uint i; i < len; ++i) { /*...*/} +} +``` +is more efficient than +``` +function iterate(uint[] memory myArray) { + for (uint i; i < myArray.length; ++i) { /*...*/} +} +``` +even if it is trivial for the optimizer to check that `myArray`'s length can't possibly change as part of the loop. + +If `myArray` uses `calldata` instead of `memory`, both versions produce the same bytecode. + +## prefer `< MAX + 1` over `<= MAX` for const comparison + +Given that the EVM only supports `LT` and `GT` but not `LTE` or `GTE`, solc implements `x<=y` as `!(x>y)`. However, given a constant `MAX`, since solc resolves `MAX + 1` at compile time, `< MAX + 1` saves one `ISZERO` opcode. \ No newline at end of file diff --git a/src/proxy/README.md b/docs/Proxy.md similarity index 100% rename from src/proxy/README.md rename to docs/Proxy.md diff --git a/docs/WormholeRelayer.md b/docs/WormholeRelayer.md new file mode 100644 index 0000000..899e47f --- /dev/null +++ b/docs/WormholeRelayer.md @@ -0,0 +1,37 @@ +# WormholeRelayer + +The WormholeRelayer (also sometimes referred to as the automatic or generic relayer) allows integrators to leverage external parties known as delivery providers, to relay messages emitted on a given source chain to the intended target chain. + +This frees integrators, who are building a cross-chain app, from the cumbersome and painful task of having to run relaying infrastructure themselves (and thus e.g. dealing with the headache of having to acquire gas tokens for the target chain). + +Messages include, but aren't limited to: Wormhole attestations (VAAs), Circle attestations (CCTP) + +Delivery providers provide a quote for the cost of a delivery on the source chain and also take payment there. This means the process is not fully trustless (delivery providers can take payment and then fail to perform the delivery), however the state of the respective chains always makes it clear whether a delivery provider has done their duty for a given delivery and delivery providers can't maliciously manipulate the content of a delivery. + +## Example Usage + +[HelloWormhole - Simple cross-chain message sending application](https://github.com/wormhole-foundation/hello-wormhole) + +[HelloToken - Simple cross-chain token sending application](https://github.com/wormhole-foundation/hello-token) + +[HelloUSDC - Simple cross-chain USDC sending application using CCTP](https://github.com/wormhole-foundation/hello-usdc) + +## SDK Summary + +- Includes interfaces to interact with contracts in the Wormhole ecosystem ([src/interfaces](https://github.com/wormhole-foundation/wormhole-solidity-sdk/tree/main/src/interfaces)) +- Includes the base class ‘Base’ with helpers for common actions that will typically need to be done within ‘receiveWormholeMessages’: + - [`onlyWormholeRelayer()`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol#L9): Checking that msg.sender is the wormhole relayer contract + Sometimes, Cross-chain applications may be set up such that there is one ‘spoke’ contract on every chain, which sends messages to the ‘hub’ contract. If so, we’d ideally only want to allow messages to be sent from these spoke contracts. Included are helpers for this: + + - [`setRegisteredSender(uint16 sourceChain, bytes32 sourceAddress)`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol#L45): Setting the specified sender for ‘sourceChain’ to be ‘sourceAddress’ + - [`isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress)`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/Base.sol#L30): Checking that the sender who requested the delivery is the registered address for that chain + + Look at test/Counter.t.sol for an example usage of Base + +- Included are also: + - The ‘[TokenSender](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/TokenBase.sol#L24)’ and ‘[TokenReceiver](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/TokenBase.sol#L158)’ base classes with helpers for smart contracts that wish to send and receive tokens using Wormhole’s TokenBridge. See ‘[HelloToken](https://github.com/wormhole-foundation/hello-token)’ for example usage. + - The ‘[CCTPSender](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPBase.sol#L59)’ and ‘[CCTPReceiver](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPBase.sol#L177)’ base classes with helpers for smart contracts that wish to send and receive both tokens using Wormhole’s TokenBridge as well as USDC using CCTP. See ‘[HelloUSDC](https://github.com/wormhole-foundation/hello-usdc)’ for example usage. + - Or a combination of both - [CCTPAndTokenBase](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayer/CCTPAndTokenBase.sol). + - Helpers for setting up a local forge testing environment. See ‘[HelloWormhole](https://github.com/wormhole-foundation/hello-wormhole)’ for example usage. + +**Note: This code is meant to be used as starter / reference code. Feel free to modify for use in your contracts, and also make sure to audit any code used from here as part of your contracts before deploying to mainnet.** \ No newline at end of file diff --git a/src/Utils.sol b/src/Utils.sol index 36eb112..dc42d21 100644 --- a/src/Utils.sol +++ b/src/Utils.sol @@ -5,14 +5,14 @@ pragma solidity ^0.8.19; error NotAnEvmAddress(bytes32); function toUniversalAddress(address addr) pure returns (bytes32 universalAddr) { - universalAddr = bytes32(uint256(uint160(addr))); + universalAddr = bytes32(uint256(uint160(addr))); } function fromUniversalAddress(bytes32 universalAddr) pure returns (address addr) { - if (bytes12(universalAddr) != 0) - revert NotAnEvmAddress(universalAddr); + if (bytes12(universalAddr) != 0) + revert NotAnEvmAddress(universalAddr); - assembly ("memory-safe") { - addr := universalAddr - } + assembly ("memory-safe") { + addr := universalAddr + } } diff --git a/src/WormholeRelayer/Base.sol b/src/WormholeRelayer/Base.sol index 7c54b6b..5e2f62d 100644 --- a/src/WormholeRelayer/Base.sol +++ b/src/WormholeRelayer/Base.sol @@ -7,49 +7,49 @@ import "wormhole-sdk/interfaces/IWormhole.sol"; import "wormhole-sdk/Utils.sol"; abstract contract Base { - IWormholeRelayer public immutable wormholeRelayer; - IWormhole public immutable wormhole; + IWormholeRelayer public immutable wormholeRelayer; + IWormhole public immutable wormhole; - address registrationOwner; - mapping(uint16 => bytes32) registeredSenders; + address registrationOwner; + mapping(uint16 => bytes32) registeredSenders; - constructor(address _wormholeRelayer, address _wormhole) { - wormholeRelayer = IWormholeRelayer(_wormholeRelayer); - wormhole = IWormhole(_wormhole); - registrationOwner = msg.sender; - } + constructor(address _wormholeRelayer, address _wormhole) { + wormholeRelayer = IWormholeRelayer(_wormholeRelayer); + wormhole = IWormhole(_wormhole); + registrationOwner = msg.sender; + } - modifier onlyWormholeRelayer() { - require( - msg.sender == address(wormholeRelayer), - "Msg.sender is not Wormhole Relayer" - ); - _; - } + modifier onlyWormholeRelayer() { + require( + msg.sender == address(wormholeRelayer), + "Msg.sender is not Wormhole Relayer" + ); + _; + } - modifier isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress) { - require( - registeredSenders[sourceChain] == sourceAddress, - "Not registered sender" - ); - _; - } + modifier isRegisteredSender(uint16 sourceChain, bytes32 sourceAddress) { + require( + registeredSenders[sourceChain] == sourceAddress, + "Not registered sender" + ); + _; + } - /** - * Sets the registered address for 'sourceChain' to 'sourceAddress' - * So that for messages from 'sourceChain', only ones from 'sourceAddress' are valid - * - * Assumes only one sender per chain is valid - * Sender is the address that called 'send' on the Wormhole Relayer contract on the source chain) - */ - function setRegisteredSender( - uint16 sourceChain, - bytes32 sourceAddress - ) public { - require( - msg.sender == registrationOwner, - "Not allowed to set registered sender" - ); - registeredSenders[sourceChain] = sourceAddress; - } + /** + * Sets the registered address for 'sourceChain' to 'sourceAddress' + * So that for messages from 'sourceChain', only ones from 'sourceAddress' are valid + * + * Assumes only one sender per chain is valid + * Sender is the address that called 'send' on the Wormhole Relayer contract on the source chain) + */ + function setRegisteredSender( + uint16 sourceChain, + bytes32 sourceAddress + ) public { + require( + msg.sender == registrationOwner, + "Not allowed to set registered sender" + ); + registeredSenders[sourceChain] = sourceAddress; + } } diff --git a/src/WormholeRelayer/CCTPAndTokenBase.sol b/src/WormholeRelayer/CCTPAndTokenBase.sol index 0f9312f..82cea38 100644 --- a/src/WormholeRelayer/CCTPAndTokenBase.sol +++ b/src/WormholeRelayer/CCTPAndTokenBase.sol @@ -13,445 +13,415 @@ import "./TokenBase.sol"; import "./CCTPBase.sol"; abstract contract CCTPAndTokenBase is CCTPBase { - ITokenBridge public immutable tokenBridge; - - enum Transfer { - TOKEN_BRIDGE, - CCTP - } - - constructor( - address _wormholeRelayer, - address _tokenBridge, - address _wormhole, - address _circleMessageTransmitter, - address _circleTokenMessenger, - address _USDC + ITokenBridge public immutable tokenBridge; + + enum Transfer { + TOKEN_BRIDGE, + CCTP + } + + constructor( + address _wormholeRelayer, + address _tokenBridge, + address _wormhole, + address _circleMessageTransmitter, + address _circleTokenMessenger, + address _USDC + ) + CCTPBase( + _wormholeRelayer, + _wormhole, + _circleMessageTransmitter, + _circleTokenMessenger, + _USDC ) - CCTPBase( - _wormholeRelayer, - _wormhole, - _circleMessageTransmitter, - _circleTokenMessenger, - _USDC - ) - { - tokenBridge = ITokenBridge(_tokenBridge); - } + { + tokenBridge = ITokenBridge(_tokenBridge); + } } abstract contract CCTPAndTokenSender is CCTPAndTokenBase { - // CCTP Sender functions, taken from "./CCTPBase.sol" - - uint8 internal constant CONSISTENCY_LEVEL_FINALIZED = 15; - - using CCTPMessageLib for *; - - mapping(uint16 => uint32) public chainIdToCCTPDomain; - - /** - * Sets the CCTP Domain corresponding to chain 'chain' to be 'cctpDomain' - * So that transfers of USDC to chain 'chain' use the target CCTP domain 'cctpDomain' - * - * This action can only be performed by 'cctpConfigurationOwner', who is set to be the deployer - * - * Currently, cctp domains are: - * Ethereum: Wormhole chain id 2, cctp domain 0 - * Avalanche: Wormhole chain id 6, cctp domain 1 - * Optimism: Wormhole chain id 24, cctp domain 2 - * Arbitrum: Wormhole chain id 23, cctp domain 3 - * Base: Wormhole chain id 30, cctp domain 6 - * - * These can be set via: - * setCCTPDomain(2, 0); - * setCCTPDomain(6, 1); - * setCCTPDomain(24, 2); - * setCCTPDomain(23, 3); - * setCCTPDomain(30, 6); - */ - function setCCTPDomain(uint16 chain, uint32 cctpDomain) public { - require( - msg.sender == cctpConfigurationOwner, - "Not allowed to set CCTP Domain" - ); - chainIdToCCTPDomain[chain] = cctpDomain; - } - - function getCCTPDomain(uint16 chain) internal view returns (uint32) { - return chainIdToCCTPDomain[chain]; - } - - /** - * transferUSDC wraps common boilerplate for sending tokens to another chain using IWormholeRelayer - * - approves the Circle TokenMessenger contract to spend 'amount' of USDC - * - calls Circle's 'depositForBurnWithCaller' - * - returns key for inclusion in WormholeRelayer `additionalVaas` argument - * - * Note: this requires that only the targetAddress can redeem transfers. - * - */ - - function transferUSDC( - uint256 amount, - uint16 targetChain, - address targetAddress - ) internal returns (MessageKey memory) { - IERC20(USDC).approve(address(circleTokenMessenger), amount); - bytes32 targetAddressBytes32 = addressToBytes32CCTP(targetAddress); - uint64 nonce = circleTokenMessenger.depositForBurnWithCaller( - amount, - getCCTPDomain(targetChain), - targetAddressBytes32, - USDC, - targetAddressBytes32 - ); - return - MessageKey( - CCTPMessageLib.CCTP_KEY_TYPE, - abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce) - ); - } - - // Publishes a CCTP transfer of 'amount' of USDC - // and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain' - // - // The second step is done by publishing a wormhole message representing a request - // to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain' - // with the payload 'abi.encode(Transfer.CCTP, amount, payload)' - // (we encode a Transfer enum to distinguish this from a TokenBridge transfer) - // (and we encode the amount so it can be checked on the target chain) - function sendUSDCWithPayloadToEvm( - uint16 targetChain, - address targetAddress, - bytes memory payload, - uint256 receiverValue, - uint256 gasLimit, - uint256 amount - ) internal returns (uint64 sequence) { - MessageKey[] memory messageKeys = new MessageKey[](1); - messageKeys[0] = transferUSDC(amount, targetChain, targetAddress); - - bytes memory userPayload = abi.encode(Transfer.CCTP, amount, payload); - address defaultDeliveryProvider = wormholeRelayer - .getDefaultDeliveryProvider(); - - (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( - targetChain, - receiverValue, - gasLimit - ); - - sequence = wormholeRelayer.sendToEvm{value: cost}( - targetChain, - targetAddress, - userPayload, - receiverValue, - 0, - gasLimit, - targetChain, - address(0x0), - defaultDeliveryProvider, - messageKeys, - CONSISTENCY_LEVEL_FINALIZED - ); - } - - function addressToBytes32CCTP(address addr) private pure returns (bytes32) { - return bytes32(uint256(uint160(addr))); - } - - // TokenBridge Sender functions, taken from "./TokenBase.sol" - - /** - * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer - * - approves tokenBridge to spend 'amount' of 'token' - * - emits token transfer VAA - * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument - * - * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress - * can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by - * the offchain relayer and the target contract would have to be hardened against this. - * - */ - function transferTokens( - address token, - uint256 amount, - uint16 targetChain, - address targetAddress - ) internal returns (VaaKey memory) { - return - transferTokens( - token, - amount, - targetChain, - targetAddress, - bytes("") - ); - } - - /** - * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer. - * A payload can be included in the transfer vaa. By including a payload here instead of the deliveryVaa, - * fewer trust assumptions are placed on the WormholeRelayer contract. - * - * - approves tokenBridge to spend 'amount' of 'token' - * - emits token transfer VAA - * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument - * - * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress - * can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by - * the offchain relayer and the target contract would have to be hardened against this. - */ - function transferTokens( - address token, - uint256 amount, - uint16 targetChain, - address targetAddress, - bytes memory payload - ) internal returns (VaaKey memory) { - IERC20(token).approve(address(tokenBridge), amount); - uint64 sequence = tokenBridge.transferTokensWithPayload{ - value: wormhole.messageFee() - }( - token, - amount, - targetChain, - toUniversalAddress(targetAddress), - 0, - payload - ); - return - VaaKey({ - emitterAddress: toUniversalAddress(address(tokenBridge)), - chainId: wormhole.chainId(), - sequence: sequence - }); - } - - // Publishes a wormhole message representing a 'TokenBridge' transfer of 'amount' of 'token' - // and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain' - // - // The second step is done by publishing a wormhole message representing a request - // to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain' - // with the payload 'abi.encode(Transfer.TOKEN_BRIDGE, payload)' - // (we encode a Transfer enum to distinguish this from a CCTP transfer) - function sendTokenWithPayloadToEvm( - uint16 targetChain, - address targetAddress, - bytes memory payload, - uint256 receiverValue, - uint256 gasLimit, - address token, - uint256 amount - ) internal returns (uint64) { - VaaKey[] memory vaaKeys = new VaaKey[](1); - vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress); - - (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( - targetChain, - receiverValue, - gasLimit - ); - return - wormholeRelayer.sendVaasToEvm{value: cost}( - targetChain, - targetAddress, - abi.encode(Transfer.TOKEN_BRIDGE, payload), - receiverValue, - gasLimit, - vaaKeys - ); - } - - function sendTokenWithPayloadToEvm( - uint16 targetChain, - address targetAddress, - bytes memory payload, - uint256 receiverValue, - uint256 gasLimit, - address token, - uint256 amount, - uint16 refundChain, - address refundAddress - ) internal returns (uint64) { - VaaKey[] memory vaaKeys = new VaaKey[](1); - vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress); - - (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( - targetChain, - receiverValue, - gasLimit - ); - return - wormholeRelayer.sendVaasToEvm{value: cost}( - targetChain, - targetAddress, - abi.encode(Transfer.TOKEN_BRIDGE, payload), - receiverValue, - gasLimit, - vaaKeys, - refundChain, - refundAddress - ); - } + // CCTP Sender functions, taken from "./CCTPBase.sol" + + uint8 internal constant CONSISTENCY_LEVEL_FINALIZED = 15; + + using CCTPMessageLib for *; + + mapping(uint16 => uint32) public chainIdToCCTPDomain; + + /** + * Sets the CCTP Domain corresponding to chain 'chain' to be 'cctpDomain' + * So that transfers of USDC to chain 'chain' use the target CCTP domain 'cctpDomain' + * + * This action can only be performed by 'cctpConfigurationOwner', who is set to be the deployer + * + * Currently, cctp domains are: + * Ethereum: Wormhole chain id 2, cctp domain 0 + * Avalanche: Wormhole chain id 6, cctp domain 1 + * Optimism: Wormhole chain id 24, cctp domain 2 + * Arbitrum: Wormhole chain id 23, cctp domain 3 + * Base: Wormhole chain id 30, cctp domain 6 + * + * These can be set via: + * setCCTPDomain(2, 0); + * setCCTPDomain(6, 1); + * setCCTPDomain(24, 2); + * setCCTPDomain(23, 3); + * setCCTPDomain(30, 6); + */ + function setCCTPDomain(uint16 chain, uint32 cctpDomain) public { + require( + msg.sender == cctpConfigurationOwner, + "Not allowed to set CCTP Domain" + ); + chainIdToCCTPDomain[chain] = cctpDomain; + } + + function getCCTPDomain(uint16 chain) internal view returns (uint32) { + return chainIdToCCTPDomain[chain]; + } + + /** + * transferUSDC wraps common boilerplate for sending tokens to another chain using IWormholeRelayer + * - approves the Circle TokenMessenger contract to spend 'amount' of USDC + * - calls Circle's 'depositForBurnWithCaller' + * - returns key for inclusion in WormholeRelayer `additionalVaas` argument + * + * Note: this requires that only the targetAddress can redeem transfers. + * + */ + + function transferUSDC( + uint256 amount, + uint16 targetChain, + address targetAddress + ) internal returns (MessageKey memory) { + IERC20(USDC).approve(address(circleTokenMessenger), amount); + bytes32 targetAddressBytes32 = addressToBytes32CCTP(targetAddress); + uint64 nonce = circleTokenMessenger.depositForBurnWithCaller( + amount, + getCCTPDomain(targetChain), + targetAddressBytes32, + USDC, + targetAddressBytes32 + ); + return MessageKey( + CCTPMessageLib.CCTP_KEY_TYPE, + abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce) + ); + } + + // Publishes a CCTP transfer of 'amount' of USDC + // and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain' + // + // The second step is done by publishing a wormhole message representing a request + // to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain' + // with the payload 'abi.encode(Transfer.CCTP, amount, payload)' + // (we encode a Transfer enum to distinguish this from a TokenBridge transfer) + // (and we encode the amount so it can be checked on the target chain) + function sendUSDCWithPayloadToEvm( + uint16 targetChain, + address targetAddress, + bytes memory payload, + uint256 receiverValue, + uint256 gasLimit, + uint256 amount + ) internal returns (uint64 sequence) { + MessageKey[] memory messageKeys = new MessageKey[](1); + messageKeys[0] = transferUSDC(amount, targetChain, targetAddress); + + bytes memory userPayload = abi.encode(Transfer.CCTP, amount, payload); + address defaultDeliveryProvider = wormholeRelayer.getDefaultDeliveryProvider(); + + (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit); + + sequence = wormholeRelayer.sendToEvm{value: cost}( + targetChain, + targetAddress, + userPayload, + receiverValue, + 0, + gasLimit, + targetChain, + address(0x0), + defaultDeliveryProvider, + messageKeys, + CONSISTENCY_LEVEL_FINALIZED + ); + } + + function addressToBytes32CCTP(address addr) private pure returns (bytes32) { + return bytes32(uint256(uint160(addr))); + } + + // TokenBridge Sender functions, taken from "./TokenBase.sol" + + /** + * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer + * - approves tokenBridge to spend 'amount' of 'token' + * - emits token transfer VAA + * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument + * + * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress + * can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by + * the offchain relayer and the target contract would have to be hardened against this. + * + */ + function transferTokens( + address token, + uint256 amount, + uint16 targetChain, + address targetAddress + ) internal returns (VaaKey memory) { + return transferTokens( + token, + amount, + targetChain, + targetAddress, + bytes("") + ); + } + + /** + * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer. + * A payload can be included in the transfer vaa. By including a payload here instead of the deliveryVaa, + * fewer trust assumptions are placed on the WormholeRelayer contract. + * + * - approves tokenBridge to spend 'amount' of 'token' + * - emits token transfer VAA + * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument + * + * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress + * can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by + * the offchain relayer and the target contract would have to be hardened against this. + */ + function transferTokens( + address token, + uint256 amount, + uint16 targetChain, + address targetAddress, + bytes memory payload + ) internal returns (VaaKey memory) { + IERC20(token).approve(address(tokenBridge), amount); + uint64 sequence = tokenBridge.transferTokensWithPayload{ + value: wormhole.messageFee() + }( + token, + amount, + targetChain, + toUniversalAddress(targetAddress), + 0, + payload + ); + return VaaKey({ + emitterAddress: toUniversalAddress(address(tokenBridge)), + chainId: wormhole.chainId(), + sequence: sequence + }); + } + + // Publishes a wormhole message representing a 'TokenBridge' transfer of 'amount' of 'token' + // and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain' + // + // The second step is done by publishing a wormhole message representing a request + // to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain' + // with the payload 'abi.encode(Transfer.TOKEN_BRIDGE, payload)' + // (we encode a Transfer enum to distinguish this from a CCTP transfer) + function sendTokenWithPayloadToEvm( + uint16 targetChain, + address targetAddress, + bytes memory payload, + uint256 receiverValue, + uint256 gasLimit, + address token, + uint256 amount + ) internal returns (uint64) { + VaaKey[] memory vaaKeys = new VaaKey[](1); + vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress); + + (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit); + return wormholeRelayer.sendVaasToEvm{value: cost}( + targetChain, + targetAddress, + abi.encode(Transfer.TOKEN_BRIDGE, payload), + receiverValue, + gasLimit, + vaaKeys + ); + } + + function sendTokenWithPayloadToEvm( + uint16 targetChain, + address targetAddress, + bytes memory payload, + uint256 receiverValue, + uint256 gasLimit, + address token, + uint256 amount, + uint16 refundChain, + address refundAddress + ) internal returns (uint64) { + VaaKey[] memory vaaKeys = new VaaKey[](1); + vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress); + + (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit); + return wormholeRelayer.sendVaasToEvm{value: cost}( + targetChain, + targetAddress, + abi.encode(Transfer.TOKEN_BRIDGE, payload), + receiverValue, + gasLimit, + vaaKeys, + refundChain, + refundAddress + ); + } } abstract contract CCTPAndTokenReceiver is CCTPAndTokenBase { - function redeemUSDC( - bytes memory cctpMessage - ) internal returns (uint256 amount) { - (bytes memory message, bytes memory signature) = abi.decode( - cctpMessage, - (bytes, bytes) + function redeemUSDC( + bytes memory cctpMessage + ) internal returns (uint256 amount) { + (bytes memory message, bytes memory signature) = abi.decode( + cctpMessage, + (bytes, bytes) + ); + uint256 beforeBalance = IERC20(USDC).balanceOf(address(this)); + circleMessageTransmitter.receiveMessage(message, signature); + return IERC20(USDC).balanceOf(address(this)) - beforeBalance; + } + + struct TokenReceived { + bytes32 tokenHomeAddress; + uint16 tokenHomeChain; + address tokenAddress; // wrapped address if tokenHomeChain !== this chain, else tokenHomeAddress (in evm address format) + uint256 amount; + uint256 amountNormalized; // if decimals > 8, normalized to 8 decimal places + } + + function getDecimals( + address tokenAddress + ) internal view returns (uint8 decimals) { + // query decimals + (, bytes memory queriedDecimals) = + address(tokenAddress).staticcall(abi.encodeWithSignature("decimals()")); + decimals = abi.decode(queriedDecimals, (uint8)); + } + + function getTokenAddressOnThisChain( + uint16 tokenHomeChain, + bytes32 tokenHomeAddress + ) internal view returns (address tokenAddressOnThisChain) { + return tokenHomeChain == wormhole.chainId() + ? fromUniversalAddress(tokenHomeAddress) + : tokenBridge.wrappedAsset(tokenHomeChain, tokenHomeAddress); + } + + function receiveWormholeMessages( + bytes memory payload, + bytes[] memory additionalMessages, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) external payable { + Transfer transferType = abi.decode(payload, (Transfer)); + if (transferType == Transfer.TOKEN_BRIDGE) { + TokenReceived[] memory receivedTokens = new TokenReceived[](additionalMessages.length); + + for (uint256 i = 0; i < additionalMessages.length; ++i) { + IWormhole.VM memory parsed = wormhole.parseVM(additionalMessages[i]); + require( + parsed.emitterAddress == tokenBridge.bridgeContracts(parsed.emitterChainId), + "Not a Token Bridge VAA" ); - uint256 beforeBalance = IERC20(USDC).balanceOf(address(this)); - circleMessageTransmitter.receiveMessage(message, signature); - return IERC20(USDC).balanceOf(address(this)) - beforeBalance; - } - - struct TokenReceived { - bytes32 tokenHomeAddress; - uint16 tokenHomeChain; - address tokenAddress; // wrapped address if tokenHomeChain !== this chain, else tokenHomeAddress (in evm address format) - uint256 amount; - uint256 amountNormalized; // if decimals > 8, normalized to 8 decimal places - } - - function getDecimals( - address tokenAddress - ) internal view returns (uint8 decimals) { - // query decimals - (, bytes memory queriedDecimals) = address(tokenAddress).staticcall( - abi.encodeWithSignature("decimals()") + ITokenBridge.TransferWithPayload memory transfer = + tokenBridge.parseTransferWithPayload(parsed.payload); + require( + transfer.to == toUniversalAddress(address(this)) && + transfer.toChain == wormhole.chainId(), + "Token was not sent to this address" ); - decimals = abi.decode(queriedDecimals, (uint8)); - } - function getTokenAddressOnThisChain( - uint16 tokenHomeChain, - bytes32 tokenHomeAddress - ) internal view returns (address tokenAddressOnThisChain) { - return - tokenHomeChain == wormhole.chainId() - ? fromUniversalAddress(tokenHomeAddress) - : tokenBridge.wrappedAsset(tokenHomeChain, tokenHomeAddress); + tokenBridge.completeTransferWithPayload(additionalMessages[i]); + + address thisChainTokenAddress = + getTokenAddressOnThisChain(transfer.tokenChain, transfer.tokenAddress); + uint8 decimals = getDecimals(thisChainTokenAddress); + uint256 denormalizedAmount = transfer.amount; + if (decimals > 8) + denormalizedAmount *= uint256(10) ** (decimals - 8); + + receivedTokens[i] = TokenReceived({ + tokenHomeAddress: transfer.tokenAddress, + tokenHomeChain: transfer.tokenChain, + tokenAddress: thisChainTokenAddress, + amount: denormalizedAmount, + amountNormalized: transfer.amount + }); + } + + (, bytes memory userPayload) = abi.decode(payload, (Transfer, bytes)); + + // call into overriden method + receivePayloadAndTokens( + userPayload, + receivedTokens, + sourceAddress, + sourceChain, + deliveryHash + ); } - - function receiveWormholeMessages( - bytes memory payload, - bytes[] memory additionalMessages, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 deliveryHash - ) external payable { - Transfer transferType = abi.decode(payload, (Transfer)); - if (transferType == Transfer.TOKEN_BRIDGE) { - TokenReceived[] memory receivedTokens = new TokenReceived[]( - additionalMessages.length - ); - - for (uint256 i = 0; i < additionalMessages.length; ++i) { - IWormhole.VM memory parsed = wormhole.parseVM( - additionalMessages[i] - ); - require( - parsed.emitterAddress == - tokenBridge.bridgeContracts(parsed.emitterChainId), - "Not a Token Bridge VAA" - ); - ITokenBridge.TransferWithPayload memory transfer = tokenBridge - .parseTransferWithPayload(parsed.payload); - require( - transfer.to == toUniversalAddress(address(this)) && - transfer.toChain == wormhole.chainId(), - "Token was not sent to this address" - ); - - tokenBridge.completeTransferWithPayload(additionalMessages[i]); - - address thisChainTokenAddress = getTokenAddressOnThisChain( - transfer.tokenChain, - transfer.tokenAddress - ); - uint8 decimals = getDecimals(thisChainTokenAddress); - uint256 denormalizedAmount = transfer.amount; - if (decimals > 8) - denormalizedAmount *= uint256(10) ** (decimals - 8); - - receivedTokens[i] = TokenReceived({ - tokenHomeAddress: transfer.tokenAddress, - tokenHomeChain: transfer.tokenChain, - tokenAddress: thisChainTokenAddress, - amount: denormalizedAmount, - amountNormalized: transfer.amount - }); - } - - (, bytes memory userPayload) = abi.decode( - payload, - (Transfer, bytes) - ); - - // call into overriden method - receivePayloadAndTokens( - userPayload, - receivedTokens, - sourceAddress, - sourceChain, - deliveryHash - ); - } else if (transferType == Transfer.CCTP) { - // Currently, 'sendUSDCWithPayloadToEVM' only sends one CCTP transfer - // That can be modified if the integrator desires to send multiple CCTP transfers - // in which case the following code would have to be modified to support - // redeeming these multiple transfers and checking that their 'amount's are accurate - require( - additionalMessages.length <= 1, - "CCTP: At most one Message is supported" - ); - - uint256 amountUSDCReceived; - if (additionalMessages.length == 1) { - amountUSDCReceived = redeemUSDC(additionalMessages[0]); - } - - (, uint256 amount, bytes memory userPayload) = abi.decode( - payload, - (Transfer, uint256, bytes) - ); - - // Check that the correct amount was received - // It is important to verify that the 'USDC' sent in by the relayer is the same amount - // that the sender sent in on the source chain - require(amount == amountUSDCReceived, "Wrong amount received"); - - receivePayloadAndUSDC( - userPayload, - amountUSDCReceived, - sourceAddress, - sourceChain, - deliveryHash - ); - } else { - revert("Invalid transfer type"); - } + else if (transferType == Transfer.CCTP) { + // Currently, 'sendUSDCWithPayloadToEVM' only sends one CCTP transfer + // That can be modified if the integrator desires to send multiple CCTP transfers + // in which case the following code would have to be modified to support + // redeeming these multiple transfers and checking that their 'amount's are accurate + require( + additionalMessages.length <= 1, + "CCTP: At most one Message is supported" + ); + + uint256 amountUSDCReceived; + if (additionalMessages.length == 1) + amountUSDCReceived = redeemUSDC(additionalMessages[0]); + + (, uint256 amount, bytes memory userPayload) = abi.decode( + payload, + (Transfer, uint256, bytes) + ); + + // Check that the correct amount was received + // It is important to verify that the 'USDC' sent in by the relayer is the same amount + // that the sender sent in on the source chain + require(amount == amountUSDCReceived, "Wrong amount received"); + + receivePayloadAndUSDC( + userPayload, + amountUSDCReceived, + sourceAddress, + sourceChain, + deliveryHash + ); } - - // Implement this function to handle in-bound deliveries that include a CCTP transfer - function receivePayloadAndUSDC( - bytes memory payload, - uint256 amountUSDCReceived, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 deliveryHash - ) internal virtual {} - - // Implement this function to handle in-bound deliveries that include a TokenBridge transfer - function receivePayloadAndTokens( - bytes memory payload, - TokenReceived[] memory receivedTokens, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 deliveryHash - ) internal virtual {} + else + revert("Invalid transfer type"); + } + + // Implement this function to handle in-bound deliveries that include a CCTP transfer + function receivePayloadAndUSDC( + bytes memory payload, + uint256 amountUSDCReceived, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) internal virtual {} + + // Implement this function to handle in-bound deliveries that include a TokenBridge transfer + function receivePayloadAndTokens( + bytes memory payload, + TokenReceived[] memory receivedTokens, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) internal virtual {} } diff --git a/src/WormholeRelayer/CCTPBase.sol b/src/WormholeRelayer/CCTPBase.sol index 44e76b8..97bcc8d 100644 --- a/src/WormholeRelayer/CCTPBase.sol +++ b/src/WormholeRelayer/CCTPBase.sol @@ -11,228 +11,213 @@ import "wormhole-sdk/Utils.sol"; import "./Base.sol"; library CCTPMessageLib { - // The second standardized key type is a CCTP Key - // representing a CCTP transfer of USDC - // (on the IWormholeRelayer interface) - - // Note - the default delivery provider only will relay CCTP transfers that were sent - // in the same transaction that this message was emitted! - // (This will always be the case if 'CCTPSender' is used) - - uint8 constant CCTP_KEY_TYPE = 2; - - // encoded using abi.encodePacked(domain, nonce) - struct CCTPKey { - uint32 domain; - uint64 nonce; - } - - // encoded using abi.encode(message, signature) - struct CCTPMessage { - bytes message; - bytes signature; - } + // The second standardized key type is a CCTP Key + // representing a CCTP transfer of USDC + // (on the IWormholeRelayer interface) + + // Note - the default delivery provider only will relay CCTP transfers that were sent + // in the same transaction that this message was emitted! + // (This will always be the case if 'CCTPSender' is used) + + uint8 constant CCTP_KEY_TYPE = 2; + + // encoded using abi.encodePacked(domain, nonce) + struct CCTPKey { + uint32 domain; + uint64 nonce; + } + + // encoded using abi.encode(message, signature) + struct CCTPMessage { + bytes message; + bytes signature; + } } abstract contract CCTPBase is Base { - ITokenMessenger immutable circleTokenMessenger; - IMessageTransmitter immutable circleMessageTransmitter; - address immutable USDC; - address cctpConfigurationOwner; - - constructor( - address _wormholeRelayer, - address _wormhole, - address _circleMessageTransmitter, - address _circleTokenMessenger, - address _USDC - ) Base(_wormholeRelayer, _wormhole) { - circleTokenMessenger = ITokenMessenger(_circleTokenMessenger); - circleMessageTransmitter = IMessageTransmitter( - _circleMessageTransmitter - ); - USDC = _USDC; - cctpConfigurationOwner = msg.sender; - } + ITokenMessenger immutable circleTokenMessenger; + IMessageTransmitter immutable circleMessageTransmitter; + address immutable USDC; + address cctpConfigurationOwner; + + constructor( + address _wormholeRelayer, + address _wormhole, + address _circleMessageTransmitter, + address _circleTokenMessenger, + address _USDC + ) Base(_wormholeRelayer, _wormhole) { + circleTokenMessenger = ITokenMessenger(_circleTokenMessenger); + circleMessageTransmitter = IMessageTransmitter(_circleMessageTransmitter); + USDC = _USDC; + cctpConfigurationOwner = msg.sender; + } } abstract contract CCTPSender is CCTPBase { - uint8 internal constant CONSISTENCY_LEVEL_FINALIZED = 15; - - using CCTPMessageLib for *; - - mapping(uint16 => uint32) public chainIdToCCTPDomain; - - /** - * Sets the CCTP Domain corresponding to chain 'chain' to be 'cctpDomain' - * So that transfers of USDC to chain 'chain' use the target CCTP domain 'cctpDomain' - * - * This action can only be performed by 'cctpConfigurationOwner', who is set to be the deployer - * - * Currently, cctp domains are: - * Ethereum: Wormhole chain id 2, cctp domain 0 - * Avalanche: Wormhole chain id 6, cctp domain 1 - * Optimism: Wormhole chain id 24, cctp domain 2 - * Arbitrum: Wormhole chain id 23, cctp domain 3 - * Base: Wormhole chain id 30, cctp domain 6 - * - * These can be set via: - * setCCTPDomain(2, 0); - * setCCTPDomain(6, 1); - * setCCTPDomain(24, 2); - * setCCTPDomain(23, 3); - * setCCTPDomain(30, 6); - */ - function setCCTPDomain(uint16 chain, uint32 cctpDomain) public { - require( - msg.sender == cctpConfigurationOwner, - "Not allowed to set CCTP Domain" - ); - chainIdToCCTPDomain[chain] = cctpDomain; - } - - function getCCTPDomain(uint16 chain) internal view returns (uint32) { - return chainIdToCCTPDomain[chain]; - } - - /** - * transferUSDC wraps common boilerplate for sending tokens to another chain using IWormholeRelayer - * - approves the Circle TokenMessenger contract to spend 'amount' of USDC - * - calls Circle's 'depositForBurnWithCaller' - * - returns key for inclusion in WormholeRelayer `additionalVaas` argument - * - * Note: this requires that only the targetAddress can redeem transfers. - * - */ - - function transferUSDC( - uint256 amount, - uint16 targetChain, - address targetAddress - ) internal returns (MessageKey memory) { - IERC20(USDC).approve(address(circleTokenMessenger), amount); - bytes32 targetAddressBytes32 = addressToBytes32CCTP(targetAddress); - uint64 nonce = circleTokenMessenger.depositForBurnWithCaller( - amount, - getCCTPDomain(targetChain), - targetAddressBytes32, - USDC, - targetAddressBytes32 - ); - return - MessageKey( - CCTPMessageLib.CCTP_KEY_TYPE, - abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce) - ); - } - - // Publishes a CCTP transfer of 'amount' of USDC - // and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain' - // - // The second step is done by publishing a wormhole message representing a request - // to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain' - // with the payload 'abi.encode(amount, payload)' - // (and we encode the amount so it can be checked on the target chain) - function sendUSDCWithPayloadToEvm( - uint16 targetChain, - address targetAddress, - bytes memory payload, - uint256 receiverValue, - uint256 gasLimit, - uint256 amount - ) internal returns (uint64 sequence) { - MessageKey[] memory messageKeys = new MessageKey[](1); - messageKeys[0] = transferUSDC(amount, targetChain, targetAddress); - - bytes memory userPayload = abi.encode(amount, payload); - address defaultDeliveryProvider = wormholeRelayer - .getDefaultDeliveryProvider(); - - (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( - targetChain, - receiverValue, - gasLimit - ); - - sequence = wormholeRelayer.sendToEvm{value: cost}( - targetChain, - targetAddress, - userPayload, - receiverValue, - 0, - gasLimit, - targetChain, - address(0x0), - defaultDeliveryProvider, - messageKeys, - CONSISTENCY_LEVEL_FINALIZED - ); - } - - function addressToBytes32CCTP(address addr) private pure returns (bytes32) { - return bytes32(uint256(uint160(addr))); - } + uint8 internal constant CONSISTENCY_LEVEL_FINALIZED = 15; + + using CCTPMessageLib for *; + + mapping(uint16 => uint32) public chainIdToCCTPDomain; + + /** + * Sets the CCTP Domain corresponding to chain 'chain' to be 'cctpDomain' + * So that transfers of USDC to chain 'chain' use the target CCTP domain 'cctpDomain' + * + * This action can only be performed by 'cctpConfigurationOwner', who is set to be the deployer + * + * Currently, cctp domains are: + * Ethereum: Wormhole chain id 2, cctp domain 0 + * Avalanche: Wormhole chain id 6, cctp domain 1 + * Optimism: Wormhole chain id 24, cctp domain 2 + * Arbitrum: Wormhole chain id 23, cctp domain 3 + * Base: Wormhole chain id 30, cctp domain 6 + * + * These can be set via: + * setCCTPDomain(2, 0); + * setCCTPDomain(6, 1); + * setCCTPDomain(24, 2); + * setCCTPDomain(23, 3); + * setCCTPDomain(30, 6); + */ + function setCCTPDomain(uint16 chain, uint32 cctpDomain) public { + require( + msg.sender == cctpConfigurationOwner, + "Not allowed to set CCTP Domain" + ); + chainIdToCCTPDomain[chain] = cctpDomain; + } + + function getCCTPDomain(uint16 chain) internal view returns (uint32) { + return chainIdToCCTPDomain[chain]; + } + + /** + * transferUSDC wraps common boilerplate for sending tokens to another chain using IWormholeRelayer + * - approves the Circle TokenMessenger contract to spend 'amount' of USDC + * - calls Circle's 'depositForBurnWithCaller' + * - returns key for inclusion in WormholeRelayer `additionalVaas` argument + * + * Note: this requires that only the targetAddress can redeem transfers. + * + */ + + function transferUSDC( + uint256 amount, + uint16 targetChain, + address targetAddress + ) internal returns (MessageKey memory) { + IERC20(USDC).approve(address(circleTokenMessenger), amount); + bytes32 targetAddressBytes32 = addressToBytes32CCTP(targetAddress); + uint64 nonce = circleTokenMessenger.depositForBurnWithCaller( + amount, + getCCTPDomain(targetChain), + targetAddressBytes32, + USDC, + targetAddressBytes32 + ); + return MessageKey( + CCTPMessageLib.CCTP_KEY_TYPE, + abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce) + ); + } + + // Publishes a CCTP transfer of 'amount' of USDC + // and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain' + // + // The second step is done by publishing a wormhole message representing a request + // to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain' + // with the payload 'abi.encode(amount, payload)' + // (and we encode the amount so it can be checked on the target chain) + function sendUSDCWithPayloadToEvm( + uint16 targetChain, + address targetAddress, + bytes memory payload, + uint256 receiverValue, + uint256 gasLimit, + uint256 amount + ) internal returns (uint64 sequence) { + MessageKey[] memory messageKeys = new MessageKey[](1); + messageKeys[0] = transferUSDC(amount, targetChain, targetAddress); + + bytes memory userPayload = abi.encode(amount, payload); + address defaultDeliveryProvider = wormholeRelayer.getDefaultDeliveryProvider(); + + (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit); + + sequence = wormholeRelayer.sendToEvm{value: cost}( + targetChain, + targetAddress, + userPayload, + receiverValue, + 0, + gasLimit, + targetChain, + address(0x0), + defaultDeliveryProvider, + messageKeys, + CONSISTENCY_LEVEL_FINALIZED + ); + } + + function addressToBytes32CCTP(address addr) private pure returns (bytes32) { + return bytes32(uint256(uint160(addr))); + } } abstract contract CCTPReceiver is CCTPBase { - function redeemUSDC( - bytes memory cctpMessage - ) internal returns (uint256 amount) { - (bytes memory message, bytes memory signature) = abi.decode( - cctpMessage, - (bytes, bytes) - ); - uint256 beforeBalance = IERC20(USDC).balanceOf(address(this)); - circleMessageTransmitter.receiveMessage(message, signature); - return IERC20(USDC).balanceOf(address(this)) - beforeBalance; - } - - function receiveWormholeMessages( - bytes memory payload, - bytes[] memory additionalMessages, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 deliveryHash - ) external payable { - // Currently, 'sendUSDCWithPayloadToEVM' only sends one CCTP transfer - // That can be modified if the integrator desires to send multiple CCTP transfers - // in which case the following code would have to be modified to support - // redeeming these multiple transfers and checking that their 'amount's are accurate - require( - additionalMessages.length <= 1, - "CCTP: At most one Message is supported" - ); - - uint256 amountUSDCReceived; - if (additionalMessages.length == 1) { - amountUSDCReceived = redeemUSDC(additionalMessages[0]); - } - - (uint256 amount, bytes memory userPayload) = abi.decode( - payload, - (uint256, bytes) - ); - - // Check that the correct amount was received - // It is important to verify that the 'USDC' sent in by the relayer is the same amount - // that the sender sent in on the source chain - require(amount == amountUSDCReceived, "Wrong amount received"); - - receivePayloadAndUSDC( - userPayload, - amountUSDCReceived, - sourceAddress, - sourceChain, - deliveryHash - ); - } - - // Implement this function to handle in-bound deliveries that include a CCTP transfer - function receivePayloadAndUSDC( - bytes memory payload, - uint256 amountUSDCReceived, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 deliveryHash - ) internal virtual {} + function redeemUSDC( + bytes memory cctpMessage + ) internal returns (uint256 amount) { + (bytes memory message, bytes memory signature) = abi.decode(cctpMessage, (bytes, bytes)); + uint256 beforeBalance = IERC20(USDC).balanceOf(address(this)); + circleMessageTransmitter.receiveMessage(message, signature); + return IERC20(USDC).balanceOf(address(this)) - beforeBalance; + } + + function receiveWormholeMessages( + bytes memory payload, + bytes[] memory additionalMessages, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) external payable { + // Currently, 'sendUSDCWithPayloadToEVM' only sends one CCTP transfer + // That can be modified if the integrator desires to send multiple CCTP transfers + // in which case the following code would have to be modified to support + // redeeming these multiple transfers and checking that their 'amount's are accurate + require( + additionalMessages.length <= 1, + "CCTP: At most one Message is supported" + ); + + uint256 amountUSDCReceived; + if (additionalMessages.length == 1) + amountUSDCReceived = redeemUSDC(additionalMessages[0]); + + (uint256 amount, bytes memory userPayload) = abi.decode(payload, (uint256, bytes)); + + // Check that the correct amount was received + // It is important to verify that the 'USDC' sent in by the relayer is the same amount + // that the sender sent in on the source chain + require(amount == amountUSDCReceived, "Wrong amount received"); + + receivePayloadAndUSDC( + userPayload, + amountUSDCReceived, + sourceAddress, + sourceChain, + deliveryHash + ); + } + + // Implement this function to handle in-bound deliveries that include a CCTP transfer + function receivePayloadAndUSDC( + bytes memory payload, + uint256 amountUSDCReceived, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) internal virtual {} } diff --git a/src/WormholeRelayer/TokenBase.sol b/src/WormholeRelayer/TokenBase.sol index ff0edd3..6438fbb 100644 --- a/src/WormholeRelayer/TokenBase.sol +++ b/src/WormholeRelayer/TokenBase.sol @@ -10,242 +10,224 @@ import "wormhole-sdk/Utils.sol"; import {Base} from "./Base.sol"; abstract contract TokenBase is Base { - ITokenBridge public immutable tokenBridge; - - constructor( - address _wormholeRelayer, - address _tokenBridge, - address _wormhole - ) Base(_wormholeRelayer, _wormhole) { - tokenBridge = ITokenBridge(_tokenBridge); - } + ITokenBridge public immutable tokenBridge; + + constructor( + address _wormholeRelayer, + address _tokenBridge, + address _wormhole + ) Base(_wormholeRelayer, _wormhole) { + tokenBridge = ITokenBridge(_tokenBridge); + } } abstract contract TokenSender is TokenBase { - /** - * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer - * - approves tokenBridge to spend 'amount' of 'token' - * - emits token transfer VAA - * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument - * - * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress - * can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by - * the offchain relayer and the target contract would have to be hardened against this. - * - */ - function transferTokens( - address token, - uint256 amount, - uint16 targetChain, - address targetAddress - ) internal returns (VaaKey memory) { - return - transferTokens( - token, - amount, - targetChain, - targetAddress, - bytes("") - ); - } - - /** - * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer. - * A payload can be included in the transfer vaa. By including a payload here instead of the deliveryVaa, - * fewer trust assumptions are placed on the WormholeRelayer contract. - * - * - approves tokenBridge to spend 'amount' of 'token' - * - emits token transfer VAA - * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument - * - * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress - * can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by - * the offchain relayer and the target contract would have to be hardened against this. - */ - function transferTokens( - address token, - uint256 amount, - uint16 targetChain, - address targetAddress, - bytes memory payload - ) internal returns (VaaKey memory) { - IERC20(token).approve(address(tokenBridge), amount); - uint64 sequence = tokenBridge.transferTokensWithPayload{ - value: wormhole.messageFee() - }( - token, - amount, - targetChain, - toUniversalAddress(targetAddress), - 0, - payload - ); - return - VaaKey({ - emitterAddress: toUniversalAddress(address(tokenBridge)), - chainId: wormhole.chainId(), - sequence: sequence - }); - } - - // Publishes a wormhole message representing a 'TokenBridge' transfer of 'amount' of 'token' - // and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain' - // - // The second step is done by publishing a wormhole message representing a request - // to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain' - // with the payload 'payload' - function sendTokenWithPayloadToEvm( - uint16 targetChain, - address targetAddress, - bytes memory payload, - uint256 receiverValue, - uint256 gasLimit, - address token, - uint256 amount - ) internal returns (uint64) { - VaaKey[] memory vaaKeys = new VaaKey[](1); - vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress); - - (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( - targetChain, - receiverValue, - gasLimit - ); - return - wormholeRelayer.sendVaasToEvm{value: cost}( - targetChain, - targetAddress, - payload, - receiverValue, - gasLimit, - vaaKeys - ); - } - - function sendTokenWithPayloadToEvm( - uint16 targetChain, - address targetAddress, - bytes memory payload, - uint256 receiverValue, - uint256 gasLimit, - address token, - uint256 amount, - uint16 refundChain, - address refundAddress - ) internal returns (uint64) { - VaaKey[] memory vaaKeys = new VaaKey[](1); - vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress); - - (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( - targetChain, - receiverValue, - gasLimit - ); - return - wormholeRelayer.sendVaasToEvm{value: cost}( - targetChain, - targetAddress, - payload, - receiverValue, - gasLimit, - vaaKeys, - refundChain, - refundAddress - ); - } + /** + * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer + * - approves tokenBridge to spend 'amount' of 'token' + * - emits token transfer VAA + * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument + * + * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress + * can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by + * the offchain relayer and the target contract would have to be hardened against this. + * + */ + function transferTokens( + address token, + uint256 amount, + uint16 targetChain, + address targetAddress + ) internal returns (VaaKey memory) { + return transferTokens( + token, + amount, + targetChain, + targetAddress, + bytes("") + ); + } + + /** + * transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer. + * A payload can be included in the transfer vaa. By including a payload here instead of the deliveryVaa, + * fewer trust assumptions are placed on the WormholeRelayer contract. + * + * - approves tokenBridge to spend 'amount' of 'token' + * - emits token transfer VAA + * - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument + * + * Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress + * can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by + * the offchain relayer and the target contract would have to be hardened against this. + */ + function transferTokens( + address token, + uint256 amount, + uint16 targetChain, + address targetAddress, + bytes memory payload + ) internal returns (VaaKey memory) { + IERC20(token).approve(address(tokenBridge), amount); + uint64 sequence = tokenBridge.transferTokensWithPayload{ + value: wormhole.messageFee() + }( + token, + amount, + targetChain, + toUniversalAddress(targetAddress), + 0, + payload + ); + + return VaaKey({ + emitterAddress: toUniversalAddress(address(tokenBridge)), + chainId: wormhole.chainId(), + sequence: sequence + }); + } + + // Publishes a wormhole message representing a 'TokenBridge' transfer of 'amount' of 'token' + // and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain' + // + // The second step is done by publishing a wormhole message representing a request + // to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain' + // with the payload 'payload' + function sendTokenWithPayloadToEvm( + uint16 targetChain, + address targetAddress, + bytes memory payload, + uint256 receiverValue, + uint256 gasLimit, + address token, + uint256 amount + ) internal returns (uint64) { + VaaKey[] memory vaaKeys = new VaaKey[](1); + vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress); + + (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit); + return wormholeRelayer.sendVaasToEvm{value: cost}( + targetChain, + targetAddress, + payload, + receiverValue, + gasLimit, + vaaKeys + ); + } + + function sendTokenWithPayloadToEvm( + uint16 targetChain, + address targetAddress, + bytes memory payload, + uint256 receiverValue, + uint256 gasLimit, + address token, + uint256 amount, + uint16 refundChain, + address refundAddress + ) internal returns (uint64) { + VaaKey[] memory vaaKeys = new VaaKey[](1); + vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress); + + (uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit); + return wormholeRelayer.sendVaasToEvm{value: cost}( + targetChain, + targetAddress, + payload, + receiverValue, + gasLimit, + vaaKeys, + refundChain, + refundAddress + ); + } } abstract contract TokenReceiver is TokenBase { - struct TokenReceived { - bytes32 tokenHomeAddress; - uint16 tokenHomeChain; - address tokenAddress; // wrapped address if tokenHomeChain !== this chain, else tokenHomeAddress (in evm address format) - uint256 amount; - uint256 amountNormalized; // if decimals > 8, normalized to 8 decimal places - } - - function getDecimals( - address tokenAddress - ) internal view returns (uint8 decimals) { - // query decimals - (, bytes memory queriedDecimals) = address(tokenAddress).staticcall( - abi.encodeWithSignature("decimals()") - ); - decimals = abi.decode(queriedDecimals, (uint8)); - } - - function getTokenAddressOnThisChain( - uint16 tokenHomeChain, - bytes32 tokenHomeAddress - ) internal view returns (address tokenAddressOnThisChain) { - return - tokenHomeChain == wormhole.chainId() - ? fromUniversalAddress(tokenHomeAddress) - : tokenBridge.wrappedAsset(tokenHomeChain, tokenHomeAddress); - } - - function receiveWormholeMessages( - bytes memory payload, - bytes[] memory additionalVaas, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 deliveryHash - ) external payable { - TokenReceived[] memory receivedTokens = new TokenReceived[]( - additionalVaas.length - ); - - for (uint256 i = 0; i < additionalVaas.length; ++i) { - IWormhole.VM memory parsed = wormhole.parseVM(additionalVaas[i]); - require( - parsed.emitterAddress == - tokenBridge.bridgeContracts(parsed.emitterChainId), - "Not a Token Bridge VAA" - ); - ITokenBridge.TransferWithPayload memory transfer = tokenBridge - .parseTransferWithPayload(parsed.payload); - require( - transfer.to == toUniversalAddress(address(this)) && - transfer.toChain == wormhole.chainId(), - "Token was not sent to this address" - ); - - tokenBridge.completeTransferWithPayload(additionalVaas[i]); - - address thisChainTokenAddress = getTokenAddressOnThisChain( - transfer.tokenChain, - transfer.tokenAddress - ); - uint8 decimals = getDecimals(thisChainTokenAddress); - uint256 denormalizedAmount = transfer.amount; - if (decimals > 8) - denormalizedAmount *= uint256(10) ** (decimals - 8); - - receivedTokens[i] = TokenReceived({ - tokenHomeAddress: transfer.tokenAddress, - tokenHomeChain: transfer.tokenChain, - tokenAddress: thisChainTokenAddress, - amount: denormalizedAmount, - amountNormalized: transfer.amount - }); - } - - // call into overriden method - receivePayloadAndTokens( - payload, - receivedTokens, - sourceAddress, - sourceChain, - deliveryHash - ); + struct TokenReceived { + bytes32 tokenHomeAddress; + uint16 tokenHomeChain; + address tokenAddress; // wrapped address if tokenHomeChain !== this chain, else tokenHomeAddress (in evm address format) + uint256 amount; + uint256 amountNormalized; // if decimals > 8, normalized to 8 decimal places + } + + function getDecimals( + address tokenAddress + ) internal view returns (uint8 decimals) { + // query decimals + (, bytes memory queriedDecimals) = address(tokenAddress).staticcall( + abi.encodeWithSignature("decimals()") + ); + decimals = abi.decode(queriedDecimals, (uint8)); + } + + function getTokenAddressOnThisChain( + uint16 tokenHomeChain, + bytes32 tokenHomeAddress + ) internal view returns (address tokenAddressOnThisChain) { + return tokenHomeChain == wormhole.chainId() + ? fromUniversalAddress(tokenHomeAddress) + : tokenBridge.wrappedAsset(tokenHomeChain, tokenHomeAddress); + } + + function receiveWormholeMessages( + bytes memory payload, + bytes[] memory additionalVaas, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) external payable { + TokenReceived[] memory receivedTokens = new TokenReceived[](additionalVaas.length); + + for (uint256 i = 0; i < additionalVaas.length; ++i) { + IWormhole.VM memory parsed = wormhole.parseVM(additionalVaas[i]); + require( + parsed.emitterAddress == tokenBridge.bridgeContracts(parsed.emitterChainId), + "Not a Token Bridge VAA" + ); + ITokenBridge.TransferWithPayload memory transfer = + tokenBridge.parseTransferWithPayload(parsed.payload); + require( + transfer.to == toUniversalAddress(address(this)) && transfer.toChain == wormhole.chainId(), + "Token was not sent to this address" + ); + + tokenBridge.completeTransferWithPayload(additionalVaas[i]); + + address thisChainTokenAddress = + getTokenAddressOnThisChain(transfer.tokenChain, transfer.tokenAddress); + uint8 decimals = getDecimals(thisChainTokenAddress); + uint256 denormalizedAmount = transfer.amount; + if (decimals > 8) + denormalizedAmount *= uint256(10) ** (decimals - 8); + + receivedTokens[i] = TokenReceived({ + tokenHomeAddress: transfer.tokenAddress, + tokenHomeChain: transfer.tokenChain, + tokenAddress: thisChainTokenAddress, + amount: denormalizedAmount, + amountNormalized: transfer.amount + }); } - // Implement this function to handle in-bound deliveries that include a TokenBridge transfer - function receivePayloadAndTokens( - bytes memory payload, - TokenReceived[] memory receivedTokens, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 deliveryHash - ) internal virtual {} + // call into overriden method + receivePayloadAndTokens( + payload, + receivedTokens, + sourceAddress, + sourceChain, + deliveryHash + ); + } + + // Implement this function to handle in-bound deliveries that include a TokenBridge transfer + function receivePayloadAndTokens( + bytes memory payload, + TokenReceived[] memory receivedTokens, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 deliveryHash + ) internal virtual {} } diff --git a/src/libraries/BytesParsing.sol b/src/libraries/BytesParsing.sol index 731db7f..937152b 100644 --- a/src/libraries/BytesParsing.sol +++ b/src/libraries/BytesParsing.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.4; library BytesParsing { - uint256 private constant freeMemoryPtr = 0x40; - uint256 private constant wordSize = 32; + uint256 private constant _FREE_MEMORY_PTR = 0x40; + uint256 private constant _WORD_SIZE = 32; error OutOfBounds(uint256 offset, uint256 length); error LengthMismatch(uint256 encodedLength, uint256 expectedLength); @@ -49,7 +49,7 @@ library BytesParsing { /// @solidity memory-safe-assembly assembly { nextOffset := add(offset, length) - ret := mload(freeMemoryPtr) + ret := mload(_FREE_MEMORY_PTR) //Explanation on how we copy data here: // The bytes type has the following layout in memory: @@ -65,7 +65,7 @@ library BytesParsing { // overwritting those garbage bytes. let shift := and(length, 31) //equivalent to `mod(length, 32)` but 2 gas cheaper if iszero(shift) { - shift := wordSize + shift := _WORD_SIZE } let dest := add(ret, shift) @@ -73,8 +73,8 @@ library BytesParsing { for { let src := add(add(encoded, shift), offset) } lt(dest, end) { - src := add(src, wordSize) - dest := add(dest, wordSize) + src := add(src, _WORD_SIZE) + dest := add(dest, _WORD_SIZE) } { mstore(dest, mload(src)) } @@ -82,7 +82,7 @@ library BytesParsing { mstore(ret, length) //When compiling with --via-ir then normally allocated memory (i.e. via new) will have 32 byte // memory alignment and so we enforce the same memory alignment here. - mstore(freeMemoryPtr, and(add(dest, 31), not(31))) + mstore(_FREE_MEMORY_PTR, and(add(dest, 31), not(31))) } } @@ -218,7 +218,7 @@ function asBytes${bytes}Unchecked( ) internal pure returns (bytes${bytes} ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, ${bytes}) } } @@ -260,7 +260,7 @@ function asBytes${bytes}( ) internal pure returns (bytes1 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 1) } } @@ -298,7 +298,7 @@ function asBytes${bytes}( ) internal pure returns (bytes2 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 2) } } @@ -336,7 +336,7 @@ function asBytes${bytes}( ) internal pure returns (bytes3 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 3) } } @@ -374,7 +374,7 @@ function asBytes${bytes}( ) internal pure returns (bytes4 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 4) } } @@ -412,7 +412,7 @@ function asBytes${bytes}( ) internal pure returns (bytes5 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 5) } } @@ -450,7 +450,7 @@ function asBytes${bytes}( ) internal pure returns (bytes6 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 6) } } @@ -488,7 +488,7 @@ function asBytes${bytes}( ) internal pure returns (bytes7 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 7) } } @@ -526,7 +526,7 @@ function asBytes${bytes}( ) internal pure returns (bytes8 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 8) } } @@ -564,7 +564,7 @@ function asBytes${bytes}( ) internal pure returns (bytes9 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 9) } } @@ -602,7 +602,7 @@ function asBytes${bytes}( ) internal pure returns (bytes10 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 10) } } @@ -640,7 +640,7 @@ function asBytes${bytes}( ) internal pure returns (bytes11 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 11) } } @@ -678,7 +678,7 @@ function asBytes${bytes}( ) internal pure returns (bytes12 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 12) } } @@ -716,7 +716,7 @@ function asBytes${bytes}( ) internal pure returns (bytes13 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 13) } } @@ -754,7 +754,7 @@ function asBytes${bytes}( ) internal pure returns (bytes14 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 14) } } @@ -792,7 +792,7 @@ function asBytes${bytes}( ) internal pure returns (bytes15 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 15) } } @@ -830,7 +830,7 @@ function asBytes${bytes}( ) internal pure returns (bytes16 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 16) } } @@ -868,7 +868,7 @@ function asBytes${bytes}( ) internal pure returns (bytes17 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 17) } } @@ -906,7 +906,7 @@ function asBytes${bytes}( ) internal pure returns (bytes18 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 18) } } @@ -944,7 +944,7 @@ function asBytes${bytes}( ) internal pure returns (bytes19 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 19) } } @@ -982,7 +982,7 @@ function asBytes${bytes}( ) internal pure returns (bytes20 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 20) } } @@ -1020,7 +1020,7 @@ function asBytes${bytes}( ) internal pure returns (bytes21 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 21) } } @@ -1058,7 +1058,7 @@ function asBytes${bytes}( ) internal pure returns (bytes22 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 22) } } @@ -1096,7 +1096,7 @@ function asBytes${bytes}( ) internal pure returns (bytes23 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 23) } } @@ -1134,7 +1134,7 @@ function asBytes${bytes}( ) internal pure returns (bytes24 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 24) } } @@ -1172,7 +1172,7 @@ function asBytes${bytes}( ) internal pure returns (bytes25 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 25) } } @@ -1210,7 +1210,7 @@ function asBytes${bytes}( ) internal pure returns (bytes26 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 26) } } @@ -1248,7 +1248,7 @@ function asBytes${bytes}( ) internal pure returns (bytes27 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 27) } } @@ -1286,7 +1286,7 @@ function asBytes${bytes}( ) internal pure returns (bytes28 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 28) } } @@ -1324,7 +1324,7 @@ function asBytes${bytes}( ) internal pure returns (bytes29 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 29) } } @@ -1362,7 +1362,7 @@ function asBytes${bytes}( ) internal pure returns (bytes30 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 30) } } @@ -1400,7 +1400,7 @@ function asBytes${bytes}( ) internal pure returns (bytes31 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 31) } } @@ -1438,7 +1438,7 @@ function asBytes${bytes}( ) internal pure returns (bytes32 ret, uint nextOffset) { /// @solidity memory-safe-assembly assembly { - ret := mload(add(encoded, add(offset, wordSize))) + ret := mload(add(encoded, add(offset, _WORD_SIZE))) nextOffset := add(offset, 32) } } diff --git a/src/libraries/WormholeCctpMessages.sol b/src/libraries/WormholeCctpMessages.sol index ef4841d..e3ee34a 100644 --- a/src/libraries/WormholeCctpMessages.sol +++ b/src/libraries/WormholeCctpMessages.sol @@ -6,195 +6,184 @@ import {BytesParsing} from "wormhole-sdk/libraries/BytesParsing.sol"; import {toUniversalAddress} from "wormhole-sdk/Utils.sol"; library WormholeCctpMessages { - using { toUniversalAddress } for address; - using BytesParsing for bytes; - - // Payload IDs. - // - // NOTE: This library reserves payloads 1 through 10 for future use. When using this library, - // please consider starting your own Wormhole message payloads at 11. - uint8 private constant DEPOSIT = 1; - uint8 private constant RESERVED_2 = 2; - uint8 private constant RESERVED_3 = 3; - uint8 private constant RESERVED_4 = 4; - uint8 private constant RESERVED_5 = 5; - uint8 private constant RESERVED_6 = 6; - uint8 private constant RESERVED_7 = 7; - uint8 private constant RESERVED_8 = 8; - uint8 private constant RESERVED_9 = 9; - uint8 private constant RESERVED_10 = 10; - - error MissingPayload(); - error PayloadTooLarge(uint256); - error InvalidMessage(); - - /** - * @dev NOTE: This method encodes the Wormhole message payload assuming the payload ID == 1. - */ - function encodeDeposit( - address token, - uint256 amount, - uint32 sourceCctpDomain, - uint32 targetCctpDomain, - uint64 cctpNonce, - bytes32 burnSource, - bytes32 mintRecipient, - bytes memory payload - ) internal pure returns (bytes memory encoded) { - encoded = encodeDeposit( - token.toUniversalAddress(), - DEPOSIT, - amount, - sourceCctpDomain, - targetCctpDomain, - cctpNonce, - burnSource, - mintRecipient, - payload - ); - } - - /** - * @dev NOTE: This method encodes the Wormhole message payload assuming the payload ID == 1. - */ - function encodeDeposit( - bytes32 universalTokenAddress, - uint256 amount, - uint32 sourceCctpDomain, - uint32 targetCctpDomain, - uint64 cctpNonce, - bytes32 burnSource, - bytes32 mintRecipient, - bytes memory payload - ) internal pure returns (bytes memory encoded) { - encoded = encodeDeposit( - universalTokenAddress, - DEPOSIT, - amount, - sourceCctpDomain, - targetCctpDomain, - cctpNonce, - burnSource, - mintRecipient, - payload - ); - } - - function encodeDeposit( - address token, - uint8 payloadId, - uint256 amount, - uint32 sourceCctpDomain, - uint32 targetCctpDomain, - uint64 cctpNonce, - bytes32 burnSource, - bytes32 mintRecipient, - bytes memory payload - ) internal pure returns (bytes memory encoded) { - encoded = encodeDeposit( - token.toUniversalAddress(), - payloadId, - amount, - sourceCctpDomain, - targetCctpDomain, - cctpNonce, - burnSource, - mintRecipient, - payload - ); - } - - function encodeDeposit( - bytes32 universalTokenAddress, - uint8 payloadId, - uint256 amount, - uint32 sourceCctpDomain, - uint32 targetCctpDomain, - uint64 cctpNonce, - bytes32 burnSource, - bytes32 mintRecipient, - bytes memory payload - ) internal pure returns (bytes memory encoded) { - uint256 payloadLen = payload.length; - if (payloadLen == 0) { - revert MissingPayload(); - } else if (payloadLen > type(uint16).max) { - revert PayloadTooLarge(payloadLen); - } - - encoded = abi.encodePacked( - payloadId, - universalTokenAddress, - amount, - sourceCctpDomain, - targetCctpDomain, - cctpNonce, - burnSource, - mintRecipient, - uint16(payloadLen), - payload - ); - } - - // left in for backwards compatibility - function decodeDeposit(IWormhole.VM memory vaa) - internal - pure - returns ( - bytes32 token, - uint256 amount, - uint32 sourceCctpDomain, - uint32 targetCctpDomain, - uint64 cctpNonce, - bytes32 burnSource, - bytes32 mintRecipient, - bytes memory payload - ) - { - return decodeDeposit(vaa.payload); - } - - function decodeDeposit(bytes memory encoded) - internal - pure - returns ( - bytes32 token, - uint256 amount, - uint32 sourceCctpDomain, - uint32 targetCctpDomain, - uint64 cctpNonce, - bytes32 burnSource, - bytes32 mintRecipient, - bytes memory payload - ) - { - uint256 offset = _checkPayloadId(encoded, 0, DEPOSIT); - - (token, offset) = encoded.asBytes32Unchecked(offset); - (amount, offset) = encoded.asUint256Unchecked(offset); - (sourceCctpDomain, offset) = encoded.asUint32Unchecked(offset); - (targetCctpDomain, offset) = encoded.asUint32Unchecked(offset); - (cctpNonce, offset) = encoded.asUint64Unchecked(offset); - (burnSource, offset) = encoded.asBytes32Unchecked(offset); - (mintRecipient, offset) = encoded.asBytes32Unchecked(offset); - uint16 payloadLength; - (payloadLength, offset) = encoded.asUint16Unchecked(offset); - (payload, offset) = encoded.sliceUnchecked(offset, payloadLength); - - encoded.checkLength(offset); - } - - // ---------------------------------------- private ------------------------------------------- - - function _checkPayloadId( - bytes memory encoded, - uint256 startOffset, - uint8 expectedPayloadId - ) private pure returns (uint256 offset) { - uint8 parsedPayloadId; - (parsedPayloadId, offset) = encoded.asUint8Unchecked(startOffset); - - if (parsedPayloadId != expectedPayloadId) - revert InvalidMessage(); - } + using { toUniversalAddress } for address; + using BytesParsing for bytes; + + // Payload IDs. + // + // NOTE: This library reserves payloads 1 through 10 for future use. When using this library, + // please consider starting your own Wormhole message payloads at 11. + uint8 private constant DEPOSIT = 1; + uint8 private constant RESERVED_2 = 2; + uint8 private constant RESERVED_3 = 3; + uint8 private constant RESERVED_4 = 4; + uint8 private constant RESERVED_5 = 5; + uint8 private constant RESERVED_6 = 6; + uint8 private constant RESERVED_7 = 7; + uint8 private constant RESERVED_8 = 8; + uint8 private constant RESERVED_9 = 9; + uint8 private constant RESERVED_10 = 10; + + error MissingPayload(); + error PayloadTooLarge(uint256); + error InvalidMessage(); + + /** + * @dev NOTE: This method encodes the Wormhole message payload assuming the payload ID == 1. + */ + function encodeDeposit( + address token, + uint256 amount, + uint32 sourceCctpDomain, + uint32 targetCctpDomain, + uint64 cctpNonce, + bytes32 burnSource, + bytes32 mintRecipient, + bytes memory payload + ) internal pure returns (bytes memory encoded) { + encoded = encodeDeposit( + token.toUniversalAddress(), + DEPOSIT, + amount, + sourceCctpDomain, + targetCctpDomain, + cctpNonce, + burnSource, + mintRecipient, + payload + ); + } + + /** + * @dev NOTE: This method encodes the Wormhole message payload assuming the payload ID == 1. + */ + function encodeDeposit( + bytes32 universalTokenAddress, + uint256 amount, + uint32 sourceCctpDomain, + uint32 targetCctpDomain, + uint64 cctpNonce, + bytes32 burnSource, + bytes32 mintRecipient, + bytes memory payload + ) internal pure returns (bytes memory encoded) { + encoded = encodeDeposit( + universalTokenAddress, + DEPOSIT, + amount, + sourceCctpDomain, + targetCctpDomain, + cctpNonce, + burnSource, + mintRecipient, + payload + ); + } + + function encodeDeposit( + address token, + uint8 payloadId, + uint256 amount, + uint32 sourceCctpDomain, + uint32 targetCctpDomain, + uint64 cctpNonce, + bytes32 burnSource, + bytes32 mintRecipient, + bytes memory payload + ) internal pure returns (bytes memory encoded) { + encoded = encodeDeposit( + token.toUniversalAddress(), + payloadId, + amount, + sourceCctpDomain, + targetCctpDomain, + cctpNonce, + burnSource, + mintRecipient, + payload + ); + } + + function encodeDeposit( + bytes32 universalTokenAddress, + uint8 payloadId, + uint256 amount, + uint32 sourceCctpDomain, + uint32 targetCctpDomain, + uint64 cctpNonce, + bytes32 burnSource, + bytes32 mintRecipient, + bytes memory payload + ) internal pure returns (bytes memory encoded) { + uint256 payloadLen = payload.length; + if (payloadLen == 0) + revert MissingPayload(); + else if (payloadLen > type(uint16).max) + revert PayloadTooLarge(payloadLen); + + encoded = abi.encodePacked( + payloadId, + universalTokenAddress, + amount, + sourceCctpDomain, + targetCctpDomain, + cctpNonce, + burnSource, + mintRecipient, + uint16(payloadLen), + payload + ); + } + + // left in for backwards compatibility + function decodeDeposit(IWormhole.VM memory vaa) internal pure returns ( + bytes32 token, + uint256 amount, + uint32 sourceCctpDomain, + uint32 targetCctpDomain, + uint64 cctpNonce, + bytes32 burnSource, + bytes32 mintRecipient, + bytes memory payload + ) { + return decodeDeposit(vaa.payload); + } + + function decodeDeposit(bytes memory encoded) internal pure returns ( + bytes32 token, + uint256 amount, + uint32 sourceCctpDomain, + uint32 targetCctpDomain, + uint64 cctpNonce, + bytes32 burnSource, + bytes32 mintRecipient, + bytes memory payload + ) { + uint256 offset = _checkPayloadId(encoded, 0, DEPOSIT); + + (token, offset) = encoded.asBytes32Unchecked(offset); + (amount, offset) = encoded.asUint256Unchecked(offset); + (sourceCctpDomain, offset) = encoded.asUint32Unchecked(offset); + (targetCctpDomain, offset) = encoded.asUint32Unchecked(offset); + (cctpNonce, offset) = encoded.asUint64Unchecked(offset); + (burnSource, offset) = encoded.asBytes32Unchecked(offset); + (mintRecipient, offset) = encoded.asBytes32Unchecked(offset); + (payload, offset) = encoded.sliceUint16PrefixedUnchecked(offset); + + encoded.checkLength(offset); + } + + // ---------------------------------------- private ------------------------------------------- + + function _checkPayloadId( + bytes memory encoded, + uint256 startOffset, + uint8 expectedPayloadId + ) private pure returns (uint256 offset) { + uint8 parsedPayloadId; + (parsedPayloadId, offset) = encoded.asUint8Unchecked(startOffset); + + if (parsedPayloadId != expectedPayloadId) + revert InvalidMessage(); + } } diff --git a/src/testing/CctpMessages.sol b/src/testing/CctpMessages.sol index ba2f36a..8a99ea7 100644 --- a/src/testing/CctpMessages.sol +++ b/src/testing/CctpMessages.sol @@ -95,7 +95,7 @@ library CctpMessages { function isCctpTokenBurnMessage(bytes memory encoded) internal pure returns (bool) { if (encoded.length != _CCTP_TOKEN_BURN_MESSAGE_SIZE) return false; - + (uint headerVersion,) = encoded.asUint32Unchecked(0); (uint bodyVersion, ) = encoded.asUint32Unchecked(_CCTP_HEADER_SIZE); return headerVersion == MESSAGE_TRANSMITTER_HEADER_VERSION && diff --git a/src/testing/CctpOverride.sol b/src/testing/CctpOverride.sol index 8bf1c98..c2ee1f5 100644 --- a/src/testing/CctpOverride.sol +++ b/src/testing/CctpOverride.sol @@ -31,7 +31,7 @@ library CctpOverride { require(attesterPrivateKey(messageTransmitter) == 0, "CctpOverride: already set up"); require(messageTransmitter.version() == CctpMessages.MESSAGE_TRANSMITTER_HEADER_VERSION); - + //as pioneered in WormholeOverride vm.store(address(messageTransmitter), _ATTESTER_PK_SLOT, bytes32(signer)); diff --git a/src/testing/ERC20Mock.sol b/src/testing/ERC20Mock.sol index d7ee38e..3380a03 100644 --- a/src/testing/ERC20Mock.sol +++ b/src/testing/ERC20Mock.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import "IERC20/IERC20.sol"; /* - * ERC20 impl from solmate + * ERC20 impl from solmate */ /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. diff --git a/src/testing/LogUtils.sol b/src/testing/LogUtils.sol index add3d67..880f77c 100644 --- a/src/testing/LogUtils.sol +++ b/src/testing/LogUtils.sol @@ -14,7 +14,7 @@ library LogUtils { function filter( Vm.Log[] memory logs, bytes32 topic - ) internal pure returns (Vm.Log[] memory) { + ) internal pure returns (Vm.Log[] memory) { return filter(logs, address(0), topic, _noDataFilter); } @@ -22,7 +22,7 @@ library LogUtils { Vm.Log[] memory logs, address emitter, bytes32 topic - ) internal pure returns (Vm.Log[] memory) { + ) internal pure returns (Vm.Log[] memory) { return filter(logs, emitter, topic, _noDataFilter); } diff --git a/src/testing/WormholeOverride.sol b/src/testing/WormholeOverride.sol index 99cdc8a..ff5e58d 100644 --- a/src/testing/WormholeOverride.sol +++ b/src/testing/WormholeOverride.sol @@ -31,7 +31,7 @@ library WormholeOverride { // keccak256("devnetGuardianPrivateKey") - 1 bytes32 private constant _DEVNET_GUARDIAN_PK_SLOT = 0x4c7087e9f1bf599f9f9fff4deb3ecae99b29adaab34a0f53d9fa9d61aeaecb63; - + uint32 constant DEFAULT_NONCE = 0xBBBBBBBB; uint8 constant DEFAULT_CONSISTENCY_LEVEL = 1; uint8 constant WORMHOLE_VAA_VERSION = 1; diff --git a/src/testing/WormholeRelayer/DeliveryInstructionDecoder.sol b/src/testing/WormholeRelayer/DeliveryInstructionDecoder.sol index 956b896..27d01aa 100644 --- a/src/testing/WormholeRelayer/DeliveryInstructionDecoder.sol +++ b/src/testing/WormholeRelayer/DeliveryInstructionDecoder.sol @@ -13,239 +13,202 @@ uint8 constant PAYLOAD_ID_REDELIVERY_INSTRUCTION = 2; using BytesParsing for bytes; struct DeliveryInstruction { - uint16 targetChain; - bytes32 targetAddress; - bytes payload; - uint256 requestedReceiverValue; - uint256 extraReceiverValue; - bytes encodedExecutionInfo; - uint16 refundChain; - bytes32 refundAddress; - bytes32 refundDeliveryProvider; - bytes32 sourceDeliveryProvider; - bytes32 senderAddress; - MessageKey[] messageKeys; + uint16 targetChain; + bytes32 targetAddress; + bytes payload; + uint256 requestedReceiverValue; + uint256 extraReceiverValue; + bytes encodedExecutionInfo; + uint16 refundChain; + bytes32 refundAddress; + bytes32 refundDeliveryProvider; + bytes32 sourceDeliveryProvider; + bytes32 senderAddress; + MessageKey[] messageKeys; } struct RedeliveryInstruction { - VaaKey deliveryVaaKey; - uint16 targetChain; - uint256 newRequestedReceiverValue; - bytes newEncodedExecutionInfo; - bytes32 newSourceDeliveryProvider; - bytes32 newSenderAddress; + VaaKey deliveryVaaKey; + uint16 targetChain; + uint256 newRequestedReceiverValue; + bytes newEncodedExecutionInfo; + bytes32 newSourceDeliveryProvider; + bytes32 newSenderAddress; } struct DeliveryOverride { - uint256 newReceiverValue; - bytes newExecutionInfo; - bytes32 redeliveryHash; + uint256 newReceiverValue; + bytes newExecutionInfo; + bytes32 redeliveryHash; } function decodeDeliveryInstruction( - bytes memory encoded + bytes memory encoded ) pure returns (DeliveryInstruction memory strct) { - uint256 offset = checkUint8(encoded, 0, PAYLOAD_ID_DELIVERY_INSTRUCTION); + uint256 offset = checkUint8(encoded, 0, PAYLOAD_ID_DELIVERY_INSTRUCTION); - uint256 requestedReceiverValue; - uint256 extraReceiverValue; + (strct.targetChain, offset) = encoded.asUint16Unchecked(offset); + (strct.targetAddress, offset) = encoded.asBytes32Unchecked(offset); + (strct.payload, offset) = decodeBytes(encoded, offset); + (strct.requestedReceiverValue, offset) = encoded.asUint256Unchecked(offset); + (strct.extraReceiverValue, offset) = encoded.asUint256Unchecked(offset); + (strct.encodedExecutionInfo, offset) = decodeBytes(encoded, offset); + (strct.refundChain, offset) = encoded.asUint16Unchecked(offset); + (strct.refundAddress, offset) = encoded.asBytes32Unchecked(offset); + (strct.refundDeliveryProvider, offset) = encoded.asBytes32Unchecked(offset); + (strct.sourceDeliveryProvider, offset) = encoded.asBytes32Unchecked(offset); + (strct.senderAddress, offset) = encoded.asBytes32Unchecked(offset); + (strct.messageKeys, offset) = decodeMessageKeyArray(encoded, offset); - (strct.targetChain, offset) = encoded.asUint16Unchecked(offset); - (strct.targetAddress, offset) = encoded.asBytes32Unchecked(offset); - (strct.payload, offset) = decodeBytes(encoded, offset); - (requestedReceiverValue, offset) = encoded.asUint256Unchecked(offset); - (extraReceiverValue, offset) = encoded.asUint256Unchecked(offset); - (strct.encodedExecutionInfo, offset) = decodeBytes(encoded, offset); - (strct.refundChain, offset) = encoded.asUint16Unchecked(offset); - (strct.refundAddress, offset) = encoded.asBytes32Unchecked(offset); - (strct.refundDeliveryProvider, offset) = encoded.asBytes32Unchecked(offset); - (strct.sourceDeliveryProvider, offset) = encoded.asBytes32Unchecked(offset); - (strct.senderAddress, offset) = encoded.asBytes32Unchecked(offset); - (strct.messageKeys, offset) = decodeMessageKeyArray(encoded, offset); - - strct.requestedReceiverValue = requestedReceiverValue; - strct.extraReceiverValue = extraReceiverValue; - - encoded.checkLength(offset); + encoded.checkLength(offset); } function decodeRedeliveryInstruction( - bytes memory encoded + bytes memory encoded ) pure returns (RedeliveryInstruction memory strct) { - uint256 offset = checkUint8(encoded, 0, PAYLOAD_ID_REDELIVERY_INSTRUCTION); - - uint256 newRequestedReceiverValue; - offset = checkUint8(encoded, offset, VAA_KEY_TYPE); - (strct.deliveryVaaKey, offset) = decodeVaaKey(encoded, offset); - (strct.targetChain, offset) = encoded.asUint16Unchecked(offset); - (newRequestedReceiverValue, offset) = encoded.asUint256Unchecked(offset); - (strct.newEncodedExecutionInfo, offset) = decodeBytes(encoded, offset); - (strct.newSourceDeliveryProvider, offset) = encoded.asBytes32Unchecked( - offset - ); - (strct.newSenderAddress, offset) = encoded.asBytes32Unchecked(offset); + uint256 offset = checkUint8(encoded, 0, PAYLOAD_ID_REDELIVERY_INSTRUCTION); + offset = checkUint8(encoded, offset, VAA_KEY_TYPE); - strct.newRequestedReceiverValue = newRequestedReceiverValue; + (strct.deliveryVaaKey, offset) = decodeVaaKey(encoded, offset); + (strct.targetChain, offset) = encoded.asUint16Unchecked(offset); + (strct.newRequestedReceiverValue, offset) = encoded.asUint256Unchecked(offset); + (strct.newEncodedExecutionInfo, offset) = decodeBytes(encoded, offset); + (strct.newSourceDeliveryProvider, offset) = encoded.asBytes32Unchecked(offset); + (strct.newSenderAddress, offset) = encoded.asBytes32Unchecked(offset); - encoded.checkLength(offset); + encoded.checkLength(offset); } function vaaKeyArrayToMessageKeyArray( - VaaKey[] memory vaaKeys + VaaKey[] memory vaaKeys ) pure returns (MessageKey[] memory msgKeys) { - msgKeys = new MessageKey[](vaaKeys.length); - uint256 len = vaaKeys.length; - for (uint256 i = 0; i < len; ) { - msgKeys[i] = MessageKey(VAA_KEY_TYPE, encodeVaaKey(vaaKeys[i])); - unchecked { - ++i; - } - } + msgKeys = new MessageKey[](vaaKeys.length); + uint256 len = vaaKeys.length; + for (uint256 i = 0; i < len; ) { + msgKeys[i] = MessageKey(VAA_KEY_TYPE, encodeVaaKey(vaaKeys[i])); + unchecked { ++i; } + } } function encodeMessageKey( - MessageKey memory msgKey + MessageKey memory msgKey ) pure returns (bytes memory encoded) { - if (msgKey.keyType == VAA_KEY_TYPE) { - // known length - encoded = abi.encodePacked(msgKey.keyType, msgKey.encodedKey); - } else { - encoded = abi.encodePacked( - msgKey.keyType, - encodeBytes(msgKey.encodedKey) - ); - } + encoded = (msgKey.keyType == VAA_KEY_TYPE) + ? abi.encodePacked(msgKey.keyType, msgKey.encodedKey) // known length + : abi.encodePacked(msgKey.keyType, encodeBytes(msgKey.encodedKey)); } uint256 constant VAA_KEY_TYPE_LENGTH = 2 + 32 + 8; function decodeMessageKey( - bytes memory encoded, - uint256 startOffset + bytes memory encoded, + uint256 startOffset ) pure returns (MessageKey memory msgKey, uint256 offset) { - (msgKey.keyType, offset) = encoded.asUint8Unchecked(startOffset); - if (msgKey.keyType == VAA_KEY_TYPE) { - (msgKey.encodedKey, offset) = encoded.sliceUnchecked( - offset, - VAA_KEY_TYPE_LENGTH - ); - } else { - (msgKey.encodedKey, offset) = decodeBytes(encoded, offset); - } + (msgKey.keyType, offset) = encoded.asUint8Unchecked(startOffset); + (msgKey.encodedKey, offset) = msgKey.keyType == VAA_KEY_TYPE + ? encoded.sliceUnchecked(offset, VAA_KEY_TYPE_LENGTH) + : decodeBytes(encoded, offset); } function encodeVaaKey( - VaaKey memory vaaKey + VaaKey memory vaaKey ) pure returns (bytes memory encoded) { - encoded = abi.encodePacked( - vaaKey.chainId, - vaaKey.emitterAddress, - vaaKey.sequence - ); + encoded = abi.encodePacked(vaaKey.chainId, vaaKey.emitterAddress, vaaKey.sequence); } function decodeVaaKey( - bytes memory encoded, - uint256 startOffset + bytes memory encoded, + uint256 startOffset ) pure returns (VaaKey memory vaaKey, uint256 offset) { - offset = startOffset; - (vaaKey.chainId, offset) = encoded.asUint16Unchecked(offset); - (vaaKey.emitterAddress, offset) = encoded.asBytes32Unchecked(offset); - (vaaKey.sequence, offset) = encoded.asUint64Unchecked(offset); + offset = startOffset; + (vaaKey.chainId, offset) = encoded.asUint16Unchecked(offset); + (vaaKey.emitterAddress, offset) = encoded.asBytes32Unchecked(offset); + (vaaKey.sequence, offset) = encoded.asUint64Unchecked(offset); } function encodeMessageKeyArray( - MessageKey[] memory msgKeys + MessageKey[] memory msgKeys ) pure returns (bytes memory encoded) { - uint256 len = msgKeys.length; - if (len > type(uint8).max) { - revert TooManyMessageKeys(len); - } - encoded = abi.encodePacked(uint8(msgKeys.length)); - for (uint256 i = 0; i < len; ) { - encoded = abi.encodePacked(encoded, encodeMessageKey(msgKeys[i])); - unchecked { - ++i; - } - } + uint256 len = msgKeys.length; + if (len > type(uint8).max) + revert TooManyMessageKeys(len); + + encoded = abi.encodePacked(uint8(msgKeys.length)); + for (uint256 i = 0; i < len; ) { + encoded = abi.encodePacked(encoded, encodeMessageKey(msgKeys[i])); + unchecked { ++i; } + } } function decodeMessageKeyArray( - bytes memory encoded, - uint256 startOffset + bytes memory encoded, + uint256 startOffset ) pure returns (MessageKey[] memory msgKeys, uint256 offset) { - uint8 msgKeysLength; - (msgKeysLength, offset) = encoded.asUint8Unchecked(startOffset); - msgKeys = new MessageKey[](msgKeysLength); - for (uint256 i = 0; i < msgKeysLength; ) { - (msgKeys[i], offset) = decodeMessageKey(encoded, offset); - unchecked { - ++i; - } - } + uint8 msgKeysLength; + (msgKeysLength, offset) = encoded.asUint8Unchecked(startOffset); + msgKeys = new MessageKey[](msgKeysLength); + for (uint256 i = 0; i < msgKeysLength; ) { + (msgKeys[i], offset) = decodeMessageKey(encoded, offset); + unchecked { ++i; } + } } function decodeCCTPKey( - bytes memory encoded, - uint256 startOffset + bytes memory encoded, + uint256 startOffset ) pure returns (CCTPMessageLib.CCTPKey memory cctpKey, uint256 offset) { - offset = startOffset; - (cctpKey.domain, offset) = encoded.asUint32Unchecked(offset); - (cctpKey.nonce, offset) = encoded.asUint64Unchecked(offset); + offset = startOffset; + (cctpKey.domain, offset) = encoded.asUint32Unchecked(offset); + (cctpKey.nonce, offset) = encoded.asUint64Unchecked(offset); } // ------------------------------------------ -------------------------------------------- function encodeBytes(bytes memory payload) pure returns (bytes memory encoded) { - //casting payload.length to uint32 is safe because you'll be hard-pressed to allocate 4 GB of - // EVM memory in a single transaction - encoded = abi.encodePacked(uint32(payload.length), payload); + //casting payload.length to uint32 is safe because you'll be hard-pressed to allocate 4 GB of + // EVM memory in a single transaction + encoded = abi.encodePacked(uint32(payload.length), payload); } function decodeBytes( - bytes memory encoded, - uint256 startOffset + bytes memory encoded, + uint256 startOffset ) pure returns (bytes memory payload, uint256 offset) { - uint32 payloadLength; - (payloadLength, offset) = encoded.asUint32Unchecked(startOffset); - (payload, offset) = encoded.sliceUnchecked(offset, payloadLength); + uint32 payloadLength; + (payloadLength, offset) = encoded.asUint32Unchecked(startOffset); + (payload, offset) = encoded.sliceUnchecked(offset, payloadLength); } function checkUint8( - bytes memory encoded, - uint256 startOffset, - uint8 expectedPayloadId + bytes memory encoded, + uint256 startOffset, + uint8 expectedPayloadId ) pure returns (uint256 offset) { - uint8 parsedPayloadId; - (parsedPayloadId, offset) = encoded.asUint8Unchecked(startOffset); - if (parsedPayloadId != expectedPayloadId) { - revert InvalidPayloadId(parsedPayloadId, expectedPayloadId); - } + uint8 parsedPayloadId; + (parsedPayloadId, offset) = encoded.asUint8Unchecked(startOffset); + if (parsedPayloadId != expectedPayloadId) + revert InvalidPayloadId(parsedPayloadId, expectedPayloadId); } function encode( - DeliveryOverride memory strct + DeliveryOverride memory strct ) pure returns (bytes memory encoded) { - encoded = abi.encodePacked( - VERSION_DELIVERY_OVERRIDE, - strct.newReceiverValue, - encodeBytes(strct.newExecutionInfo), - strct.redeliveryHash - ); + encoded = abi.encodePacked( + VERSION_DELIVERY_OVERRIDE, + strct.newReceiverValue, + encodeBytes(strct.newExecutionInfo), + strct.redeliveryHash + ); } function decodeDeliveryOverride( - bytes memory encoded + bytes memory encoded ) pure returns (DeliveryOverride memory strct) { - uint256 offset = checkUint8(encoded, 0, VERSION_DELIVERY_OVERRIDE); - - uint256 receiverValue; - - (receiverValue, offset) = encoded.asUint256Unchecked(offset); - (strct.newExecutionInfo, offset) = decodeBytes(encoded, offset); - (strct.redeliveryHash, offset) = encoded.asBytes32Unchecked(offset); + uint256 offset = checkUint8(encoded, 0, VERSION_DELIVERY_OVERRIDE); - strct.newReceiverValue = receiverValue; + (strct.newReceiverValue, offset) = encoded.asUint256Unchecked(offset); + (strct.newExecutionInfo, offset) = decodeBytes(encoded, offset); + (strct.redeliveryHash, offset) = encoded.asBytes32Unchecked(offset); - encoded.checkLength(offset); + encoded.checkLength(offset); } diff --git a/src/testing/WormholeRelayer/ExecutionParameters.sol b/src/testing/WormholeRelayer/ExecutionParameters.sol index f61c393..f9b71e8 100644 --- a/src/testing/WormholeRelayer/ExecutionParameters.sol +++ b/src/testing/WormholeRelayer/ExecutionParameters.sol @@ -17,53 +17,65 @@ using BytesParsing for bytes; enum ExecutionParamsVersion {EVM_V1} struct EvmExecutionParamsV1 { - uint256 gasLimit; + uint256 gasLimit; } enum ExecutionInfoVersion {EVM_V1} struct EvmExecutionInfoV1 { - uint256 gasLimit; - uint256 targetChainRefundPerGasUnused; + uint256 gasLimit; + uint256 targetChainRefundPerGasUnused; } -function decodeExecutionParamsVersion(bytes memory data) pure returns (ExecutionParamsVersion version) { - (version) = abi.decode(data, (ExecutionParamsVersion)); +function decodeExecutionParamsVersion( + bytes memory data +) pure returns (ExecutionParamsVersion version) { + (version) = abi.decode(data, (ExecutionParamsVersion)); } -function decodeExecutionInfoVersion(bytes memory data) pure returns (ExecutionInfoVersion version) { - (version) = abi.decode(data, (ExecutionInfoVersion)); +function decodeExecutionInfoVersion( + bytes memory data +) pure returns (ExecutionInfoVersion version) { + (version) = abi.decode(data, (ExecutionInfoVersion)); } -function encodeEvmExecutionParamsV1(EvmExecutionParamsV1 memory executionParams) pure returns (bytes memory) { - return abi.encode(uint8(ExecutionParamsVersion.EVM_V1), executionParams.gasLimit); +function encodeEvmExecutionParamsV1( + EvmExecutionParamsV1 memory executionParams +) pure returns (bytes memory) { + return abi.encode(uint8(ExecutionParamsVersion.EVM_V1), executionParams.gasLimit); } -function decodeEvmExecutionParamsV1(bytes memory data) pure returns (EvmExecutionParamsV1 memory executionParams) { - uint8 version; - (version, executionParams.gasLimit) = abi.decode(data, (uint8, uint256)); +function decodeEvmExecutionParamsV1( + bytes memory data +) pure returns (EvmExecutionParamsV1 memory executionParams) { + uint8 version; + (version, executionParams.gasLimit) = abi.decode(data, (uint8, uint256)); - if (version != uint8(ExecutionParamsVersion.EVM_V1)) { - revert UnexpectedExecutionParamsVersion(version, uint8(ExecutionParamsVersion.EVM_V1)); - } + if (version != uint8(ExecutionParamsVersion.EVM_V1)) { + revert UnexpectedExecutionParamsVersion(version, uint8(ExecutionParamsVersion.EVM_V1)); + } } -function encodeEvmExecutionInfoV1(EvmExecutionInfoV1 memory executionInfo) pure returns (bytes memory) { - return abi.encode( - uint8(ExecutionInfoVersion.EVM_V1), executionInfo.gasLimit, executionInfo.targetChainRefundPerGasUnused - ); +function encodeEvmExecutionInfoV1( + EvmExecutionInfoV1 memory executionInfo +) pure returns (bytes memory) { + return abi.encode( + uint8(ExecutionInfoVersion.EVM_V1), executionInfo.gasLimit, executionInfo.targetChainRefundPerGasUnused + ); } -function decodeEvmExecutionInfoV1(bytes memory data) pure returns (EvmExecutionInfoV1 memory executionInfo) { - uint8 version; - (version, executionInfo.gasLimit, executionInfo.targetChainRefundPerGasUnused) = - abi.decode(data, (uint8, uint256, uint256)); +function decodeEvmExecutionInfoV1( + bytes memory data +) pure returns (EvmExecutionInfoV1 memory executionInfo) { + uint8 version; + (version, executionInfo.gasLimit, executionInfo.targetChainRefundPerGasUnused) = + abi.decode(data, (uint8, uint256, uint256)); - if (version != uint8(ExecutionInfoVersion.EVM_V1)) { - revert UnexpectedExecutionInfoVersion(version, uint8(ExecutionInfoVersion.EVM_V1)); - } + if (version != uint8(ExecutionInfoVersion.EVM_V1)) { + revert UnexpectedExecutionInfoVersion(version, uint8(ExecutionInfoVersion.EVM_V1)); + } } function getEmptyEvmExecutionParamsV1() pure returns (EvmExecutionParamsV1 memory executionParams) { - executionParams.gasLimit = 0; + executionParams.gasLimit = 0; } diff --git a/src/testing/WormholeRelayer/MockOffchainRelayer.sol b/src/testing/WormholeRelayer/MockOffchainRelayer.sol index 01b3236..6fd9ce7 100644 --- a/src/testing/WormholeRelayer/MockOffchainRelayer.sol +++ b/src/testing/WormholeRelayer/MockOffchainRelayer.sol @@ -134,7 +134,7 @@ contract MockOffchainRelayer { bytes[] memory encodedSignedVaas = new bytes[](pms.length); for (uint256 i = 0; i < encodedSignedVaas.length; ++i) (vaas[i], encodedSignedVaas[i]) = emitterWormhole.sign(pms[i]); - + CCTPMessageLib.CCTPMessage[] memory cctpSignedMsgs = new CCTPMessageLib.CCTPMessage[](0); IMessageTransmitter emitterMessageTransmitter = getForkMessageTransmitter(); if (address(emitterMessageTransmitter) != address(0)) { @@ -228,7 +228,7 @@ contract MockOffchainRelayer { } } else if (instruction.messageKeys[i].keyType == 2) { // CCTP Key - (CCTPMessageLib.CCTPKey memory key,) = decodeCCTPKey(instruction.messageKeys[i].encodedKey, 0); + (CCTPMessageLib.CCTPKey memory key,) = decodeCCTPKey(instruction.messageKeys[i].encodedKey, 0); for (uint8 j = 0; j < cctpMessages.length; j++) { if (cctpKeyMatchesCCTPMessage(key, cctpMessages[j])) { encodedSignedVaasToBeDelivered[i] = abi.encode(cctpMessages[j].message, cctpMessages[j].signature); diff --git a/src/testing/WormholeRelayerTest.sol b/src/testing/WormholeRelayerTest.sol index 163522d..4c54888 100644 --- a/src/testing/WormholeRelayerTest.sol +++ b/src/testing/WormholeRelayerTest.sol @@ -19,630 +19,630 @@ import "./WormholeRelayer/ExecutionParameters.sol"; import "./WormholeRelayer/MockOffchainRelayer.sol"; struct ChainInfo { - uint16 chainId; - string name; - string url; - IWormholeRelayer relayer; - ITokenBridge tokenBridge; - IWormhole wormhole; - IMessageTransmitter circleMessageTransmitter; - ITokenMessenger circleTokenMessenger; - IUSDC USDC; + uint16 chainId; + string name; + string url; + IWormholeRelayer relayer; + ITokenBridge tokenBridge; + IWormhole wormhole; + IMessageTransmitter circleMessageTransmitter; + ITokenMessenger circleTokenMessenger; + IUSDC USDC; } struct ActiveFork { - uint16 chainId; - string name; - string url; - uint256 fork; - IWormholeRelayer relayer; - ITokenBridge tokenBridge; - IWormhole wormhole; - // USDC parameters - only non-empty for Ethereum, Avalanche, Optimism, Arbitrum mainnets/testnets - IUSDC USDC; - ITokenMessenger circleTokenMessenger; - IMessageTransmitter circleMessageTransmitter; + uint16 chainId; + string name; + string url; + uint256 fork; + IWormholeRelayer relayer; + ITokenBridge tokenBridge; + IWormhole wormhole; + // USDC parameters - only non-empty for Ethereum, Avalanche, Optimism, Arbitrum mainnets/testnets + IUSDC USDC; + ITokenMessenger circleTokenMessenger; + IMessageTransmitter circleMessageTransmitter; } abstract contract WormholeRelayerTest is Test { - using WormholeOverride for IWormhole; - using CctpOverride for IMessageTransmitter; - using UsdcDealer for IUSDC; - - /** - * @dev required override to initialize active forks before each test - */ - function setUpFork(ActiveFork memory fork) public virtual; - - /** - * @dev optional override that runs after all forks have been set up - */ - function setUpGeneral() public virtual {} - - // conveneince information to set up tests against testnet/mainnet forks - mapping(uint16 => ChainInfo) public chainInfosTestnet; - mapping(uint16 => ChainInfo) public chainInfosMainnet; - - // active forks for the test - mapping(uint16 => ActiveFork) public activeForks; - uint16[] public activeForksList; - - MockOffchainRelayer public mockOffchainRelayer; - - constructor() { - initChainInfo(); - - // set default active forks. These can be overridden in your test - ChainInfo[] memory forks = new ChainInfo[](2); - forks[0] = chainInfosTestnet[6]; // fuji avax - forks[1] = chainInfosTestnet[14]; // alfajores celo - setActiveForks(forks); + using WormholeOverride for IWormhole; + using CctpOverride for IMessageTransmitter; + using UsdcDealer for IUSDC; + + /** + * @dev required override to initialize active forks before each test + */ + function setUpFork(ActiveFork memory fork) public virtual; + + /** + * @dev optional override that runs after all forks have been set up + */ + function setUpGeneral() public virtual {} + + // conveneince information to set up tests against testnet/mainnet forks + mapping(uint16 => ChainInfo) public chainInfosTestnet; + mapping(uint16 => ChainInfo) public chainInfosMainnet; + + // active forks for the test + mapping(uint16 => ActiveFork) public activeForks; + uint16[] public activeForksList; + + MockOffchainRelayer public mockOffchainRelayer; + + constructor() { + initChainInfo(); + + // set default active forks. These can be overridden in your test + ChainInfo[] memory forks = new ChainInfo[](2); + forks[0] = chainInfosTestnet[6]; // fuji avax + forks[1] = chainInfosTestnet[14]; // alfajores celo + setActiveForks(forks); + } + + function _setActiveForks(ChainInfo[] memory chainInfos) internal virtual { + if (chainInfos.length < 2) { + console.log("setActiveForks: 2 or more forks must be specified"); + revert("setActiveForks: 2 or more forks must be specified"); } - - function _setActiveForks(ChainInfo[] memory chainInfos) internal virtual { - if (chainInfos.length < 2) { - console.log("setActiveForks: 2 or more forks must be specified"); - revert("setActiveForks: 2 or more forks must be specified"); - } - activeForksList = new uint16[](chainInfos.length); - for (uint256 i = 0; i < chainInfos.length; i++) { - activeForksList[i] = chainInfos[i].chainId; - activeForks[chainInfos[i].chainId] = ActiveFork({ - chainId: chainInfos[i].chainId, - url: chainInfos[i].url, - name: chainInfos[i].name, - relayer: chainInfos[i].relayer, - tokenBridge: chainInfos[i].tokenBridge, - wormhole: chainInfos[i].wormhole, - // patch these in setUp() once we have the fork - fork: 0, - circleMessageTransmitter: chainInfos[i].circleMessageTransmitter, - circleTokenMessenger: chainInfos[i].circleTokenMessenger, - USDC: chainInfos[i].USDC - }); - } + activeForksList = new uint16[](chainInfos.length); + for (uint256 i = 0; i < chainInfos.length; i++) { + activeForksList[i] = chainInfos[i].chainId; + activeForks[chainInfos[i].chainId] = ActiveFork({ + chainId: chainInfos[i].chainId, + url: chainInfos[i].url, + name: chainInfos[i].name, + relayer: chainInfos[i].relayer, + tokenBridge: chainInfos[i].tokenBridge, + wormhole: chainInfos[i].wormhole, + // patch these in setUp() once we have the fork + fork: 0, + circleMessageTransmitter: chainInfos[i].circleMessageTransmitter, + circleTokenMessenger: chainInfos[i].circleTokenMessenger, + USDC: chainInfos[i].USDC + }); } - - function setActiveForks(ChainInfo[] memory chainInfos) public virtual { - _setActiveForks(chainInfos); + } + + function setActiveForks(ChainInfo[] memory chainInfos) public virtual { + _setActiveForks(chainInfos); + } + + function setUp() public virtual { + _setUp(); + } + + function _setUp() internal { + // create and setup each active fork + for (uint256 i = 0; i < activeForksList.length; ++i) { + uint16 chainId = activeForksList[i]; + ActiveFork storage fork = activeForks[chainId]; + fork.fork = vm.createSelectFork(fork.url); + fork.wormhole.setUpOverride(); + if (address(fork.circleMessageTransmitter) != address(0)) + fork.circleMessageTransmitter.setUpOverride(); } - function setUp() public virtual { - _setUp(); + // run setUp virtual functions for each fork + for (uint256 i = 0; i < activeForksList.length; ++i) { + ActiveFork memory fork = activeForks[activeForksList[i]]; + vm.selectFork(fork.fork); + setUpFork(fork); } - function _setUp() internal { - // create and setup each active fork - for (uint256 i = 0; i < activeForksList.length; ++i) { - uint16 chainId = activeForksList[i]; - ActiveFork storage fork = activeForks[chainId]; - fork.fork = vm.createSelectFork(fork.url); - fork.wormhole.setUpOverride(); - if (address(fork.circleMessageTransmitter) != address(0)) - fork.circleMessageTransmitter.setUpOverride(); - } - - // run setUp virtual functions for each fork - for (uint256 i = 0; i < activeForksList.length; ++i) { - ActiveFork memory fork = activeForks[activeForksList[i]]; - vm.selectFork(fork.fork); - setUpFork(fork); - } - - ActiveFork memory firstFork = activeForks[activeForksList[0]]; - vm.selectFork(firstFork.fork); - mockOffchainRelayer = new MockOffchainRelayer(); - // register all active forks with the 'offchain' relayer - for (uint256 i = 0; i < activeForksList.length; ++i) { - ActiveFork storage fork = activeForks[activeForksList[i]]; - mockOffchainRelayer.registerChain( - fork.chainId, - fork.wormhole, - fork.circleMessageTransmitter, - fork.relayer, - fork.fork - ); - } - - // Allow the offchain relayer to work on all forks - vm.makePersistent(address(mockOffchainRelayer)); - - vm.selectFork(firstFork.fork); - setUpGeneral(); - - vm.selectFork(firstFork.fork); + ActiveFork memory firstFork = activeForks[activeForksList[0]]; + vm.selectFork(firstFork.fork); + mockOffchainRelayer = new MockOffchainRelayer(); + // register all active forks with the 'offchain' relayer + for (uint256 i = 0; i < activeForksList.length; ++i) { + ActiveFork storage fork = activeForks[activeForksList[i]]; + mockOffchainRelayer.registerChain( + fork.chainId, + fork.wormhole, + fork.circleMessageTransmitter, + fork.relayer, + fork.fork + ); } - function performDelivery() public { - performDelivery(vm.getRecordedLogs(), false); + // Allow the offchain relayer to work on all forks + vm.makePersistent(address(mockOffchainRelayer)); + + vm.selectFork(firstFork.fork); + setUpGeneral(); + + vm.selectFork(firstFork.fork); + } + + function performDelivery() public { + performDelivery(vm.getRecordedLogs(), false); + } + + function performDelivery(bool debugLogging) public { + performDelivery(vm.getRecordedLogs(), debugLogging); + } + + function performDelivery(Vm.Log[] memory logs) public { + performDelivery(logs, false); + } + + function performDelivery(Vm.Log[] memory logs, bool debugLogging) public { + require(logs.length > 0, "no events recorded"); + mockOffchainRelayer.relay(logs, debugLogging); + } + + function createAndAttestToken( + uint16 homeChain + ) public returns (ERC20Mock token) { + uint256 originalFork = vm.activeFork(); + ActiveFork memory home = activeForks[homeChain]; + vm.selectFork(home.fork); + + token = new ERC20Mock("Test Token", "TST"); + token.mint(address(this), 5000e18); + + vm.recordLogs(); + home.tokenBridge.attestToken(address(token), 0); + (, bytes memory attestation) = home.wormhole.sign( + home.wormhole.fetchPublishedMessages(vm.getRecordedLogs())[0] + ); + + for (uint256 i = 0; i < activeForksList.length; ++i) { + if (activeForksList[i] == home.chainId) { + continue; + } + ActiveFork memory fork = activeForks[activeForksList[i]]; + vm.selectFork(fork.fork); + fork.tokenBridge.createWrapped(attestation); } - function performDelivery(bool debugLogging) public { - performDelivery(vm.getRecordedLogs(), debugLogging); - } - - function performDelivery(Vm.Log[] memory logs) public { - performDelivery(logs, false); - } + vm.selectFork(originalFork); + } - function performDelivery(Vm.Log[] memory logs, bool debugLogging) public { - require(logs.length > 0, "no events recorded"); - mockOffchainRelayer.relay(logs, debugLogging); - } + function mintUSDC(uint16 chain, address addr, uint256 amount) public { + uint256 originalFork = vm.activeFork(); + ActiveFork memory current = activeForks[chain]; + vm.selectFork(current.fork); - function createAndAttestToken( - uint16 homeChain - ) public returns (ERC20Mock token) { - uint256 originalFork = vm.activeFork(); - ActiveFork memory home = activeForks[homeChain]; - vm.selectFork(home.fork); + current.USDC.deal(addr, amount); - token = new ERC20Mock("Test Token", "TST"); - token.mint(address(this), 5000e18); + vm.selectFork(originalFork); + } - vm.recordLogs(); - home.tokenBridge.attestToken(address(token), 0); - (, bytes memory attestation) = home.wormhole.sign( - home.wormhole.fetchPublishedMessages(vm.getRecordedLogs())[0] + function logFork() public view { + uint256 fork = vm.activeFork(); + for (uint256 i = 0; i < activeForksList.length; ++i) { + if (fork == activeForks[activeForksList[i]].fork) { + console.log( + "%s fork active", + activeForks[activeForksList[i]].name ); - - for (uint256 i = 0; i < activeForksList.length; ++i) { - if (activeForksList[i] == home.chainId) { - continue; - } - ActiveFork memory fork = activeForks[activeForksList[i]]; - vm.selectFork(fork.fork); - fork.tokenBridge.createWrapped(attestation); - } - - vm.selectFork(originalFork); - } - - function mintUSDC(uint16 chain, address addr, uint256 amount) public { - uint256 originalFork = vm.activeFork(); - ActiveFork memory current = activeForks[chain]; - vm.selectFork(current.fork); - - current.USDC.deal(addr, amount); - - vm.selectFork(originalFork); - } - - function logFork() public view { - uint256 fork = vm.activeFork(); - for (uint256 i = 0; i < activeForksList.length; ++i) { - if (fork == activeForks[activeForksList[i]].fork) { - console.log( - "%s fork active", - activeForks[activeForksList[i]].name - ); - return; - } - } - } - - function initChainInfo() private { - chainInfosTestnet[6] = ChainInfo({ - chainId: 6, - name: "fuji - avalanche", - url: vm.envOr( - "AVALANCHE_FUJI_RPC_URL", - string("https://api.avax-test.network/ext/bc/C/rpc") - ), - relayer: IWormholeRelayer( - 0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB - ), - tokenBridge: ITokenBridge( - 0x61E44E506Ca5659E6c0bba9b678586fA2d729756 - ), - wormhole: IWormhole(0x7bbcE28e64B3F8b84d876Ab298393c38ad7aac4C), - circleMessageTransmitter: IMessageTransmitter( - 0xa9fB1b3009DCb79E2fe346c16a604B8Fa8aE0a79 - ), - circleTokenMessenger: ITokenMessenger( - 0xeb08f243E5d3FCFF26A9E38Ae5520A669f4019d0 - ), - USDC: IUSDC(0x5425890298aed601595a70AB815c96711a31Bc65) - }); - chainInfosTestnet[14] = ChainInfo({ - chainId: 14, - name: "alfajores - celo", - url: vm.envOr( - "CELO_TESTNET_RPC_URL", - string("https://alfajores-forno.celo-testnet.org") - ), - relayer: IWormholeRelayer( - 0x306B68267Deb7c5DfCDa3619E22E9Ca39C374f84 - ), - tokenBridge: ITokenBridge( - 0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153 - ), - wormhole: IWormhole(0x88505117CA88e7dd2eC6EA1E13f0948db2D50D56), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosTestnet[4] = ChainInfo({ - chainId: 4, - name: "bsc testnet", - url: vm.envOr( - "BSC_TESTNET_RPC_URL", - string("https://bsc-testnet-rpc.publicnode.com/") - ), - relayer: IWormholeRelayer( - 0x80aC94316391752A193C1c47E27D382b507c93F3 - ), - tokenBridge: ITokenBridge( - 0x9dcF9D205C9De35334D646BeE44b2D2859712A09 - ), - wormhole: IWormhole(0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosTestnet[16] = ChainInfo({ - chainId: 16, - name: "moonbase alpha - moonbeam", - url: vm.envOr( - "MOONBASE_ALPHA_RPC_URL", - string("https://rpc.testnet.moonbeam.network") - ), - relayer: IWormholeRelayer( - 0x0591C25ebd0580E0d4F27A82Fc2e24E7489CB5e0 - ), - tokenBridge: ITokenBridge( - 0xbc976D4b9D57E57c3cA52e1Fd136C45FF7955A96 - ), - wormhole: IWormhole(0xa5B7D85a8f27dd7907dc8FdC21FA5657D5E2F901), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosMainnet[2] = ChainInfo({ - chainId: 2, - name: "ethereum", - url: vm.envOr( - "ETHEREUM_RPC_URL", - string("https://rpc.ankr.com/eth") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0x3ee18B2214AFF97000D974cf647E7C347E8fa585 - ), - wormhole: IWormhole(0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B), - circleMessageTransmitter: IMessageTransmitter( - 0x0a992d191DEeC32aFe36203Ad87D7d289a738F81 - ), - circleTokenMessenger: ITokenMessenger( - 0xBd3fa81B58Ba92a82136038B25aDec7066af3155 - ), - USDC: IUSDC(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) - }); - chainInfosMainnet[4] = ChainInfo({ - chainId: 4, - name: "bsc", - url: vm.envOr( - "BSC_RPC_URL", - string("https://bsc-dataseed2.defibit.io") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7 - ), - wormhole: IWormhole(0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosMainnet[6] = ChainInfo({ - chainId: 6, - name: "avalanche", - url: vm.envOr( - "AVALANCHE_RPC_URL", - string("https://rpc.ankr.com/avalanche") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0x0e082F06FF657D94310cB8cE8B0D9a04541d8052 - ), - wormhole: IWormhole(0x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c), - circleMessageTransmitter: IMessageTransmitter( - 0x8186359aF5F57FbB40c6b14A588d2A59C0C29880 - ), - circleTokenMessenger: ITokenMessenger( - 0x6B25532e1060CE10cc3B0A99e5683b91BFDe6982 - ), - USDC: IUSDC(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E) - }); - chainInfosMainnet[10] = ChainInfo({ - chainId: 10, - name: "fantom", - url: vm.envOr( - "FANTOM_RPC_URL", - string("https://rpc.ankr.com/fantom") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2 - ), - wormhole: IWormhole(0x126783A6Cb203a3E35344528B26ca3a0489a1485), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosMainnet[13] = ChainInfo({ - chainId: 13, - name: "klaytn", - url: vm.envOr( - "KLAYTN_RPC_URL", - string("https://klaytn-mainnet-rpc.allthatnode.com:8551") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0x5b08ac39EAED75c0439FC750d9FE7E1F9dD0193F - ), - wormhole: IWormhole(0x0C21603c4f3a6387e241c0091A7EA39E43E90bb7), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosMainnet[14] = ChainInfo({ - chainId: 14, - name: "celo", - url: vm.envOr("CELO_RPC_URL", string("https://forno.celo.org")), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0x796Dff6D74F3E27060B71255Fe517BFb23C93eed - ), - wormhole: IWormhole(0xa321448d90d4e5b0A732867c18eA198e75CAC48E), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosMainnet[12] = ChainInfo({ - chainId: 12, - name: "acala", - url: vm.envOr( - "ACALA_RPC_URL", - string("https://eth-rpc-acala.aca-api.network") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0xae9d7fe007b3327AA64A32824Aaac52C42a6E624 - ), - wormhole: IWormhole(0xa321448d90d4e5b0A732867c18eA198e75CAC48E), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosMainnet[11] = ChainInfo({ - chainId: 11, - name: "karura", - url: vm.envOr( - "KARURA_RPC_URL", - string("https://eth-rpc-karura.aca-api.network") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0xae9d7fe007b3327AA64A32824Aaac52C42a6E624 - ), - wormhole: IWormhole(0xa321448d90d4e5b0A732867c18eA198e75CAC48E), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosMainnet[16] = ChainInfo({ - chainId: 16, - name: "moombeam", - url: vm.envOr( - "MOOMBEAM_RPC_URL", - string("https://rpc.ankr.com/moonbeam") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0xB1731c586ca89a23809861c6103F0b96B3F57D92 - ), - wormhole: IWormhole(0xC8e2b0cD52Cf01b0Ce87d389Daa3d414d4cE29f3), - circleMessageTransmitter: IMessageTransmitter(address(0)), - circleTokenMessenger: ITokenMessenger(address(0)), - USDC: IUSDC(address(0)) - }); - chainInfosMainnet[23] = ChainInfo({ - chainId: 23, - name: "arbitrum", - url: vm.envOr( - "ARBITRUM_RPC_URL", - string("https://arb1.arbitrum.io/rpc") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0x0b2402144Bb366A632D14B83F244D2e0e21bD39c - ), - wormhole: IWormhole(0xa5f208e072434bC67592E4C49C1B991BA79BCA46), - circleMessageTransmitter: IMessageTransmitter( - 0xC30362313FBBA5cf9163F0bb16a0e01f01A896ca - ), - circleTokenMessenger: ITokenMessenger( - 0x19330d10D9Cc8751218eaf51E8885D058642E08A - ), - USDC: IUSDC(0xaf88d065e77c8cC2239327C5EDb3A432268e5831) - }); - chainInfosMainnet[24] = ChainInfo({ - chainId: 24, - name: "optimism", - url: vm.envOr( - "OPTIMISM_RPC_URL", - string("https://rpc.ankr.com/optimism") - ), - relayer: IWormholeRelayer( - 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 - ), - tokenBridge: ITokenBridge( - 0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b - ), - wormhole: IWormhole(0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722), - circleMessageTransmitter: IMessageTransmitter( - 0x4D41f22c5a0e5c74090899E5a8Fb597a8842b3e8 - ), - circleTokenMessenger: ITokenMessenger( - 0x2B4069517957735bE00ceE0fadAE88a26365528f - ), - USDC: IUSDC(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) - }); - chainInfosMainnet[30] = ChainInfo({ - chainId: 30, - name: "base", - url: vm.envOr("BASE_RPC_URL", string("https://mainnet.base.org")), - relayer: IWormholeRelayer( - 0x706F82e9bb5b0813501714Ab5974216704980e31 - ), - tokenBridge: ITokenBridge( - 0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627 - ), - wormhole: IWormhole(0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6), - circleMessageTransmitter: IMessageTransmitter( - address(0xAD09780d193884d503182aD4588450C416D6F9D4) - ), - circleTokenMessenger: ITokenMessenger( - address(0x1682Ae6375C4E4A97e4B583BC394c861A46D8962) - ), - USDC: IUSDC(address(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913)) - }); + return; + } } - - receive() external payable {} + } + + function initChainInfo() private { + chainInfosTestnet[6] = ChainInfo({ + chainId: 6, + name: "fuji - avalanche", + url: vm.envOr( + "AVALANCHE_FUJI_RPC_URL", + string("https://api.avax-test.network/ext/bc/C/rpc") + ), + relayer: IWormholeRelayer( + 0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB + ), + tokenBridge: ITokenBridge( + 0x61E44E506Ca5659E6c0bba9b678586fA2d729756 + ), + wormhole: IWormhole(0x7bbcE28e64B3F8b84d876Ab298393c38ad7aac4C), + circleMessageTransmitter: IMessageTransmitter( + 0xa9fB1b3009DCb79E2fe346c16a604B8Fa8aE0a79 + ), + circleTokenMessenger: ITokenMessenger( + 0xeb08f243E5d3FCFF26A9E38Ae5520A669f4019d0 + ), + USDC: IUSDC(0x5425890298aed601595a70AB815c96711a31Bc65) + }); + chainInfosTestnet[14] = ChainInfo({ + chainId: 14, + name: "alfajores - celo", + url: vm.envOr( + "CELO_TESTNET_RPC_URL", + string("https://alfajores-forno.celo-testnet.org") + ), + relayer: IWormholeRelayer( + 0x306B68267Deb7c5DfCDa3619E22E9Ca39C374f84 + ), + tokenBridge: ITokenBridge( + 0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153 + ), + wormhole: IWormhole(0x88505117CA88e7dd2eC6EA1E13f0948db2D50D56), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosTestnet[4] = ChainInfo({ + chainId: 4, + name: "bsc testnet", + url: vm.envOr( + "BSC_TESTNET_RPC_URL", + string("https://bsc-testnet-rpc.publicnode.com/") + ), + relayer: IWormholeRelayer( + 0x80aC94316391752A193C1c47E27D382b507c93F3 + ), + tokenBridge: ITokenBridge( + 0x9dcF9D205C9De35334D646BeE44b2D2859712A09 + ), + wormhole: IWormhole(0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosTestnet[16] = ChainInfo({ + chainId: 16, + name: "moonbase alpha - moonbeam", + url: vm.envOr( + "MOONBASE_ALPHA_RPC_URL", + string("https://rpc.testnet.moonbeam.network") + ), + relayer: IWormholeRelayer( + 0x0591C25ebd0580E0d4F27A82Fc2e24E7489CB5e0 + ), + tokenBridge: ITokenBridge( + 0xbc976D4b9D57E57c3cA52e1Fd136C45FF7955A96 + ), + wormhole: IWormhole(0xa5B7D85a8f27dd7907dc8FdC21FA5657D5E2F901), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosMainnet[2] = ChainInfo({ + chainId: 2, + name: "ethereum", + url: vm.envOr( + "ETHEREUM_RPC_URL", + string("https://rpc.ankr.com/eth") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0x3ee18B2214AFF97000D974cf647E7C347E8fa585 + ), + wormhole: IWormhole(0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B), + circleMessageTransmitter: IMessageTransmitter( + 0x0a992d191DEeC32aFe36203Ad87D7d289a738F81 + ), + circleTokenMessenger: ITokenMessenger( + 0xBd3fa81B58Ba92a82136038B25aDec7066af3155 + ), + USDC: IUSDC(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + }); + chainInfosMainnet[4] = ChainInfo({ + chainId: 4, + name: "bsc", + url: vm.envOr( + "BSC_RPC_URL", + string("https://bsc-dataseed2.defibit.io") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7 + ), + wormhole: IWormhole(0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosMainnet[6] = ChainInfo({ + chainId: 6, + name: "avalanche", + url: vm.envOr( + "AVALANCHE_RPC_URL", + string("https://rpc.ankr.com/avalanche") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0x0e082F06FF657D94310cB8cE8B0D9a04541d8052 + ), + wormhole: IWormhole(0x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c), + circleMessageTransmitter: IMessageTransmitter( + 0x8186359aF5F57FbB40c6b14A588d2A59C0C29880 + ), + circleTokenMessenger: ITokenMessenger( + 0x6B25532e1060CE10cc3B0A99e5683b91BFDe6982 + ), + USDC: IUSDC(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E) + }); + chainInfosMainnet[10] = ChainInfo({ + chainId: 10, + name: "fantom", + url: vm.envOr( + "FANTOM_RPC_URL", + string("https://rpc.ankr.com/fantom") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2 + ), + wormhole: IWormhole(0x126783A6Cb203a3E35344528B26ca3a0489a1485), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosMainnet[13] = ChainInfo({ + chainId: 13, + name: "klaytn", + url: vm.envOr( + "KLAYTN_RPC_URL", + string("https://klaytn-mainnet-rpc.allthatnode.com:8551") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0x5b08ac39EAED75c0439FC750d9FE7E1F9dD0193F + ), + wormhole: IWormhole(0x0C21603c4f3a6387e241c0091A7EA39E43E90bb7), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosMainnet[14] = ChainInfo({ + chainId: 14, + name: "celo", + url: vm.envOr("CELO_RPC_URL", string("https://forno.celo.org")), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0x796Dff6D74F3E27060B71255Fe517BFb23C93eed + ), + wormhole: IWormhole(0xa321448d90d4e5b0A732867c18eA198e75CAC48E), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosMainnet[12] = ChainInfo({ + chainId: 12, + name: "acala", + url: vm.envOr( + "ACALA_RPC_URL", + string("https://eth-rpc-acala.aca-api.network") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0xae9d7fe007b3327AA64A32824Aaac52C42a6E624 + ), + wormhole: IWormhole(0xa321448d90d4e5b0A732867c18eA198e75CAC48E), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosMainnet[11] = ChainInfo({ + chainId: 11, + name: "karura", + url: vm.envOr( + "KARURA_RPC_URL", + string("https://eth-rpc-karura.aca-api.network") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0xae9d7fe007b3327AA64A32824Aaac52C42a6E624 + ), + wormhole: IWormhole(0xa321448d90d4e5b0A732867c18eA198e75CAC48E), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosMainnet[16] = ChainInfo({ + chainId: 16, + name: "moombeam", + url: vm.envOr( + "MOOMBEAM_RPC_URL", + string("https://rpc.ankr.com/moonbeam") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0xB1731c586ca89a23809861c6103F0b96B3F57D92 + ), + wormhole: IWormhole(0xC8e2b0cD52Cf01b0Ce87d389Daa3d414d4cE29f3), + circleMessageTransmitter: IMessageTransmitter(address(0)), + circleTokenMessenger: ITokenMessenger(address(0)), + USDC: IUSDC(address(0)) + }); + chainInfosMainnet[23] = ChainInfo({ + chainId: 23, + name: "arbitrum", + url: vm.envOr( + "ARBITRUM_RPC_URL", + string("https://arb1.arbitrum.io/rpc") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0x0b2402144Bb366A632D14B83F244D2e0e21bD39c + ), + wormhole: IWormhole(0xa5f208e072434bC67592E4C49C1B991BA79BCA46), + circleMessageTransmitter: IMessageTransmitter( + 0xC30362313FBBA5cf9163F0bb16a0e01f01A896ca + ), + circleTokenMessenger: ITokenMessenger( + 0x19330d10D9Cc8751218eaf51E8885D058642E08A + ), + USDC: IUSDC(0xaf88d065e77c8cC2239327C5EDb3A432268e5831) + }); + chainInfosMainnet[24] = ChainInfo({ + chainId: 24, + name: "optimism", + url: vm.envOr( + "OPTIMISM_RPC_URL", + string("https://rpc.ankr.com/optimism") + ), + relayer: IWormholeRelayer( + 0x27428DD2d3DD32A4D7f7C497eAaa23130d894911 + ), + tokenBridge: ITokenBridge( + 0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b + ), + wormhole: IWormhole(0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722), + circleMessageTransmitter: IMessageTransmitter( + 0x4D41f22c5a0e5c74090899E5a8Fb597a8842b3e8 + ), + circleTokenMessenger: ITokenMessenger( + 0x2B4069517957735bE00ceE0fadAE88a26365528f + ), + USDC: IUSDC(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) + }); + chainInfosMainnet[30] = ChainInfo({ + chainId: 30, + name: "base", + url: vm.envOr("BASE_RPC_URL", string("https://mainnet.base.org")), + relayer: IWormholeRelayer( + 0x706F82e9bb5b0813501714Ab5974216704980e31 + ), + tokenBridge: ITokenBridge( + 0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627 + ), + wormhole: IWormhole(0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6), + circleMessageTransmitter: IMessageTransmitter( + address(0xAD09780d193884d503182aD4588450C416D6F9D4) + ), + circleTokenMessenger: ITokenMessenger( + address(0x1682Ae6375C4E4A97e4B583BC394c861A46D8962) + ), + USDC: IUSDC(address(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913)) + }); + } + + receive() external payable {} } abstract contract WormholeRelayerBasicTest is WormholeRelayerTest { - /** - * @dev virtual function to initialize source chain before each test - */ - function setUpSource() public virtual; - - /** - * @dev virtual function to initialize target chain before each test - */ - function setUpTarget() public virtual; - - /** - * @dev virtual function to initialize other active forks before each test - * Note: not called for source/target forks - */ - function setUpOther(ActiveFork memory fork) public virtual {} - - /* - * aliases for activeForks - */ - - ChainInfo public sourceChainInfo; - ChainInfo public targetChainInfo; - - uint16 public sourceChain; - uint16 public targetChain; - - uint256 public sourceFork; - uint256 public targetFork; - - IWormholeRelayer public relayerSource; - ITokenBridge public tokenBridgeSource; - IWormhole public wormholeSource; - - IWormholeRelayer public relayerTarget; - ITokenBridge public tokenBridgeTarget; - IWormhole public wormholeTarget; - - /* - * end activeForks aliases - */ - - constructor() WormholeRelayerTest() { - setTestnetForkChains(6, 14); - } - - function setUp() public override { - sourceFork = 0; - targetFork = 1; - _setUp(); - // aliases can't be set until after setUp - sourceFork = activeForks[activeForksList[0]].fork; - targetFork = activeForks[activeForksList[1]].fork; - } - - function setUpFork(ActiveFork memory fork) public override { - if (fork.chainId == sourceChain) { - setUpSource(); - } else if (fork.chainId == targetChain) { - setUpTarget(); - } else { - setUpOther(fork); - } - } - - function setActiveForks(ChainInfo[] memory chainInfos) public override { - _setActiveForks(chainInfos); - - sourceChainInfo = chainInfos[0]; - sourceChain = sourceChainInfo.chainId; - relayerSource = sourceChainInfo.relayer; - tokenBridgeSource = sourceChainInfo.tokenBridge; - wormholeSource = sourceChainInfo.wormhole; - - targetChainInfo = chainInfos[1]; - targetChain = targetChainInfo.chainId; - relayerTarget = targetChainInfo.relayer; - tokenBridgeTarget = targetChainInfo.tokenBridge; - wormholeTarget = targetChainInfo.wormhole; + /** + * @dev virtual function to initialize source chain before each test + */ + function setUpSource() public virtual; + + /** + * @dev virtual function to initialize target chain before each test + */ + function setUpTarget() public virtual; + + /** + * @dev virtual function to initialize other active forks before each test + * Note: not called for source/target forks + */ + function setUpOther(ActiveFork memory fork) public virtual {} + + /* + * aliases for activeForks + */ + + ChainInfo public sourceChainInfo; + ChainInfo public targetChainInfo; + + uint16 public sourceChain; + uint16 public targetChain; + + uint256 public sourceFork; + uint256 public targetFork; + + IWormholeRelayer public relayerSource; + ITokenBridge public tokenBridgeSource; + IWormhole public wormholeSource; + + IWormholeRelayer public relayerTarget; + ITokenBridge public tokenBridgeTarget; + IWormhole public wormholeTarget; + + /* + * end activeForks aliases + */ + + constructor() WormholeRelayerTest() { + setTestnetForkChains(6, 14); + } + + function setUp() public override { + sourceFork = 0; + targetFork = 1; + _setUp(); + // aliases can't be set until after setUp + sourceFork = activeForks[activeForksList[0]].fork; + targetFork = activeForks[activeForksList[1]].fork; + } + + function setUpFork(ActiveFork memory fork) public override { + if (fork.chainId == sourceChain) { + setUpSource(); + } else if (fork.chainId == targetChain) { + setUpTarget(); + } else { + setUpOther(fork); } - - function setTestnetForkChains( - uint16 _sourceChain, - uint16 _targetChain - ) public { - ChainInfo[] memory forks = new ChainInfo[](2); - forks[0] = chainInfosTestnet[_sourceChain]; - forks[1] = chainInfosTestnet[_targetChain]; - setActiveForks(forks); - } - - function setMainnetForkChains( - uint16 _sourceChain, - uint16 _targetChain - ) public { - ChainInfo[] memory forks = new ChainInfo[](2); - forks[0] = chainInfosMainnet[_sourceChain]; - forks[1] = chainInfosMainnet[_targetChain]; - setActiveForks(forks); - } - - function setForkChains( - bool testnet, - uint16 _sourceChain, - uint16 _targetChain - ) public { - if (testnet) { - setTestnetForkChains(_sourceChain, _targetChain); - return; - } - setMainnetForkChains(_sourceChain, _targetChain); + } + + function setActiveForks(ChainInfo[] memory chainInfos) public override { + _setActiveForks(chainInfos); + + sourceChainInfo = chainInfos[0]; + sourceChain = sourceChainInfo.chainId; + relayerSource = sourceChainInfo.relayer; + tokenBridgeSource = sourceChainInfo.tokenBridge; + wormholeSource = sourceChainInfo.wormhole; + + targetChainInfo = chainInfos[1]; + targetChain = targetChainInfo.chainId; + relayerTarget = targetChainInfo.relayer; + tokenBridgeTarget = targetChainInfo.tokenBridge; + wormholeTarget = targetChainInfo.wormhole; + } + + function setTestnetForkChains( + uint16 _sourceChain, + uint16 _targetChain + ) public { + ChainInfo[] memory forks = new ChainInfo[](2); + forks[0] = chainInfosTestnet[_sourceChain]; + forks[1] = chainInfosTestnet[_targetChain]; + setActiveForks(forks); + } + + function setMainnetForkChains( + uint16 _sourceChain, + uint16 _targetChain + ) public { + ChainInfo[] memory forks = new ChainInfo[](2); + forks[0] = chainInfosMainnet[_sourceChain]; + forks[1] = chainInfosMainnet[_targetChain]; + setActiveForks(forks); + } + + function setForkChains( + bool testnet, + uint16 _sourceChain, + uint16 _targetChain + ) public { + if (testnet) { + setTestnetForkChains(_sourceChain, _targetChain); + return; } + setMainnetForkChains(_sourceChain, _targetChain); + } } diff --git a/test/Proxy.t.sol b/test/Proxy.t.sol index f7b7a95..e666745 100644 --- a/test/Proxy.t.sol +++ b/test/Proxy.t.sol @@ -1,7 +1,5 @@ // SPDX-License-Identifier: Apache 2 -// forge test --match-contract QueryTest - pragma solidity ^0.8.24; import "forge-std/Test.sol"; diff --git a/test/WormholeRelayer/CCTPAndTokenBridgeBase.t.sol b/test/WormholeRelayer/CCTPAndTokenBridgeBase.t.sol index 1c6ff90..9db9995 100644 --- a/test/WormholeRelayer/CCTPAndTokenBridgeBase.t.sol +++ b/test/WormholeRelayer/CCTPAndTokenBridgeBase.t.sol @@ -5,237 +5,237 @@ import "wormhole-sdk/interfaces/token/IERC20.sol"; import "wormhole-sdk/testing/WormholeRelayerTest.sol"; contract CCTPAndTokenBridgeToy is CCTPAndTokenSender, CCTPAndTokenReceiver { - uint256 constant GAS_LIMIT = 250_000; - - constructor( - address _wormholeRelayer, - address _tokenBridge, - address _wormhole, - address _circleMessageTransmitter, - address _circleTokenMessenger, - address _USDC + uint256 constant GAS_LIMIT = 250_000; + + constructor( + address _wormholeRelayer, + address _tokenBridge, + address _wormhole, + address _circleMessageTransmitter, + address _circleTokenMessenger, + address _USDC + ) + CCTPAndTokenBase( + _wormholeRelayer, + _tokenBridge, + _wormhole, + _circleMessageTransmitter, + _circleTokenMessenger, + _USDC ) - CCTPAndTokenBase( - _wormholeRelayer, - _tokenBridge, - _wormhole, - _circleMessageTransmitter, - _circleTokenMessenger, - _USDC - ) - { - setCCTPDomain(23, 3); - setCCTPDomain(2, 0); - } - - function quoteCrossChainDeposit( - uint16 targetChain - ) public view returns (uint256 cost) { - // Cost of delivering token and payload to targetChain - (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( - targetChain, - 0, - GAS_LIMIT - ); - } - - function sendCrossChainDeposit( - uint16 targetChain, - address recipient, - uint256 amount, - address token - ) public payable { - uint256 cost = quoteCrossChainDeposit(targetChain); - require( - msg.value == cost, - "msg.value must be quoteCrossChainDeposit(targetChain)" - ); - - IERC20(token).transferFrom(msg.sender, address(this), amount); - - bytes memory payload = abi.encode(recipient, amount); - sendTokenWithPayloadToEvm( - targetChain, - fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to - payload, - 0, // receiver value - GAS_LIMIT, - token, - amount - ); - } - - function sendCrossChainUSDCDeposit( - uint16 targetChain, - address recipient, - uint256 amount - ) public payable { - uint256 cost = quoteCrossChainDeposit(targetChain); - require( - msg.value == cost, - "msg.value must be quoteCrossChainDeposit(targetChain)" - ); - - IERC20(USDC).transferFrom(msg.sender, address(this), amount); - - bytes memory payload = abi.encode(recipient, amount); - sendUSDCWithPayloadToEvm( - targetChain, - fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to - payload, - 0, // receiver value - GAS_LIMIT, - amount - ); - } - - function receivePayloadAndUSDC( - bytes memory payload, - uint256 amount, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 // deliveryHash - ) - internal - override - onlyWormholeRelayer - isRegisteredSender(sourceChain, sourceAddress) - { - (address recipient, uint256 expectedAmount) = abi.decode( - payload, - (address, uint256) - ); - require(amount == expectedAmount, "amount != payload.expectedAmount"); - IERC20(USDC).transfer(recipient, amount); - } - - function receivePayloadAndTokens( - bytes memory payload, - TokenReceived[] memory receivedTokens, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 // deliveryHash - ) - internal - override - onlyWormholeRelayer - isRegisteredSender(sourceChain, sourceAddress) - { - require(receivedTokens.length == 1, "Expected 1 token transfers"); - address recipient = abi.decode(payload, (address)); - IERC20(receivedTokens[0].tokenAddress).transfer( - recipient, - receivedTokens[0].amount - ); - } + { + setCCTPDomain(23, 3); + setCCTPDomain(2, 0); + } + + function quoteCrossChainDeposit( + uint16 targetChain + ) public view returns (uint256 cost) { + // Cost of delivering token and payload to targetChain + (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( + targetChain, + 0, + GAS_LIMIT + ); + } + + function sendCrossChainDeposit( + uint16 targetChain, + address recipient, + uint256 amount, + address token + ) public payable { + uint256 cost = quoteCrossChainDeposit(targetChain); + require( + msg.value == cost, + "msg.value must be quoteCrossChainDeposit(targetChain)" + ); + + IERC20(token).transferFrom(msg.sender, address(this), amount); + + bytes memory payload = abi.encode(recipient, amount); + sendTokenWithPayloadToEvm( + targetChain, + fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to + payload, + 0, // receiver value + GAS_LIMIT, + token, + amount + ); + } + + function sendCrossChainUSDCDeposit( + uint16 targetChain, + address recipient, + uint256 amount + ) public payable { + uint256 cost = quoteCrossChainDeposit(targetChain); + require( + msg.value == cost, + "msg.value must be quoteCrossChainDeposit(targetChain)" + ); + + IERC20(USDC).transferFrom(msg.sender, address(this), amount); + + bytes memory payload = abi.encode(recipient, amount); + sendUSDCWithPayloadToEvm( + targetChain, + fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to + payload, + 0, // receiver value + GAS_LIMIT, + amount + ); + } + + function receivePayloadAndUSDC( + bytes memory payload, + uint256 amount, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 // deliveryHash + ) + internal + override + onlyWormholeRelayer + isRegisteredSender(sourceChain, sourceAddress) + { + (address recipient, uint256 expectedAmount) = abi.decode( + payload, + (address, uint256) + ); + require(amount == expectedAmount, "amount != payload.expectedAmount"); + IERC20(USDC).transfer(recipient, amount); + } + + function receivePayloadAndTokens( + bytes memory payload, + TokenReceived[] memory receivedTokens, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 // deliveryHash + ) + internal + override + onlyWormholeRelayer + isRegisteredSender(sourceChain, sourceAddress) + { + require(receivedTokens.length == 1, "Expected 1 token transfers"); + address recipient = abi.decode(payload, (address)); + IERC20(receivedTokens[0].tokenAddress).transfer( + recipient, + receivedTokens[0].amount + ); + } } contract WormholeSDKTest is WormholeRelayerBasicTest { - CCTPAndTokenBridgeToy CCTPAndTokenBridgeToySource; - CCTPAndTokenBridgeToy CCTPAndTokenBridgeToyTarget; - ERC20Mock USDCSource; - ERC20Mock USDCTarget; - ERC20Mock public token; - - constructor() { - setMainnetForkChains(23, 2); - } - - function setUpSource() public override { - USDCSource = ERC20Mock(address(sourceChainInfo.USDC)); - mintUSDC(sourceChain, address(this), 5000e18); - CCTPAndTokenBridgeToySource = new CCTPAndTokenBridgeToy( - address(relayerSource), - address(tokenBridgeSource), - address(wormholeSource), - address(sourceChainInfo.circleMessageTransmitter), - address(sourceChainInfo.circleTokenMessenger), - address(USDCSource) - ); - token = createAndAttestToken(sourceChain); - } - - function setUpTarget() public override { - USDCTarget = ERC20Mock(address(targetChainInfo.USDC)); - mintUSDC(targetChain, address(this), 5000e18); - CCTPAndTokenBridgeToyTarget = new CCTPAndTokenBridgeToy( - address(relayerTarget), - address(tokenBridgeTarget), - address(wormholeTarget), - address(targetChainInfo.circleMessageTransmitter), - address(targetChainInfo.circleTokenMessenger), - address(USDCTarget) - ); - } - - function setUpGeneral() public override { - vm.selectFork(sourceFork); - CCTPAndTokenBridgeToySource.setRegisteredSender( - targetChain, - toUniversalAddress(address(CCTPAndTokenBridgeToyTarget)) - ); - - vm.selectFork(targetFork); - CCTPAndTokenBridgeToyTarget.setRegisteredSender( - sourceChain, - toUniversalAddress(address(CCTPAndTokenBridgeToySource)) - ); - } - - function testSendUSDC() public { - vm.selectFork(sourceFork); - - uint256 amount = 100e6; - USDCSource.approve(address(CCTPAndTokenBridgeToySource), amount); - - vm.selectFork(targetFork); - address recipient = 0x1234567890123456789012345678901234567890; - - vm.selectFork(sourceFork); - uint256 cost = CCTPAndTokenBridgeToySource.quoteCrossChainDeposit( - targetChain - ); - - vm.recordLogs(); - CCTPAndTokenBridgeToySource.sendCrossChainUSDCDeposit{value: cost}( - targetChain, - recipient, - amount - ); - performDelivery(true); - - vm.selectFork(targetFork); - assertEq(IERC20(USDCTarget).balanceOf(recipient), amount); - } - - function testSendToken() public { - vm.selectFork(sourceFork); - - uint256 amount = 19e17; - token.approve(address(CCTPAndTokenBridgeToySource), amount); - - vm.selectFork(targetFork); - address recipient = 0x1234567890123456789012345678901234567890; - - vm.selectFork(sourceFork); - uint256 cost = CCTPAndTokenBridgeToySource.quoteCrossChainDeposit( - targetChain - ); - - vm.recordLogs(); - CCTPAndTokenBridgeToySource.sendCrossChainDeposit{value: cost}( - targetChain, - recipient, - amount, - address(token) - ); - performDelivery(); - - vm.selectFork(targetFork); - address wormholeWrappedToken = tokenBridgeTarget.wrappedAsset( - sourceChain, - toUniversalAddress(address(token)) - ); - assertEq(IERC20(wormholeWrappedToken).balanceOf(recipient), amount); - } + CCTPAndTokenBridgeToy CCTPAndTokenBridgeToySource; + CCTPAndTokenBridgeToy CCTPAndTokenBridgeToyTarget; + ERC20Mock USDCSource; + ERC20Mock USDCTarget; + ERC20Mock public token; + + constructor() { + setMainnetForkChains(23, 2); + } + + function setUpSource() public override { + USDCSource = ERC20Mock(address(sourceChainInfo.USDC)); + mintUSDC(sourceChain, address(this), 5000e18); + CCTPAndTokenBridgeToySource = new CCTPAndTokenBridgeToy( + address(relayerSource), + address(tokenBridgeSource), + address(wormholeSource), + address(sourceChainInfo.circleMessageTransmitter), + address(sourceChainInfo.circleTokenMessenger), + address(USDCSource) + ); + token = createAndAttestToken(sourceChain); + } + + function setUpTarget() public override { + USDCTarget = ERC20Mock(address(targetChainInfo.USDC)); + mintUSDC(targetChain, address(this), 5000e18); + CCTPAndTokenBridgeToyTarget = new CCTPAndTokenBridgeToy( + address(relayerTarget), + address(tokenBridgeTarget), + address(wormholeTarget), + address(targetChainInfo.circleMessageTransmitter), + address(targetChainInfo.circleTokenMessenger), + address(USDCTarget) + ); + } + + function setUpGeneral() public override { + vm.selectFork(sourceFork); + CCTPAndTokenBridgeToySource.setRegisteredSender( + targetChain, + toUniversalAddress(address(CCTPAndTokenBridgeToyTarget)) + ); + + vm.selectFork(targetFork); + CCTPAndTokenBridgeToyTarget.setRegisteredSender( + sourceChain, + toUniversalAddress(address(CCTPAndTokenBridgeToySource)) + ); + } + + function testSendUSDC() public { + vm.selectFork(sourceFork); + + uint256 amount = 100e6; + USDCSource.approve(address(CCTPAndTokenBridgeToySource), amount); + + vm.selectFork(targetFork); + address recipient = 0x1234567890123456789012345678901234567890; + + vm.selectFork(sourceFork); + uint256 cost = CCTPAndTokenBridgeToySource.quoteCrossChainDeposit( + targetChain + ); + + vm.recordLogs(); + CCTPAndTokenBridgeToySource.sendCrossChainUSDCDeposit{value: cost}( + targetChain, + recipient, + amount + ); + performDelivery(true); + + vm.selectFork(targetFork); + assertEq(IERC20(USDCTarget).balanceOf(recipient), amount); + } + + function testSendToken() public { + vm.selectFork(sourceFork); + + uint256 amount = 19e17; + token.approve(address(CCTPAndTokenBridgeToySource), amount); + + vm.selectFork(targetFork); + address recipient = 0x1234567890123456789012345678901234567890; + + vm.selectFork(sourceFork); + uint256 cost = CCTPAndTokenBridgeToySource.quoteCrossChainDeposit( + targetChain + ); + + vm.recordLogs(); + CCTPAndTokenBridgeToySource.sendCrossChainDeposit{value: cost}( + targetChain, + recipient, + amount, + address(token) + ); + performDelivery(); + + vm.selectFork(targetFork); + address wormholeWrappedToken = tokenBridgeTarget.wrappedAsset( + sourceChain, + toUniversalAddress(address(token)) + ); + assertEq(IERC20(wormholeWrappedToken).balanceOf(recipient), amount); + } } diff --git a/test/WormholeRelayer/CCTPBase.t.sol b/test/WormholeRelayer/CCTPBase.t.sol index e0375d6..d9dbca9 100644 --- a/test/WormholeRelayer/CCTPBase.t.sol +++ b/test/WormholeRelayer/CCTPBase.t.sol @@ -5,152 +5,152 @@ import "wormhole-sdk/interfaces/token/IERC20.sol"; import "wormhole-sdk/testing/WormholeRelayerTest.sol"; contract CCTPToy is CCTPSender, CCTPReceiver { - uint256 constant GAS_LIMIT = 250_000; - - constructor( - address _wormholeRelayer, - address _wormhole, - address _circleMessageTransmitter, - address _circleTokenMessenger, - address _USDC + uint256 constant GAS_LIMIT = 250_000; + + constructor( + address _wormholeRelayer, + address _wormhole, + address _circleMessageTransmitter, + address _circleTokenMessenger, + address _USDC + ) + CCTPBase( + _wormholeRelayer, + _wormhole, + _circleMessageTransmitter, + _circleTokenMessenger, + _USDC ) - CCTPBase( - _wormholeRelayer, - _wormhole, - _circleMessageTransmitter, - _circleTokenMessenger, - _USDC - ) - { - setCCTPDomain(23, 3); - setCCTPDomain(2, 0); - } - - function quoteCrossChainDeposit( - uint16 targetChain - ) public view returns (uint256 cost) { - // Cost of delivering token and payload to targetChain - (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( - targetChain, - 0, - GAS_LIMIT - ); - } - - function sendCrossChainDeposit( - uint16 targetChain, - address recipient, - uint256 amount - ) public payable { - uint256 cost = quoteCrossChainDeposit(targetChain); - require( - msg.value == cost, - "msg.value must be quoteCrossChainDeposit(targetChain)" - ); - - IERC20(USDC).transferFrom(msg.sender, address(this), amount); - - bytes memory payload = abi.encode(recipient, amount); - sendUSDCWithPayloadToEvm( - targetChain, - fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to - payload, - 0, // receiver value - GAS_LIMIT, - amount - ); - } - - function receivePayloadAndUSDC( - bytes memory payload, - uint256 amount, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 // deliveryHash - ) - internal - override - onlyWormholeRelayer - isRegisteredSender(sourceChain, sourceAddress) - { - (address recipient, uint256 expectedAmount) = abi.decode( - payload, - (address, uint256) - ); - require(amount == expectedAmount, "amount != payload.expectedAmount"); - IERC20(USDC).transfer(recipient, amount); - } + { + setCCTPDomain(23, 3); + setCCTPDomain(2, 0); + } + + function quoteCrossChainDeposit( + uint16 targetChain + ) public view returns (uint256 cost) { + // Cost of delivering token and payload to targetChain + (cost, ) = wormholeRelayer.quoteEVMDeliveryPrice( + targetChain, + 0, + GAS_LIMIT + ); + } + + function sendCrossChainDeposit( + uint16 targetChain, + address recipient, + uint256 amount + ) public payable { + uint256 cost = quoteCrossChainDeposit(targetChain); + require( + msg.value == cost, + "msg.value must be quoteCrossChainDeposit(targetChain)" + ); + + IERC20(USDC).transferFrom(msg.sender, address(this), amount); + + bytes memory payload = abi.encode(recipient, amount); + sendUSDCWithPayloadToEvm( + targetChain, + fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to + payload, + 0, // receiver value + GAS_LIMIT, + amount + ); + } + + function receivePayloadAndUSDC( + bytes memory payload, + uint256 amount, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 // deliveryHash + ) + internal + override + onlyWormholeRelayer + isRegisteredSender(sourceChain, sourceAddress) + { + (address recipient, uint256 expectedAmount) = abi.decode( + payload, + (address, uint256) + ); + require(amount == expectedAmount, "amount != payload.expectedAmount"); + IERC20(USDC).transfer(recipient, amount); + } } contract WormholeSDKTest is WormholeRelayerBasicTest { - CCTPToy CCTPToySource; - CCTPToy CCTPToyTarget; - ERC20Mock USDCSource; - ERC20Mock USDCTarget; - - constructor() { - setMainnetForkChains(23, 2); - } - - function setUpSource() public override { - USDCSource = ERC20Mock(address(sourceChainInfo.USDC)); - mintUSDC(sourceChain, address(this), 5000e18); - CCTPToySource = new CCTPToy( - address(relayerSource), - address(wormholeSource), - address(sourceChainInfo.circleMessageTransmitter), - address(sourceChainInfo.circleTokenMessenger), - address(USDCSource) - ); - } - - function setUpTarget() public override { - USDCTarget = ERC20Mock(address(targetChainInfo.USDC)); - mintUSDC(targetChain, address(this), 5000e18); - CCTPToyTarget = new CCTPToy( - address(relayerTarget), - address(wormholeTarget), - address(targetChainInfo.circleMessageTransmitter), - address(targetChainInfo.circleTokenMessenger), - address(USDCTarget) - ); - } - - function setUpGeneral() public override { - vm.selectFork(sourceFork); - CCTPToySource.setRegisteredSender( - targetChain, - toUniversalAddress(address(CCTPToyTarget)) - ); - - vm.selectFork(targetFork); - CCTPToyTarget.setRegisteredSender( - sourceChain, - toUniversalAddress(address(CCTPToySource)) - ); - } - - function testSendToken() public { - vm.selectFork(sourceFork); - - uint256 amount = 100e6; - USDCSource.approve(address(CCTPToySource), amount); - - vm.selectFork(targetFork); - address recipient = 0x1234567890123456789012345678901234567890; - - vm.selectFork(sourceFork); - uint256 cost = CCTPToySource.quoteCrossChainDeposit(targetChain); - - vm.recordLogs(); - CCTPToySource.sendCrossChainDeposit{value: cost}( - targetChain, - recipient, - amount - ); - performDelivery(true); - - vm.selectFork(targetFork); - assertEq(IERC20(USDCTarget).balanceOf(recipient), amount); - } + CCTPToy CCTPToySource; + CCTPToy CCTPToyTarget; + ERC20Mock USDCSource; + ERC20Mock USDCTarget; + + constructor() { + setMainnetForkChains(23, 2); + } + + function setUpSource() public override { + USDCSource = ERC20Mock(address(sourceChainInfo.USDC)); + mintUSDC(sourceChain, address(this), 5000e18); + CCTPToySource = new CCTPToy( + address(relayerSource), + address(wormholeSource), + address(sourceChainInfo.circleMessageTransmitter), + address(sourceChainInfo.circleTokenMessenger), + address(USDCSource) + ); + } + + function setUpTarget() public override { + USDCTarget = ERC20Mock(address(targetChainInfo.USDC)); + mintUSDC(targetChain, address(this), 5000e18); + CCTPToyTarget = new CCTPToy( + address(relayerTarget), + address(wormholeTarget), + address(targetChainInfo.circleMessageTransmitter), + address(targetChainInfo.circleTokenMessenger), + address(USDCTarget) + ); + } + + function setUpGeneral() public override { + vm.selectFork(sourceFork); + CCTPToySource.setRegisteredSender( + targetChain, + toUniversalAddress(address(CCTPToyTarget)) + ); + + vm.selectFork(targetFork); + CCTPToyTarget.setRegisteredSender( + sourceChain, + toUniversalAddress(address(CCTPToySource)) + ); + } + + function testSendToken() public { + vm.selectFork(sourceFork); + + uint256 amount = 100e6; + USDCSource.approve(address(CCTPToySource), amount); + + vm.selectFork(targetFork); + address recipient = 0x1234567890123456789012345678901234567890; + + vm.selectFork(sourceFork); + uint256 cost = CCTPToySource.quoteCrossChainDeposit(targetChain); + + vm.recordLogs(); + CCTPToySource.sendCrossChainDeposit{value: cost}( + targetChain, + recipient, + amount + ); + performDelivery(true); + + vm.selectFork(targetFork); + assertEq(IERC20(USDCTarget).balanceOf(recipient), amount); + } } diff --git a/test/WormholeRelayer/ChooseChains.t.sol b/test/WormholeRelayer/ChooseChains.t.sol index 13220b9..c33dd0c 100644 --- a/test/WormholeRelayer/ChooseChains.t.sol +++ b/test/WormholeRelayer/ChooseChains.t.sol @@ -7,44 +7,56 @@ import "wormhole-sdk/testing/WormholeRelayerTest.sol"; import {Toy} from "./Fork.t.sol"; contract ChooseChainsTest is WormholeRelayerBasicTest { - Toy toySource; - Toy toyTarget; - - constructor() { - setTestnetForkChains(4, 6); - } - - function setUpSource() public override { - require(wormholeSource.chainId() == 4); - toySource = new Toy(address(relayerSource), address(wormholeSource)); - toySource.setRegisteredSender(targetChain, toUniversalAddress(address(this))); - } - - function setUpTarget() public override { - require(wormholeTarget.chainId() == 6); - toyTarget = new Toy(address(relayerTarget), address(wormholeTarget)); - toyTarget.setRegisteredSender(sourceChain, toUniversalAddress(address(this))); - } - - function testSendMessage() public { - vm.recordLogs(); - (uint256 cost,) = relayerSource.quoteEVMDeliveryPrice(targetChain, 1e17, 100_000); - relayerSource.sendPayloadToEvm{value: cost}(targetChain, address(toyTarget), abi.encode(55), 1e17, 100_000); - performDelivery(); - - vm.selectFork(targetFork); - require(55 == toyTarget.payloadReceived()); - } - - function testSendMessageSource() public { - vm.selectFork(targetFork); - vm.recordLogs(); - - (uint256 cost,) = relayerTarget.quoteEVMDeliveryPrice(sourceChain, 1e17, 100_000); - relayerTarget.sendPayloadToEvm{value: cost}(sourceChain, address(toySource), abi.encode(56), 1e17, 100_000); - performDelivery(); - - vm.selectFork(sourceFork); - require(56 == toySource.payloadReceived()); - } + Toy toySource; + Toy toyTarget; + + constructor() { + setTestnetForkChains(4, 6); + } + + function setUpSource() public override { + require(wormholeSource.chainId() == 4); + toySource = new Toy(address(relayerSource), address(wormholeSource)); + toySource.setRegisteredSender(targetChain, toUniversalAddress(address(this))); + } + + function setUpTarget() public override { + require(wormholeTarget.chainId() == 6); + toyTarget = new Toy(address(relayerTarget), address(wormholeTarget)); + toyTarget.setRegisteredSender(sourceChain, toUniversalAddress(address(this))); + } + + function testSendMessage() public { + vm.recordLogs(); + (uint256 cost,) = relayerSource.quoteEVMDeliveryPrice(targetChain, 1e17, 100_000); + relayerSource.sendPayloadToEvm{value: cost}( + targetChain, + address(toyTarget), + abi.encode(55), + 1e17, + 100_000 + ); + performDelivery(); + + vm.selectFork(targetFork); + require(55 == toyTarget.payloadReceived()); + } + + function testSendMessageSource() public { + vm.selectFork(targetFork); + vm.recordLogs(); + + (uint256 cost,) = relayerTarget.quoteEVMDeliveryPrice(sourceChain, 1e17, 100_000); + relayerTarget.sendPayloadToEvm{value: cost}( + sourceChain, + address(toySource), + abi.encode(56), + 1e17, + 100_000 + ); + performDelivery(); + + vm.selectFork(sourceFork); + require(56 == toySource.payloadReceived()); + } } diff --git a/test/WormholeRelayer/ExtraChains.t.sol b/test/WormholeRelayer/ExtraChains.t.sol index 94db0cf..5b30afa 100644 --- a/test/WormholeRelayer/ExtraChains.t.sol +++ b/test/WormholeRelayer/ExtraChains.t.sol @@ -8,44 +8,44 @@ import "wormhole-sdk/testing/WormholeRelayerTest.sol"; import {Toy} from "./Fork.t.sol"; contract ExtraChainsTest is WormholeRelayerTest { - mapping(uint16 => Toy) toys; - - constructor() WormholeRelayerTest() { - ChainInfo[] memory chains = new ChainInfo[](3); - chains[0] = chainInfosTestnet[4]; - chains[1] = chainInfosTestnet[6]; - chains[2] = chainInfosTestnet[14]; - setActiveForks(chains); - } - - function setUpFork(ActiveFork memory fork) public override { - toys[fork.chainId] = new Toy(address(fork.relayer), address(fork.wormhole)); - toys[fork.chainId].setRegisteredSender(4, toUniversalAddress(address(this))); - toys[fork.chainId].setRegisteredSender(6, toUniversalAddress(address(this))); - toys[fork.chainId].setRegisteredSender(14, toUniversalAddress(address(this))); - } - - function testSendFromCelo() public { - ActiveFork memory celo = activeForks[14]; - - uint16[] memory chains = new uint16[](2); - chains[0] = 4; - chains[1] = 6; - for (uint16 i = 0; i < chains.length; ++i) { - uint16 chainId = chains[i]; - vm.selectFork(celo.fork); - vm.recordLogs(); - ActiveFork memory target = activeForks[chainId]; - - (uint256 cost,) = celo.relayer.quoteEVMDeliveryPrice(target.chainId, 1e17, 100_000); - - celo.relayer.sendPayloadToEvm{value: cost}( - target.chainId, address(toys[target.chainId]), abi.encode(56), 1e17, 100_000 - ); - performDelivery(); - - vm.selectFork(target.fork); - require(56 == toys[target.chainId].payloadReceived()); - } + mapping(uint16 => Toy) toys; + + constructor() WormholeRelayerTest() { + ChainInfo[] memory chains = new ChainInfo[](3); + chains[0] = chainInfosTestnet[4]; + chains[1] = chainInfosTestnet[6]; + chains[2] = chainInfosTestnet[14]; + setActiveForks(chains); + } + + function setUpFork(ActiveFork memory fork) public override { + toys[fork.chainId] = new Toy(address(fork.relayer), address(fork.wormhole)); + toys[fork.chainId].setRegisteredSender(4, toUniversalAddress(address(this))); + toys[fork.chainId].setRegisteredSender(6, toUniversalAddress(address(this))); + toys[fork.chainId].setRegisteredSender(14, toUniversalAddress(address(this))); + } + + function testSendFromCelo() public { + ActiveFork memory celo = activeForks[14]; + + uint16[] memory chains = new uint16[](2); + chains[0] = 4; + chains[1] = 6; + for (uint16 i = 0; i < chains.length; ++i) { + uint16 chainId = chains[i]; + vm.selectFork(celo.fork); + vm.recordLogs(); + ActiveFork memory target = activeForks[chainId]; + + (uint256 cost,) = celo.relayer.quoteEVMDeliveryPrice(target.chainId, 1e17, 100_000); + + celo.relayer.sendPayloadToEvm{value: cost}( + target.chainId, address(toys[target.chainId]), abi.encode(56), 1e17, 100_000 + ); + performDelivery(); + + vm.selectFork(target.fork); + require(56 == toys[target.chainId].payloadReceived()); } + } } diff --git a/test/WormholeRelayer/Fork.t.sol b/test/WormholeRelayer/Fork.t.sol index de0b298..4b420dd 100644 --- a/test/WormholeRelayer/Fork.t.sol +++ b/test/WormholeRelayer/Fork.t.sol @@ -7,287 +7,287 @@ import "wormhole-sdk/interfaces/token/IERC20.sol"; import "wormhole-sdk/testing/WormholeRelayerTest.sol"; contract Toy is Base { - IWormholeRelayer relayer; - - uint256 public payloadReceived; - - constructor( - address _wormholeRelayer, - address _wormhole - ) Base(_wormholeRelayer, _wormhole) {} - - function receiveWormholeMessages( - bytes memory payload, - bytes[] memory, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 // deliveryHash - ) - public - payable - onlyWormholeRelayer - isRegisteredSender(sourceChain, sourceAddress) - { - payloadReceived = abi.decode(payload, (uint256)); - - console.log("Toy received message"); - console.log("Payload", payloadReceived); - console.log("Value Received", msg.value); - } + IWormholeRelayer relayer; + + uint256 public payloadReceived; + + constructor( + address _wormholeRelayer, + address _wormhole + ) Base(_wormholeRelayer, _wormhole) {} + + function receiveWormholeMessages( + bytes memory payload, + bytes[] memory, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 // deliveryHash + ) + public + payable + onlyWormholeRelayer + isRegisteredSender(sourceChain, sourceAddress) + { + payloadReceived = abi.decode(payload, (uint256)); + + console.log("Toy received message"); + console.log("Payload", payloadReceived); + console.log("Value Received", msg.value); + } } contract TokenToy is TokenSender, TokenReceiver { - constructor( - address _wormholeRelayer, - address _bridge, - address _wormhole - ) TokenBase(_wormholeRelayer, _bridge, _wormhole) {} - - uint256 constant GAS_LIMIT = 250_000; - - function quoteCrossChainDeposit( - uint16 targetChain - ) public view returns (uint256 cost) { - // Cost of delivering token and payload to targetChain - uint256 deliveryCost; - (deliveryCost, ) = wormholeRelayer.quoteEVMDeliveryPrice( - targetChain, - 0, - GAS_LIMIT - ); - - // Total cost: delivery cost + cost of publishing the 'sending token' wormhole message - cost = deliveryCost + wormhole.messageFee(); - } - - function sendCrossChainDeposit( - uint16 targetChain, - address recipient, - uint256 amount, - address token - ) public payable { - uint256 cost = quoteCrossChainDeposit(targetChain); - require( - msg.value == cost, - "msg.value must be quoteCrossChainDeposit(targetChain)" - ); - - IERC20(token).transferFrom(msg.sender, address(this), amount); - - bytes memory payload = abi.encode(recipient); - sendTokenWithPayloadToEvm( - targetChain, - fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to - payload, - 0, // receiver value - GAS_LIMIT, - token, // address of IERC20 token contract - amount - ); - } - - function sendCrossChainDeposit( - uint16 targetChain, - address recipient, - uint256 amount, - address token, - uint16 refundChain, - address refundAddress - ) public payable { - uint256 cost = quoteCrossChainDeposit(targetChain); - require( - msg.value == cost, - "msg.value must be quoteCrossChainDeposit(targetChain)" - ); - - IERC20(token).transferFrom(msg.sender, address(this), amount); - - bytes memory payload = abi.encode(recipient); - sendTokenWithPayloadToEvm( - targetChain, - fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to - payload, - 0, // receiver value - GAS_LIMIT, - token, // address of IERC20 token contract - amount, - refundChain, - refundAddress - ); - } - - function receivePayloadAndTokens( - bytes memory payload, - TokenReceived[] memory receivedTokens, - bytes32 sourceAddress, - uint16 sourceChain, - bytes32 // deliveryHash - ) - internal - override - onlyWormholeRelayer - isRegisteredSender(sourceChain, sourceAddress) - { - require(receivedTokens.length == 1, "Expected 1 token transfers"); - address recipient = abi.decode(payload, (address)); - IERC20(receivedTokens[0].tokenAddress).transfer( - recipient, - receivedTokens[0].amount - ); - } + constructor( + address _wormholeRelayer, + address _bridge, + address _wormhole + ) TokenBase(_wormholeRelayer, _bridge, _wormhole) {} + + uint256 constant GAS_LIMIT = 250_000; + + function quoteCrossChainDeposit( + uint16 targetChain + ) public view returns (uint256 cost) { + // Cost of delivering token and payload to targetChain + uint256 deliveryCost; + (deliveryCost, ) = wormholeRelayer.quoteEVMDeliveryPrice( + targetChain, + 0, + GAS_LIMIT + ); + + // Total cost: delivery cost + cost of publishing the 'sending token' wormhole message + cost = deliveryCost + wormhole.messageFee(); + } + + function sendCrossChainDeposit( + uint16 targetChain, + address recipient, + uint256 amount, + address token + ) public payable { + uint256 cost = quoteCrossChainDeposit(targetChain); + require( + msg.value == cost, + "msg.value must be quoteCrossChainDeposit(targetChain)" + ); + + IERC20(token).transferFrom(msg.sender, address(this), amount); + + bytes memory payload = abi.encode(recipient); + sendTokenWithPayloadToEvm( + targetChain, + fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to + payload, + 0, // receiver value + GAS_LIMIT, + token, // address of IERC20 token contract + amount + ); + } + + function sendCrossChainDeposit( + uint16 targetChain, + address recipient, + uint256 amount, + address token, + uint16 refundChain, + address refundAddress + ) public payable { + uint256 cost = quoteCrossChainDeposit(targetChain); + require( + msg.value == cost, + "msg.value must be quoteCrossChainDeposit(targetChain)" + ); + + IERC20(token).transferFrom(msg.sender, address(this), amount); + + bytes memory payload = abi.encode(recipient); + sendTokenWithPayloadToEvm( + targetChain, + fromUniversalAddress(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to + payload, + 0, // receiver value + GAS_LIMIT, + token, // address of IERC20 token contract + amount, + refundChain, + refundAddress + ); + } + + function receivePayloadAndTokens( + bytes memory payload, + TokenReceived[] memory receivedTokens, + bytes32 sourceAddress, + uint16 sourceChain, + bytes32 // deliveryHash + ) + internal + override + onlyWormholeRelayer + isRegisteredSender(sourceChain, sourceAddress) + { + require(receivedTokens.length == 1, "Expected 1 token transfers"); + address recipient = abi.decode(payload, (address)); + IERC20(receivedTokens[0].tokenAddress).transfer( + recipient, + receivedTokens[0].amount + ); + } } contract WormholeSDKTest is WormholeRelayerBasicTest { - Toy toySource; - Toy toyTarget; - TokenToy tokenToySource; - TokenToy tokenToyTarget; - ERC20Mock public token; - - function setUpSource() public override { - toySource = new Toy(address(relayerSource), address(wormholeSource)); - toySource.setRegisteredSender( - targetChain, - toUniversalAddress(address(this)) - ); - - tokenToySource = new TokenToy( - address(relayerSource), - address(tokenBridgeSource), - address(wormholeSource) - ); - - token = createAndAttestToken(sourceChain); - } - - function setUpTarget() public override { - toyTarget = new Toy(address(relayerTarget), address(wormholeTarget)); - toyTarget.setRegisteredSender( - sourceChain, - toUniversalAddress(address(this)) - ); - - tokenToyTarget = new TokenToy( - address(relayerTarget), - address(tokenBridgeTarget), - address(wormholeTarget) - ); - } - - function setUpGeneral() public override { - vm.selectFork(sourceFork); - tokenToySource.setRegisteredSender( - targetChain, - toUniversalAddress(address(tokenToyTarget)) - ); - - vm.selectFork(targetFork); - tokenToyTarget.setRegisteredSender( - sourceChain, - toUniversalAddress(address(tokenToySource)) - ); - } - - function testSendMessage() public { - vm.recordLogs(); - (uint256 cost, ) = relayerSource.quoteEVMDeliveryPrice( - targetChain, - 1e17, - 100_000 - ); - relayerSource.sendPayloadToEvm{value: cost}( - targetChain, - address(toyTarget), - abi.encode(55), - 1e17, - 100_000 - ); - performDelivery(); - - vm.selectFork(targetFork); - require(55 == toyTarget.payloadReceived()); - } - - function testSendMessageSource() public { - vm.selectFork(targetFork); - vm.recordLogs(); - - (uint256 cost, ) = relayerTarget.quoteEVMDeliveryPrice( - sourceChain, - 1e17, - 100_000 - ); - relayerTarget.sendPayloadToEvm{value: cost}( - sourceChain, - address(toySource), - abi.encode(56), - 1e17, - 100_000 - ); - performDelivery(); - - vm.selectFork(sourceFork); - require(56 == toySource.payloadReceived()); - } - - function testSendToken() public { - vm.selectFork(sourceFork); - - uint256 amount = 19e17; - token.approve(address(tokenToySource), amount); - - vm.selectFork(targetFork); - address recipient = 0x1234567890123456789012345678901234567890; - - vm.selectFork(sourceFork); - uint256 cost = tokenToySource.quoteCrossChainDeposit(targetChain); - - vm.recordLogs(); - tokenToySource.sendCrossChainDeposit{value: cost}( - targetChain, - recipient, - amount, - address(token) - ); - performDelivery(); - - vm.selectFork(targetFork); - address wormholeWrappedToken = tokenBridgeTarget.wrappedAsset( - sourceChain, - toUniversalAddress(address(token)) - ); - assertEq(IERC20(wormholeWrappedToken).balanceOf(recipient), amount); - } - - function testSendTokenWithRefund() public { - vm.selectFork(sourceFork); - - uint256 amount = 19e17; - token.approve(address(tokenToySource), amount); - - vm.selectFork(targetFork); - address recipient = 0x1234567890123456789012345678901234567890; - address refundAddress = 0x2234567890123456789012345678901234567890; - vm.selectFork(sourceFork); - uint256 cost = tokenToySource.quoteCrossChainDeposit(targetChain); - - vm.recordLogs(); - tokenToySource.sendCrossChainDeposit{value: cost}( - targetChain, - recipient, - amount, - address(token), - targetChain, - refundAddress - ); - performDelivery(); - - vm.selectFork(targetFork); - address wormholeWrappedToken = tokenBridgeTarget.wrappedAsset( - sourceChain, - toUniversalAddress(address(token)) - ); - assertEq(IERC20(wormholeWrappedToken).balanceOf(recipient), amount); - assertTrue(refundAddress.balance > 0); - } + Toy toySource; + Toy toyTarget; + TokenToy tokenToySource; + TokenToy tokenToyTarget; + ERC20Mock public token; + + function setUpSource() public override { + toySource = new Toy(address(relayerSource), address(wormholeSource)); + toySource.setRegisteredSender( + targetChain, + toUniversalAddress(address(this)) + ); + + tokenToySource = new TokenToy( + address(relayerSource), + address(tokenBridgeSource), + address(wormholeSource) + ); + + token = createAndAttestToken(sourceChain); + } + + function setUpTarget() public override { + toyTarget = new Toy(address(relayerTarget), address(wormholeTarget)); + toyTarget.setRegisteredSender( + sourceChain, + toUniversalAddress(address(this)) + ); + + tokenToyTarget = new TokenToy( + address(relayerTarget), + address(tokenBridgeTarget), + address(wormholeTarget) + ); + } + + function setUpGeneral() public override { + vm.selectFork(sourceFork); + tokenToySource.setRegisteredSender( + targetChain, + toUniversalAddress(address(tokenToyTarget)) + ); + + vm.selectFork(targetFork); + tokenToyTarget.setRegisteredSender( + sourceChain, + toUniversalAddress(address(tokenToySource)) + ); + } + + function testSendMessage() public { + vm.recordLogs(); + (uint256 cost, ) = relayerSource.quoteEVMDeliveryPrice( + targetChain, + 1e17, + 100_000 + ); + relayerSource.sendPayloadToEvm{value: cost}( + targetChain, + address(toyTarget), + abi.encode(55), + 1e17, + 100_000 + ); + performDelivery(); + + vm.selectFork(targetFork); + require(55 == toyTarget.payloadReceived()); + } + + function testSendMessageSource() public { + vm.selectFork(targetFork); + vm.recordLogs(); + + (uint256 cost, ) = relayerTarget.quoteEVMDeliveryPrice( + sourceChain, + 1e17, + 100_000 + ); + relayerTarget.sendPayloadToEvm{value: cost}( + sourceChain, + address(toySource), + abi.encode(56), + 1e17, + 100_000 + ); + performDelivery(); + + vm.selectFork(sourceFork); + require(56 == toySource.payloadReceived()); + } + + function testSendToken() public { + vm.selectFork(sourceFork); + + uint256 amount = 19e17; + token.approve(address(tokenToySource), amount); + + vm.selectFork(targetFork); + address recipient = 0x1234567890123456789012345678901234567890; + + vm.selectFork(sourceFork); + uint256 cost = tokenToySource.quoteCrossChainDeposit(targetChain); + + vm.recordLogs(); + tokenToySource.sendCrossChainDeposit{value: cost}( + targetChain, + recipient, + amount, + address(token) + ); + performDelivery(); + + vm.selectFork(targetFork); + address wormholeWrappedToken = tokenBridgeTarget.wrappedAsset( + sourceChain, + toUniversalAddress(address(token)) + ); + assertEq(IERC20(wormholeWrappedToken).balanceOf(recipient), amount); + } + + function testSendTokenWithRefund() public { + vm.selectFork(sourceFork); + + uint256 amount = 19e17; + token.approve(address(tokenToySource), amount); + + vm.selectFork(targetFork); + address recipient = 0x1234567890123456789012345678901234567890; + address refundAddress = 0x2234567890123456789012345678901234567890; + vm.selectFork(sourceFork); + uint256 cost = tokenToySource.quoteCrossChainDeposit(targetChain); + + vm.recordLogs(); + tokenToySource.sendCrossChainDeposit{value: cost}( + targetChain, + recipient, + amount, + address(token), + targetChain, + refundAddress + ); + performDelivery(); + + vm.selectFork(targetFork); + address wormholeWrappedToken = tokenBridgeTarget.wrappedAsset( + sourceChain, + toUniversalAddress(address(token)) + ); + assertEq(IERC20(wormholeWrappedToken).balanceOf(recipient), amount); + assertTrue(refundAddress.balance > 0); + } }