diff --git a/docs/Testing.md b/docs/Testing.md new file mode 100644 index 0000000..6193a42 --- /dev/null +++ b/docs/Testing.md @@ -0,0 +1,55 @@ +# Testing + +As a general point, your testing environment should match your real world environment as much as possible. Therefore, fork testing is generally encouraged over running standalone tests, despite the RPC provider dependency and its associated headaches. + +Besides fork testing, forge also offers [mockCall](https://book.getfoundry.sh/cheatcodes/mock-call), [mockCallRevert](https://book.getfoundry.sh/cheatcodes/mock-call-revert), and [mockFunction](https://book.getfoundry.sh/cheatcodes/mock-function) cheat-codes, which should typically be preferred over writing and deploying mock contracts due to their more explicit nature. + +## Utils + +All Solidity testing utilities can be found in src/testing. + +### WormholeOverride + +**Purpose** + +The `WormholeOverride` library is the main way to fork test integrations. It allows overriding the current guardian set of the core bridge with a newly generated one which can then be used to sign messages and thus create VAAs. + +**Default Guardian Set** + +By default the new guardian set has the same size as the old one, again to match the forked network's setup as closely as possible and keep message sizes and gas costs accurate. Since this can bloat traces and make them harder to read due to the VAAs' sizes, overriding with a single guardian when debugging tests can be helpful. This can be achieved by setting the environment variable `DEFAULT_TO_DEVNET_GUARDIAN` to true. + +**Log Parsing** + +Besides signing messages / creating VAAs, `WormholeOverride` also provides convenient forge log parsing capabilities to ensure that the right number of messages with the correct content are emitted by the core bridge. Be sure to call `vm.recordLogs();` beforehand to capture emitted events so that they are available for parsing. + +**Message Fee** + +Integrators should ensure that their contracts work correctly in case of a non-zero Wormhole message fee. `WormholeOverride` provides `setMessageFee` for this purpose. + + +### CctpOverride + +The `CctpOverride` library, is somewhat similar to `WormholeOverride` in that it allows overriding Circle's attester in their CCTP [MessageTransmitter](https://github.com/circlefin/evm-cctp-contracts/blob/master/src/MessageTransmitter.sol) contract (which is comparable in its functionality to Wormhole's core bridge). + +However, `CctpOverride`, rather than providing generic signing and log parsing functionality like `WormholeOverride`, is more specialized and only deals with signing and log-parsing `CctpTokenBurnMessage`s emitted through Circle's [TokenMessenger](https://github.com/circlefin/evm-cctp-contracts/blob/master/src/TokenMessenger.sol) contract (which is roughly comparable to Wormhole's token bridge). + + +### WormholeCctpSimulator + +The `WormholeCctpSimulator` contract can be deployed to simulate a virtual `WormholeCctpTokenMessenger` instance on some made-up foreign chain. It uses `0xDDDDDDDD` as the circle domain of that chain, and also simulates virtual instances of Circle's TokenMessenger and USDC contract, which are correctly registered with the instances on the forked chain. The foreign Wormhole chain id and the address of the foreign sender can be set during construction. Uses `WormholeOverride` and `CctpOverride`. + +### UsdcDealer + +Forge's `deal` cheat code does not work for USDC. `UsdcDealer` is another override library that implements a `deal` function that allows minting of USDC. + +### CctpMessages + +Library to parse CCTP messages composed/emitted by Circle's `TokenMessenger` and `MessageTransmitter` contracts. Used in `CctpOverride` and `WormholeCctpSimulator`. + +### ERC20Mock + +Copy of SolMate's ERC20 Mock token that uses the overrideable `IERC20` interface of this SDK to guarantee compatibility. + +### LogUtils + +A library to simplify filtering of logs captured in Forge tests. Used by `WormholeOverride`, `CctpOverride`, ... diff --git a/src/WormholeCctpTokenMessenger.sol b/src/WormholeCctpTokenMessenger.sol new file mode 100644 index 0000000..04540d9 --- /dev/null +++ b/src/WormholeCctpTokenMessenger.sol @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.19; + +import {IERC20} from "IERC20/IERC20.sol"; + +import {IWormhole} from "wormhole-sdk/interfaces/IWormhole.sol"; +import {IMessageTransmitter} from "wormhole-sdk/interfaces/cctp/IMessageTransmitter.sol"; +import {ITokenMessenger} from "wormhole-sdk/interfaces/cctp/ITokenMessenger.sol"; +import {ITokenMinter} from "wormhole-sdk/interfaces/cctp/ITokenMinter.sol"; + +import {toUniversalAddress} from "wormhole-sdk/Utils.sol"; +import {WormholeCctpMessages} from "wormhole-sdk/libraries/WormholeCctpMessages.sol"; +import {CONSISTENCY_LEVEL_FINALIZED} from "wormhole-sdk/constants/ConsistencyLevel.sol"; + +/** + * @notice A way to associate a CCTP token burn message with a Wormhole message. + * @dev To construct the contract, the addresses to the Wormhole Core Bridge and CCTP Token + * Messenger must be provided. Using the CCTP Token Messenger, the Message Transmitter and Token + * Minter are derived. + * + * NOTE: For more information on CCTP message formats, please refer to the following: + * https://developers.circle.com/stablecoins/docs/message-format. + */ +abstract contract WormholeCctpTokenMessenger { + using { toUniversalAddress } for address; + + /// @dev Parsing and verifying VAA reverted at the Wormhole Core Bridge contract level. + error InvalidVaa(); + + /** + * @dev The CCTP message's source domain, destination domain and nonce must match the VAA's. + * NOTE: This nonce is the one acting as the CCTP message sequence (and not the arbitrary one + * specified when publishing Wormhole messages). + */ + error CctpVaaMismatch(uint32, uint32, uint64); + + /// @dev The emitter of the VAA must match the expected emitter. + error UnexpectedEmitter(bytes32, bytes32); + + /// @dev Wormhole Core Bridge contract address. + IWormhole immutable _wormhole; + + /// @dev Wormhole Chain ID. NOTE: This is NOT the EVM chain ID. + uint16 immutable _chainId; + + /// @dev CCTP Message Transmitter contract interface. + IMessageTransmitter immutable _messageTransmitter; + + /// @dev CCTP Token Messenger contract interface. + ITokenMessenger immutable _tokenMessenger; + + /// @dev CCTP Token Minter contract interface. + ITokenMinter immutable _tokenMinter; + + /// @dev CCTP domain for this network (configured by the CCTP Message Transmitter). + uint32 immutable _localCctpDomain; + + constructor(address wormhole, address cctpTokenMessenger) { + _wormhole = IWormhole(wormhole); + _chainId = _wormhole.chainId(); + + _tokenMessenger = ITokenMessenger(cctpTokenMessenger); + _messageTransmitter = _tokenMessenger.localMessageTransmitter(); + _tokenMinter = _tokenMessenger.localMinter(); + _localCctpDomain = _messageTransmitter.localDomain(); + } + + /** + * @dev A convenience method to set the token spending allowance for the CCTP Token Messenger, + * who will ultimately be burning the tokens. + */ + function setTokenMessengerApproval(address token, uint256 amount) internal { + IERC20(token).approve(address(_tokenMessenger), amount); + } + + /** + * @dev Method to burn tokens via CCTP Token Messenger and publish a Wormhole message associated + * with the CCTP Token Burn message. The Wormhole message encodes a `Deposit` (ID == 1), which + * has the same source domain, destination domain and nonce as the CCTP Token Burn message. + * + * NOTE: This method does not protect against re-entrancy here because it relies on the CCTP + * Token Messenger to protect against any possible re-entrancy. We are leaning on the fact that + * the Token Messenger keeps track of its local tokens, which are the only tokens it allows to + * burn (and in turn, mint on another network). + * + * NOTE: The wormhole message fee is not required to be paid by the transaction sender (so an + * integrator can use ETH funds in his contract to pay for this fee if he wants to). + */ + function burnAndPublish( + bytes32 destinationCaller, + uint32 destinationCctpDomain, + address token, + uint256 amount, + bytes32 mintRecipient, + uint32 wormholeNonce, + bytes memory payload, + uint256 wormholeFee + ) internal returns (uint64 wormholeSequence, uint64 cctpNonce) { + // Invoke Token Messenger to burn tokens and emit a CCTP token burn message. + cctpNonce = _tokenMessenger.depositForBurnWithCaller( + amount, destinationCctpDomain, mintRecipient, token, destinationCaller + ); + + // Publish deposit message via Wormhole Core Bridge. + wormholeSequence = _wormhole.publishMessage{value: wormholeFee}( + wormholeNonce, + WormholeCctpMessages.encodeDeposit( + token.toUniversalAddress(), + amount, + _localCctpDomain, // sourceCctpDomain + destinationCctpDomain, + cctpNonce, + msg.sender.toUniversalAddress(), // burnSource + mintRecipient, + payload + ), + CONSISTENCY_LEVEL_FINALIZED + ); + } + + /** + * @dev Method to verify and reconcile CCTP and Wormhole messages in order to mint tokens for + * the encoded mint recipient. This method will revert with custom errors. + * NOTE: This method does not require the caller to be the mint recipient. If your contract + * requires that the mint recipient is the caller, you should add a check after calling this + * method to see if msg.sender.toUniversalAddress() == mintRecipient. + */ + function verifyVaaAndMint( + bytes calldata encodedCctpMessage, + bytes calldata cctpAttestation, + bytes calldata encodedVaa + ) internal returns ( + IWormhole.VM memory vaa, + bytes32 token, + uint256 amount, + bytes32 burnSource, + bytes32 mintRecipient, + bytes memory payload + ) { + // First parse and verify VAA. + vaa = _parseAndVerifyVaa( encodedVaa, true /*revertCustomErrors*/); + + // Decode the deposit message so we can match the Wormhole message with the CCTP message. + uint32 sourceCctpDomain; + uint32 destinationCctpDomain; + uint64 cctpNonce; + ( + token, + amount, + sourceCctpDomain, + destinationCctpDomain, + cctpNonce, + burnSource, + mintRecipient, + payload + ) = WormholeCctpMessages.decodeDeposit(vaa.payload); + + // Finally reconcile messages and mint tokens to the mint recipient. + token = _matchMessagesAndMint( + encodedCctpMessage, + cctpAttestation, + sourceCctpDomain, + destinationCctpDomain, + cctpNonce, + token, + true // revertCustomErrors + ); + } + + /** + * @dev PLEASE USE `verifyVaaAndMint` INSTEAD. Method to verify and reconcile CCTP and Wormhole + * messages in order to mint tokens for the encoded mint recipient. This method will revert with + * Solidity's built-in Error(string). + * NOTE: This method does not require the caller to be the mint recipient. If your contract + * requires that the mint recipient is the caller, you should add a check after calling this + * method to see if msg.sender.toUniversalAddress() == mintRecipient. + */ + function verifyVaaAndMintLegacy( + bytes calldata encodedCctpMessage, + bytes calldata cctpAttestation, + bytes calldata encodedVaa + ) internal returns ( + IWormhole.VM memory vaa, + bytes32 token, + uint256 amount, + uint32 sourceCctpDomain, + uint32 destinationCctpDomain, + uint64 cctpNonce, + bytes32 burnSource, + bytes32 mintRecipient, + bytes memory payload + ) { + // First parse and verify VAA. + vaa = _parseAndVerifyVaa(encodedVaa, false /*revertCustomErrors*/); + + // Decode the deposit message so we can match the Wormhole message with the CCTP message. + ( + token, + amount, + sourceCctpDomain, + destinationCctpDomain, + cctpNonce, + burnSource, + mintRecipient, + payload + ) = WormholeCctpMessages.decodeDeposit(vaa.payload); + + // Finally reconcile messages and mint tokens to the mint recipient. + token = _matchMessagesAndMint( + encodedCctpMessage, + cctpAttestation, + sourceCctpDomain, + destinationCctpDomain, + cctpNonce, + token, + false // revertCustomErrors + ); + } + + /** + * @dev For a given remote domain and token, fetch the corresponding local token, for which the + * CCTP Token Minter has minting authority. + */ + function fetchLocalToken( + uint32 remoteDomain, + bytes32 remoteToken + ) internal view returns (bytes32 localToken) { + localToken = _tokenMinter.remoteTokensToLocalTokens( + keccak256(abi.encodePacked(remoteDomain, remoteToken)) + ).toUniversalAddress(); + } + + /** + * @dev We encourage an integrator to use this method to make sure the VAA is emitted from one + * that his contract trusts. Usually foreign emitters are stored in a mapping keyed off by + * Wormhole Chain ID (uint16). + * + * NOTE: Reverts with `UnexpectedEmitter(bytes32, bytes32)`. + */ + function requireEmitter(IWormhole.VM memory vaa, bytes32 expectedEmitter) internal pure { + if (expectedEmitter != 0 && vaa.emitterAddress != expectedEmitter) + revert UnexpectedEmitter(vaa.emitterAddress, expectedEmitter); + } + + /** + * @dev We encourage an integrator to use this method to make sure the VAA is emitted from one + * that his contract trusts. Usually foreign emitters are stored in a mapping keyed off by + * Wormhole Chain ID (uint16). + * + * NOTE: Reverts with built-in Error(string). + */ + function requireEmitterLegacy(IWormhole.VM memory vaa, bytes32 expectedEmitter) internal pure { + require(expectedEmitter != 0 && vaa.emitterAddress == expectedEmitter, "unknown emitter"); + } + + // private + + function _parseAndVerifyVaa( + bytes calldata encodedVaa, + bool revertCustomErrors + ) private view returns (IWormhole.VM memory vaa) { + bool valid; + string memory reason; + (vaa, valid, reason) = _wormhole.parseAndVerifyVM(encodedVaa); + + if (!valid) { + if (revertCustomErrors) + revert InvalidVaa(); + else + require(false, reason); + } + } + + function _matchMessagesAndMint( + bytes calldata encodedCctpMessage, + bytes calldata cctpAttestation, + uint32 vaaSourceCctpDomain, + uint32 vaaDestinationCctpDomain, + uint64 vaaCctpNonce, + bytes32 burnToken, + bool revertCustomErrors + ) private returns (bytes32 mintToken) { + // Confirm that the caller passed the correct message pair. + { + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + + assembly ("memory-safe") { + // NOTE: First four bytes is the CCTP message version. + let ptr := calldataload(encodedCctpMessage.offset) + + // NOTE: There is no need to mask here because the types defined outside of this + // block will already perform big-endian masking. + + // Source domain is bytes 4..8, so shift 24 bytes to the right. + sourceDomain := shr(192, ptr) + // Destination domain is bytes 8..12, so shift 20 bytes to the right. + destinationDomain := shr(160, ptr) + // Nonce is bytes 12..20, so shift 12 bytes to the right. + nonce := shr(96, ptr) + } + + if ( + vaaSourceCctpDomain != sourceDomain || + vaaDestinationCctpDomain != destinationDomain|| + vaaCctpNonce != nonce + ) { + if (revertCustomErrors) + revert CctpVaaMismatch(sourceDomain, destinationDomain, nonce); + else + require(false, "invalid message pair"); + } + } + + // Call the circle bridge to mint tokens to the recipient. + _messageTransmitter.receiveMessage(encodedCctpMessage, cctpAttestation); + + // We should trust that this getter will not return the zero address because the TokenMinter + // will have already minted the valid token for the mint recipient. + mintToken = fetchLocalToken(vaaSourceCctpDomain, burnToken); + } +} diff --git a/src/WormholeRelayer/CCTPAndTokenBase.sol b/src/WormholeRelayer/CCTPAndTokenBase.sol index 82cea38..0346e1c 100644 --- a/src/WormholeRelayer/CCTPAndTokenBase.sol +++ b/src/WormholeRelayer/CCTPAndTokenBase.sol @@ -105,10 +105,7 @@ abstract contract CCTPAndTokenSender is CCTPAndTokenBase { USDC, targetAddressBytes32 ); - return MessageKey( - CCTPMessageLib.CCTP_KEY_TYPE, - abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce) - ); + return MessageKey(CCTP_KEY_TYPE, abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce)); } // Publishes a CCTP transfer of 'amount' of USDC diff --git a/src/WormholeRelayer/CCTPBase.sol b/src/WormholeRelayer/CCTPBase.sol index 97bcc8d..1e14fda 100644 --- a/src/WormholeRelayer/CCTPBase.sol +++ b/src/WormholeRelayer/CCTPBase.sol @@ -11,16 +11,13 @@ 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 + // 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; @@ -117,10 +114,7 @@ abstract contract CCTPSender is CCTPBase { USDC, targetAddressBytes32 ); - return MessageKey( - CCTPMessageLib.CCTP_KEY_TYPE, - abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce) - ); + return MessageKey(CCTP_KEY_TYPE, abi.encodePacked(getCCTPDomain(wormhole.chainId()), nonce)); } // Publishes a CCTP transfer of 'amount' of USDC diff --git a/src/constants/ConsistencyLevel.sol b/src/constants/ConsistencyLevel.sol new file mode 100644 index 0000000..ab98b3b --- /dev/null +++ b/src/constants/ConsistencyLevel.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.4; + +uint8 constant CONSISTENCY_LEVEL_INSTANT = 200; +uint8 constant CONSISTENCY_LEVEL_SAFE = 201; +uint8 constant CONSISTENCY_LEVEL_FINALIZED = 1; diff --git a/src/interfaces/IWormholeRelayer.sol b/src/interfaces/IWormholeRelayer.sol index b219abd..c342919 100644 --- a/src/interfaces/IWormholeRelayer.sol +++ b/src/interfaces/IWormholeRelayer.sol @@ -27,6 +27,7 @@ struct VaaKey { // 0-127 are reserved for standardized KeyTypes, 128-255 are for custom use uint8 constant VAA_KEY_TYPE = 1; +uint8 constant CCTP_KEY_TYPE = 2; struct MessageKey { uint8 keyType; // 0-127 are reserved for standardized KeyTypes, 128-255 are for custom use diff --git a/src/libraries/WormholeCctpMessages.sol b/src/libraries/WormholeCctpMessages.sol index e3ee34a..1445b9c 100644 --- a/src/libraries/WormholeCctpMessages.sol +++ b/src/libraries/WormholeCctpMessages.sol @@ -5,6 +5,9 @@ import {IWormhole} from "wormhole-sdk/interfaces/IWormhole.sol"; import {BytesParsing} from "wormhole-sdk/libraries/BytesParsing.sol"; import {toUniversalAddress} from "wormhole-sdk/Utils.sol"; +//Message format emitted by WormholeCctpTokenMessenger +// Looks similar to the CCTP message format but is its own distinct format that goes into +// a VAA payload, and mirrors the information in the corresponding CCTP message. library WormholeCctpMessages { using { toUniversalAddress } for address; using BytesParsing for bytes; @@ -24,39 +27,9 @@ library WormholeCctpMessages { 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, @@ -67,76 +40,58 @@ library WormholeCctpMessages { bytes32 mintRecipient, bytes memory payload ) internal pure returns (bytes memory encoded) { - encoded = encodeDeposit( - universalTokenAddress, - DEPOSIT, - amount, - sourceCctpDomain, - targetCctpDomain, - cctpNonce, - burnSource, - mintRecipient, - payload - ); - } + uint payloadLen = payload.length; + if (payloadLen > type(uint16).max) + revert PayloadTooLarge(payloadLen); - 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, + encoded = abi.encodePacked( + DEPOSIT, + universalTokenAddress, amount, sourceCctpDomain, targetCctpDomain, cctpNonce, burnSource, mintRecipient, + uint16(payloadLen), payload ); } - - function encodeDeposit( - bytes32 universalTokenAddress, - uint8 payloadId, + + function asDepositUnchecked( + bytes memory encoded, + uint offset + ) internal pure returns ( + bytes32 token, 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); + bytes memory payload, + uint newOffset + ) { + uint8 payloadId; + (payloadId, offset) = encoded.asUint8Unchecked(offset); + if (payloadId != DEPOSIT) + revert InvalidMessage(); - encoded = abi.encodePacked( - payloadId, - universalTokenAddress, - amount, - sourceCctpDomain, - targetCctpDomain, - cctpNonce, - burnSource, - mintRecipient, - uint16(payloadLen), - payload - ); + (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); + newOffset = offset; } - // left in for backwards compatibility - function decodeDeposit(IWormhole.VM memory vaa) internal pure returns ( + function asDepositCdUnchecked( + bytes calldata encoded, + uint offset + ) internal pure returns ( bytes32 token, uint256 amount, uint32 sourceCctpDomain, @@ -144,9 +99,23 @@ library WormholeCctpMessages { uint64 cctpNonce, bytes32 burnSource, bytes32 mintRecipient, - bytes memory payload + bytes memory payload, + uint newOffset ) { - return decodeDeposit(vaa.payload); + uint8 payloadId; + (payloadId, offset) = encoded.asUint8CdUnchecked(offset); + if (payloadId != DEPOSIT) + revert InvalidMessage(); + + (token, offset) = encoded.asBytes32CdUnchecked(offset); + (amount, offset) = encoded.asUint256CdUnchecked(offset); + (sourceCctpDomain, offset) = encoded.asUint32CdUnchecked(offset); + (targetCctpDomain, offset) = encoded.asUint32CdUnchecked(offset); + (cctpNonce, offset) = encoded.asUint64CdUnchecked(offset); + (burnSource, offset) = encoded.asBytes32CdUnchecked(offset); + (mintRecipient, offset) = encoded.asBytes32CdUnchecked(offset); + (payload, offset) = encoded.sliceUint16PrefixedCdUnchecked(offset); + newOffset = offset; } function decodeDeposit(bytes memory encoded) internal pure returns ( @@ -159,31 +128,19 @@ library WormholeCctpMessages { 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); + uint offset = 0; + ( + token, + amount, + sourceCctpDomain, + targetCctpDomain, + cctpNonce, + burnSource, + mintRecipient, + payload, + offset + ) = asDepositUnchecked(encoded, 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/WormholeCctpSimulator.sol b/src/testing/WormholeCctpSimulator.sol index b9fb016..5b2359f 100644 --- a/src/testing/WormholeCctpSimulator.sol +++ b/src/testing/WormholeCctpSimulator.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.19; -//import "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import "wormhole-sdk/interfaces/cctp/ITokenMessenger.sol"; @@ -10,9 +9,9 @@ import "wormhole-sdk/interfaces/IWormhole.sol"; import {WormholeCctpMessages} from "wormhole-sdk/libraries/WormholeCctpMessages.sol"; import {toUniversalAddress} from "wormhole-sdk/Utils.sol"; -import {VM_ADDRESS} from "./Constants.sol"; -import "./CctpOverride.sol"; -import "./WormholeOverride.sol"; +import {VM_ADDRESS} from "wormhole-sdk/testing/Constants.sol"; +import "wormhole-sdk/testing/CctpOverride.sol"; +import "wormhole-sdk/testing/WormholeOverride.sol"; //faked foreign call chain: // foreignCaller -> foreignSender -> FOREIGN_TOKEN_MESSENGER -> foreign MessageTransmitter @@ -62,7 +61,8 @@ contract WormholeCctpSimulator { uint16 foreignChain_, bytes32 foreignSender_, //contract that invokes the core bridge and calls depositForBurn address mintRecipient_, - address usdc + address usdc, + bool skipOverrideSetup ) { wormhole = wormhole_; tokenMessenger = ITokenMessenger(tokenMessenger_); @@ -72,11 +72,12 @@ contract WormholeCctpSimulator { destinationCaller = mintRecipient; messageTransmitter = tokenMessenger.localMessageTransmitter(); - wormhole.setUpOverride(); - messageTransmitter.setUpOverride(); + if (!skipOverrideSetup) { + wormhole.setUpOverride(); + messageTransmitter.setUpOverride(); + } - foreignNonce = 0xBBBBBBBBBBBBBBBB; - foreignSequence = 0xAAAAAAAAAAAAAAAA; + foreignNonce = 0xBBBBBBBBBBBBBBBB; //default value - can be overridden if desired foreignCaller = 0xCA11E2000CA11E200CA11E200CA11E200CA11E200CA11E200CA11E2000CA11E2; @@ -134,10 +135,9 @@ contract WormholeCctpSimulator { (burnMsg, encodedCctpMessage, cctpAttestation) = _craftCctpTokenBurnMessage(amount); //craft the associated VAA - (, encodedVaa) = wormhole.craftVaa( + encodedVaa = wormhole.craftVaa( foreignChain, foreignSender, - foreignSequence++, WormholeCctpMessages.encodeDeposit( burnMsg.burnToken, amount, diff --git a/src/testing/WormholeOverride.sol b/src/testing/WormholeOverride.sol index ff5e58d..4a5dea8 100644 --- a/src/testing/WormholeOverride.sol +++ b/src/testing/WormholeOverride.sol @@ -1,14 +1,19 @@ // SPDX-License-Identifier: Apache 2 -pragma solidity ^0.8.19; +pragma solidity ^0.8.24; import {Vm} from "forge-std/Vm.sol"; -import {IWormhole} from "wormhole-sdk/interfaces/IWormhole.sol"; -import {BytesParsing} from "wormhole-sdk/libraries/BytesParsing.sol"; -import {toUniversalAddress} from "wormhole-sdk/Utils.sol"; +import {WORD_SIZE, WORD_SIZE_MINUS_ONE} from "wormhole-sdk/constants/Common.sol"; +import {IWormhole} from "wormhole-sdk/interfaces/IWormhole.sol"; +import {BytesParsing} from "wormhole-sdk/libraries/BytesParsing.sol"; +import {toUniversalAddress} from "wormhole-sdk/Utils.sol"; -import {VM_ADDRESS, DEVNET_GUARDIAN_PRIVATE_KEY} from "./Constants.sol"; -import "./LogUtils.sol"; +import {VM_ADDRESS, DEVNET_GUARDIAN_PRIVATE_KEY} from "wormhole-sdk/testing/Constants.sol"; +import {LogUtils} from "wormhole-sdk/testing/LogUtils.sol"; + +//┌────────────────────────────────────────────────────────────────────────────────────────────────┐ +//│ take control of the core bridge in forge fork tests to generate VAAs and test message emission │ +//└────────────────────────────────────────────────────────────────────────────────────────────────┘ struct PublishedMessage { uint32 timestamp; @@ -20,77 +25,405 @@ struct PublishedMessage { bytes payload; } -//create fake VAAs for forge tests +//use `using VaaEncoding for IWormhole.VM;` to convert VAAs to bytes via .encode() +library VaaEncoding { + function encode(IWormhole.VM memory vaa) internal pure returns (bytes memory) { + bytes memory sigs; + for (uint i = 0; i < vaa.signatures.length; ++i) { + IWormhole.Signature memory sig = vaa.signatures[i]; + sigs = bytes.concat(sigs, abi.encodePacked(sig.guardianIndex, sig.r, sig.s, sig.v)); + } + + return abi.encodePacked( + vaa.version, + vaa.guardianSetIndex, + uint8(vaa.signatures.length), + sigs, + vaa.timestamp, + vaa.nonce, + vaa.emitterChainId, + vaa.emitterAddress, + vaa.sequence, + vaa.consistencyLevel, + vaa.payload + ); + } +} + +//simple version of the library - should be sufficient for most use cases library WormholeOverride { + using AdvancedWormholeOverride for IWormhole; + + //Transition to a new guardian set under our control and set up default values for + // sequence (0), nonce (0), and consistency level (1 = finalized). + // + //Note: Depending on the DEFAULT_TO_DEVNET_GUARDIAN environment variable: + // false (default): The current guardian set is superseded by a new set of the same size. + // Keeps in line with real world conditions. Useful for realistic gas costs and VAA sizes. + // true: The new guardian set is comprised of only one key: DEVNET_GUARDIAN_PRIVATE_KEY. + // This is useful to reduce the size of encoded VAAs and hence call traces when debugging. + function setUpOverride(IWormhole wormhole) internal { + wormhole.setUpOverride(); + } + + //convenience function: + // 1. creates a PublishedMessage struct with the current block.timestamp and + // the stored values for sequence (auto-incremented), nonce, and consistency level + // 2. uses sign to turn it into a VAA + // 3. encodes the VAA as bytes + function craftVaa( + IWormhole wormhole, + uint16 emitterChain, + bytes32 emitterAddress, + bytes memory payload + ) internal returns (bytes memory encodedVaa) { + return wormhole.craftVaa(emitterChain, emitterAddress, payload); + } + + //turns a PublishedMessage struct into a VAA by having a quorum of guardians sign it + function sign( + IWormhole wormhole, + PublishedMessage memory pm + ) internal view returns (IWormhole.VM memory vaa) { + return wormhole.sign(pm); + } + + //fetch all messages from the logs that were emitted by the core bridge + function fetchPublishedMessages( + IWormhole wormhole, + Vm.Log[] memory logs + ) internal view returns (PublishedMessage[] memory ret) { + return wormhole.fetchPublishedMessages(logs); + } + + //tests should ensure support of non-zero core bridge message fees + function setMessageFee(IWormhole wormhole, uint256 msgFee) internal { + wormhole.setMessageFee(msgFee); + } + + //override the default consistency level used by craftVaa() + function setConsistencyLevel(IWormhole wormhole, uint8 consistencyLevel) internal { + wormhole.setConsistencyLevel(consistencyLevel); + } +} + +//────────────────────────────────────────────────────────────────────────────────────────────────── + +//more complex superset of WormholeOverride for more advanced tests +library AdvancedWormholeOverride { using { toUniversalAddress } for address; using BytesParsing for bytes; using LogUtils for Vm.Log[]; + using VaaEncoding for IWormhole.VM; Vm constant vm = Vm(VM_ADDRESS); - // keccak256("devnetGuardianPrivateKey") - 1 - bytes32 private constant _DEVNET_GUARDIAN_PK_SLOT = - 0x4c7087e9f1bf599f9f9fff4deb3ecae99b29adaab34a0f53d9fa9d61aeaecb63; + //not nicely exported by forge, so we copy it here + function _makeAddrAndKey( + string memory name + ) private returns (address addr, uint256 privateKey) { + privateKey = uint256(keccak256(abi.encodePacked(name))); + addr = vm.addr(privateKey); + vm.label(addr, name); + } + //CoreBridge storage layout + // (see: https://github.com/wormhole-foundation/wormhole/blob/24442309dc93aa771b71ab29155286dda3e5f884/ethereum/contracts/State.sol#L22-L47) + // (and: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#layout-of-state-variables-in-storage-and-transient-storage) + // + // slot │ type │ name + // ──────┼─────────┼──────────────────────────── + // 0 │ uint16 │ chainId + // 0 │ uint16 │ governanceChainId + // 1 │ bytes32 │ governanceContract + // 2 │ mapping │ guardianSets + // 3 │ uint32 │ guardianSetIndex + // 3 │ uint32 │ guardianSetExpiry (this makes no sense and is unused) + // 4 │ mapping │ sequences + // 5 │ mapping │ consumedGovernanceActions + // 6 │ mapping │ initializedImplementations + // 7 │ uint256 │ messageFee + // 8 │ uint256 │ evmChainId + uint256 constant private _STORAGE_GUARDIAN_SETS_SLOT = 2; + uint256 constant private _STORAGE_GUARDIAN_SET_INDEX_SLOT = 3; + uint256 constant private _STORAGE_MESSAGE_FEE_SLOT = 7; - uint32 constant DEFAULT_NONCE = 0xBBBBBBBB; - uint8 constant DEFAULT_CONSISTENCY_LEVEL = 1; - uint8 constant WORMHOLE_VAA_VERSION = 1; - uint16 constant GOVERNANCE_CHAIN_ID = 1; - bytes32 constant GOVERNANCE_CONTRACT = bytes32(uint256(4)); + //CoreBridge guardian set struct: + // struct GuardianSet { + // address[] keys; //slot +0 + // uint32 expirationTime; //slot +1 + // } + uint256 constant private _GUARDIAN_SET_STRUCT_EXPIRATION_OFFSET = 1; - function setUpOverride(IWormhole wormhole) internal { - setUpOverride(wormhole, DEVNET_GUARDIAN_PRIVATE_KEY); + uint8 constant WORMHOLE_VAA_VERSION = 1; + + //We add additional data to the core bridge's storage so we can conveniently use it in our + // library functions and to expose it to the test suite: + + //We store our additional data at slot keccak256("OverrideState")-1 in the core bridge + //We don't store this in a "local" struct in case tests use multiple forks and hence override + // multiple, different instances of the core bridge + uint256 private constant _OVERRIDE_STATE_SLOT = + 0x2e44eb2c79e88410071ac52f3c0e5ab51396d9208c2c783cdb8e12f39b763de8; + + //extra data (ors = _OVERRIDE_STATE_SLOT): + // slot │ type │ name + // ───────┼───────────┼──────────────────────────── + // ors+0 │ uint64 │ sequence + // ors+1 │ uint32 │ nonce + // ors+2 │ uint8 │ consistencyLevel + // ors+3 │ address[] │ guardianPrivateKeys + // ors+4 | bytes(*) | signingIndices (* = never packed, slot always contains only length) + uint256 constant private _OR_SEQUENCE_OFFSET = 0; + uint256 constant private _OR_NONCE_OFFSET = 1; + uint256 constant private _OR_CONSISTENCY_OFFSET = 2; + uint256 constant private _OR_GUARDIANS_OFFSET = 3; + uint256 constant private _OR_SIGNING_INDICES_OFFSET = 4; + + function setSequence(IWormhole wormhole, uint64 sequence) internal { unchecked { + vm.store( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_SEQUENCE_OFFSET), + bytes32(uint256(sequence)) + ); + }} + + function getSequence(IWormhole wormhole) internal view returns (uint64) { unchecked { + return uint64(uint256(vm.load( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_SEQUENCE_OFFSET) + ))); + }} + + function getAndIncrementSequence(IWormhole wormhole) internal returns (uint64) { unchecked { + uint64 sequence = getSequence(wormhole); + setSequence(wormhole, sequence + 1); + return sequence; + }} + + function setNonce(IWormhole wormhole, uint32 nonce) internal { unchecked { + vm.store( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_NONCE_OFFSET), + bytes32(uint256(nonce)) + ); + }} + + function getNonce(IWormhole wormhole) internal view returns (uint32) { unchecked { + return uint32(uint256(vm.load( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_NONCE_OFFSET) + ))); + }} + + function setConsistencyLevel(IWormhole wormhole, uint8 consistencyLevel) internal { + vm.store( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_CONSISTENCY_OFFSET), + bytes32(uint256(consistencyLevel)) + ); } - function setUpOverride(IWormhole wormhole, uint256 signer) internal { unchecked { - if (guardianPrivateKey(wormhole) == signer) - return; + function getConsistencyLevel(IWormhole wormhole) internal view returns (uint8) { + return uint8(uint256(vm.load( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_CONSISTENCY_OFFSET) + ))); + } + + function _arraySlot(uint256 slot) private pure returns (uint256) { + //dynamic storage arrays store their data starting at keccak256(slot) + return uint256(keccak256(abi.encode(slot))); + } + + function _setGuardianPrivateKeys( + IWormhole wormhole, + uint256[] memory guardianPrivateKeys + ) private { unchecked { + vm.store( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_GUARDIANS_OFFSET), + bytes32(guardianPrivateKeys.length) + ); + for (uint i = 0; i < guardianPrivateKeys.length; ++i) + vm.store( + address(wormhole), + bytes32(_arraySlot(_OVERRIDE_STATE_SLOT + _OR_GUARDIANS_OFFSET) + i), + bytes32(guardianPrivateKeys[i]) + ); + }} + + function getGuardianPrivateKeysLength( + IWormhole wormhole + ) internal view returns (uint256) { unchecked { + return uint256(vm.load( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_GUARDIANS_OFFSET) + )); + }} - require(guardianPrivateKey(wormhole) == 0, "WormholeOverride: already set up"); + function getGuardianPrivateKeys( + IWormhole wormhole + ) internal view returns (uint256[] memory) { unchecked { + uint len = getGuardianPrivateKeysLength(wormhole); + uint256[] memory keys = new uint256[](len); + for (uint i = 0; i < len; ++i) + keys[i] = uint256(vm.load( + address(wormhole), + bytes32(_arraySlot(_OVERRIDE_STATE_SLOT + _OR_GUARDIANS_OFFSET) + i) + )); - address devnetGuardian = vm.addr(signer); + return keys; + }} - // Get slot for Guardian Set at the current index - uint32 guardianSetIndex = wormhole.getCurrentGuardianSetIndex(); - bytes32 guardianSetSlot = keccak256(abi.encode(guardianSetIndex, 2)); + function setSigningIndices( + IWormhole wormhole, + uint8[] memory signingIndices + ) internal { unchecked { + vm.store( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_SIGNING_INDICES_OFFSET), + bytes32(signingIndices.length) + ); - // Overwrite all but first guardian set to zero address. This isn't - // necessary, but just in case we inadvertently access these slots - // for any reason. - uint256 numGuardians = uint256(vm.load(address(wormhole), guardianSetSlot)); - for (uint256 i = 1; i < numGuardians; ++i) + //abi.encodePacked pads elements of arrays so we have to manually pack here + bytes memory packedIndices = new bytes(signingIndices.length); + for (uint i = 0; i < signingIndices.length; ++i) { + uint8 curIdx = signingIndices[i]; + assembly ("memory-safe") { mstore8(add(add(packedIndices, WORD_SIZE), i), curIdx) } + } + + uint fullSlots = packedIndices.length / WORD_SIZE; + for (uint i = 0; i < fullSlots; ++i) { + (bytes32 val,) = packedIndices.asBytes32Unchecked(i * WORD_SIZE); vm.store( address(wormhole), - bytes32(uint256(keccak256(abi.encodePacked(guardianSetSlot))) + i), - 0 + bytes32(_arraySlot(_OVERRIDE_STATE_SLOT + _OR_SIGNING_INDICES_OFFSET) + i), + val ); + } + + uint remaining = packedIndices.length % WORD_SIZE; + if (remaining > 0) { + (uint256 val, ) = packedIndices.asUint256Unchecked(fullSlots * WORD_SIZE); + val &= ~(type(uint256).max >> (8 * remaining)); //clean unused bits to be safe + vm.store( + address(wormhole), + bytes32(_arraySlot(_OVERRIDE_STATE_SLOT + _OR_SIGNING_INDICES_OFFSET) + fullSlots), + bytes32(val) + ); + } + }} + + function _getSigningIndicesLength( + IWormhole wormhole + ) private view returns (uint256) { unchecked { + return uint256(vm.load( + address(wormhole), + bytes32(_OVERRIDE_STATE_SLOT + _OR_SIGNING_INDICES_OFFSET) + )); + }} + + function getSigningIndices( + IWormhole wormhole + ) internal view returns (bytes memory) { unchecked { + uint len = _getSigningIndicesLength(wormhole); + bytes32[] memory individualSlots = new bytes32[]((len + WORD_SIZE_MINUS_ONE) / WORD_SIZE); + for (uint i = 0; i < individualSlots.length; ++i) + individualSlots[i] = vm.load( + address(wormhole), + bytes32(_arraySlot(_OVERRIDE_STATE_SLOT + _OR_SIGNING_INDICES_OFFSET) + i) + ); + + bytes memory packed = abi.encodePacked(individualSlots); + assembly ("memory-safe") { mstore(packed, len) } + return packed; + }} + + function defaultGuardianLabel(uint256 index) internal pure returns (string memory) { + return string.concat("guardian", vm.toString(index + 1)); + } + + function _guardianSetSlot(uint32 index) private pure returns (uint256) { + //see https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays + return uint256(keccak256(abi.encode(index, _STORAGE_GUARDIAN_SETS_SLOT))); + } + + function setUpOverride(IWormhole wormhole) internal { + bool defaultToDevnetGuardian = vm.envOr("DEFAULT_TO_DEVNET_GUARDIAN", false); + uint256[] memory guardianPrivateKeys; + if (defaultToDevnetGuardian) { + guardianPrivateKeys = new uint256[](1); + guardianPrivateKeys[0] = DEVNET_GUARDIAN_PRIVATE_KEY; + } + else { + guardianPrivateKeys = + new uint256[](wormhole.getGuardianSet(wormhole.getCurrentGuardianSetIndex()).keys.length); - // Now overwrite the first guardian key with the devnet key specified - // in the function argument. + for (uint i = 0; i < guardianPrivateKeys.length; ++i) + (, guardianPrivateKeys[i]) = _makeAddrAndKey(defaultGuardianLabel(i)); + } + setUpOverride(wormhole, guardianPrivateKeys); + } + + function setUpOverride( + IWormhole wormhole, + uint256[] memory guardianPrivateKeys + ) internal { unchecked { + if (getGuardianPrivateKeys(wormhole).length != 0) + revert ("already set up"); + + if (guardianPrivateKeys.length == 0) + revert ("no guardian private keys provided"); + + if (guardianPrivateKeys.length > type(uint8).max) + revert ("too many guardians, core bridge enforces upper bound of 255"); + + //bring the core bridge under heel by introducing a new guardian set + uint32 curGuardianSetIndex = wormhole.getCurrentGuardianSetIndex(); + uint256 curGuardianSetSlot = _guardianSetSlot(curGuardianSetIndex); + + //expire the current guardian set like a normal guardian set transition would vm.store( address(wormhole), - bytes32(uint256(keccak256(abi.encodePacked(guardianSetSlot))) + 0), //just explicit w/ index 0 - devnetGuardian.toUniversalAddress() + bytes32(curGuardianSetSlot + _GUARDIAN_SET_STRUCT_EXPIRATION_OFFSET), + bytes32(block.timestamp + 1 days) ); + + uint32 newGuardianSetIndex = curGuardianSetIndex + 1; + uint256 newGuardianSetSlot = _guardianSetSlot(newGuardianSetIndex); - // Change the length to 1 guardian + //update the guardian set index vm.store( - address(wormhole), - guardianSetSlot, - bytes32(uint256(1)) // length == 1 + address(wormhole), + bytes32(_STORAGE_GUARDIAN_SET_INDEX_SLOT), + bytes32(uint256(newGuardianSetIndex)) ); - // Confirm guardian set override - address[] memory guardians = wormhole.getGuardianSet(guardianSetIndex).keys; - assert(guardians.length == 1 && guardians[0] == devnetGuardian); + //dynamic storage arrays store their length in their assigned slot + vm.store(address(wormhole), bytes32(newGuardianSetSlot), bytes32(guardianPrivateKeys.length)); + //initialize the new guardian set with the provided private keys + for (uint256 i = 0; i < guardianPrivateKeys.length; ++i) + vm.store( + address(wormhole), + bytes32(_arraySlot(newGuardianSetSlot) + i), + bytes32(uint256(uint160(vm.addr(guardianPrivateKeys[i])))) + ); + + //initialize override state with default values + setSequence(wormhole, 0); + setNonce(wormhole, 0); + setConsistencyLevel(wormhole, 1); //finalized + _setGuardianPrivateKeys(wormhole, guardianPrivateKeys); + uint quorum = guardianPrivateKeys.length * 2 / 3 + 1; + uint8[] memory signingIndices = new uint8[](quorum); + for (uint i = 0; i < quorum; ++i) + signingIndices[i] = uint8(i); - // Now do something crazy. Save the private key in a specific slot of Wormhole's storage for - // retrieval later. - vm.store(address(wormhole), _DEVNET_GUARDIAN_PK_SLOT, bytes32(signer)); + setSigningIndices(wormhole, signingIndices); }} - function guardianPrivateKey(IWormhole wormhole) internal view returns (uint256 pk) { - pk = uint256(vm.load(address(wormhole), _DEVNET_GUARDIAN_PK_SLOT)); + function setMessageFee(IWormhole wormhole, uint256 msgFee) internal { + vm.store(address(wormhole), bytes32(_STORAGE_MESSAGE_FEE_SLOT), bytes32(msgFee)); } function fetchPublishedMessages( @@ -115,7 +448,15 @@ library WormholeOverride { function sign( IWormhole wormhole, PublishedMessage memory pm - ) internal view returns (IWormhole.VM memory vaa, bytes memory encoded) { + ) internal view returns (IWormhole.VM memory vaa) { + return sign(wormhole, pm, getSigningIndices(wormhole)); + } + + function sign( + IWormhole wormhole, + PublishedMessage memory pm, + bytes memory signingGuardianIndices //treated as a packed uint8 array + ) internal view returns (IWormhole.VM memory vaa) { unchecked { vaa.version = WORMHOLE_VAA_VERSION; vaa.timestamp = pm.timestamp; vaa.nonce = pm.nonce; @@ -124,6 +465,7 @@ library WormholeOverride { vaa.sequence = pm.sequence; vaa.consistencyLevel = pm.consistencyLevel; vaa.payload = pm.payload; + vaa.guardianSetIndex = wormhole.getCurrentGuardianSetIndex(); bytes memory encodedBody = abi.encodePacked( pm.timestamp, @@ -136,79 +478,51 @@ library WormholeOverride { ); vaa.hash = keccak256(abi.encodePacked(keccak256(encodedBody))); - vaa.signatures = new IWormhole.Signature[](1); - (vaa.signatures[0].v, vaa.signatures[0].r, vaa.signatures[0].s) = - vm.sign(guardianPrivateKey(wormhole), vaa.hash); - vaa.signatures[0].v -= 27; - - encoded = abi.encodePacked( - vaa.version, - wormhole.getCurrentGuardianSetIndex(), - uint8(vaa.signatures.length), - vaa.signatures[0].guardianIndex, - vaa.signatures[0].r, - vaa.signatures[0].s, - vaa.signatures[0].v, - encodedBody - ); - } + vaa.signatures = new IWormhole.Signature[](signingGuardianIndices.length); + uint256[] memory guardianPrivateKeys = getGuardianPrivateKeys(wormhole); + for (uint i = 0; i < signingGuardianIndices.length; ++i) { + (uint8 gi, ) = signingGuardianIndices.asUint8(i); + (vaa.signatures[i].v, vaa.signatures[i].r, vaa.signatures[i].s) = + vm.sign(guardianPrivateKeys[gi], vaa.hash); + vaa.signatures[i].guardianIndex = gi; + vaa.signatures[i].v -= 27; + } + }} function craftVaa( IWormhole wormhole, uint16 emitterChain, bytes32 emitterAddress, - uint64 sequence, bytes memory payload - ) internal view returns (IWormhole.VM memory vaa, bytes memory encoded) { + ) internal returns (bytes memory encodedVaa) { PublishedMessage memory pm = PublishedMessage({ timestamp: uint32(block.timestamp), - nonce: DEFAULT_NONCE, + nonce: getNonce(wormhole), emitterChainId: emitterChain, emitterAddress: emitterAddress, - sequence: sequence, - consistencyLevel: DEFAULT_CONSISTENCY_LEVEL, + sequence: getAndIncrementSequence(wormhole), + consistencyLevel: getConsistencyLevel(wormhole), payload: payload }); - (vaa, encoded) = sign(wormhole, pm); + return sign(wormhole, pm).encode(); } - function craftGovernanceVaa( + function craftGovernancePublishedMessage( IWormhole wormhole, bytes32 module, uint8 action, uint16 targetChain, - uint64 sequence, bytes memory decree - ) internal view returns (IWormhole.VM memory vaa, bytes memory encoded) { - (vaa, encoded) = craftGovernanceVaa( - wormhole, - GOVERNANCE_CHAIN_ID, - GOVERNANCE_CONTRACT, - module, - action, - targetChain, - sequence, - decree - ); - } - - function craftGovernanceVaa( - IWormhole wormhole, - uint16 governanceChain, - bytes32 governanceContract, - bytes32 module, - uint8 action, - uint16 targetChain, - uint64 sequence, - bytes memory decree - ) internal view returns (IWormhole.VM memory vaa, bytes memory encoded) { - (vaa, encoded) = craftVaa( - wormhole, - governanceChain, - governanceContract, - sequence, - abi.encodePacked(module, action, targetChain, decree) - ); + ) internal returns (PublishedMessage memory) { + return PublishedMessage({ + timestamp: uint32(block.timestamp), + nonce: getNonce(wormhole), + emitterChainId: wormhole.governanceChainId(), + emitterAddress: wormhole.governanceContract(), + sequence: getAndIncrementSequence(wormhole), + consistencyLevel: getConsistencyLevel(wormhole), + payload: abi.encodePacked(module, action, targetChain, decree) + }); } } diff --git a/src/testing/WormholeRelayer/DeliveryInstructionDecoder.sol b/src/testing/WormholeRelayer/DeliveryInstructionDecoder.sol index 27d01aa..5caf71d 100644 --- a/src/testing/WormholeRelayer/DeliveryInstructionDecoder.sol +++ b/src/testing/WormholeRelayer/DeliveryInstructionDecoder.sol @@ -162,7 +162,7 @@ function decodeCCTPKey( (cctpKey.nonce, offset) = encoded.asUint64Unchecked(offset); } -// ------------------------------------------ -------------------------------------------- +// ------------------------------------------ private -------------------------------------------- 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 diff --git a/src/testing/WormholeRelayer/MockOffchainRelayer.sol b/src/testing/WormholeRelayer/MockOffchainRelayer.sol index 6fd9ce7..bdf29e8 100644 --- a/src/testing/WormholeRelayer/MockOffchainRelayer.sol +++ b/src/testing/WormholeRelayer/MockOffchainRelayer.sol @@ -20,295 +20,259 @@ import "./ExecutionParameters.sol"; using BytesParsing for bytes; contract MockOffchainRelayer { - using WormholeOverride for IWormhole; - using CctpOverride for IMessageTransmitter; - using CctpMessages for CctpTokenBurnMessage; - using { toUniversalAddress } for address; - using { fromUniversalAddress } for bytes32; - - Vm public constant vm = Vm(VM_ADDRESS); - - mapping(uint16 => IWormhole) wormholeContracts; - mapping(uint16 => IMessageTransmitter) messageTransmitterContracts; - mapping(uint16 => IWormholeRelayer) wormholeRelayerContracts; - mapping(uint16 => uint256) forks; - - mapping(uint256 => uint16) chainIdFromFork; - - mapping(bytes32 => bytes[]) pastEncodedSignedVaas; - - mapping(bytes32 => bytes) pastEncodedDeliveryVAA; - - function getForkChainId() internal view returns (uint16) { - uint16 chainId = chainIdFromFork[vm.activeFork()]; - require(chainId != 0, "Chain not registered with MockOffchainRelayer"); - return chainId; - } - - function getForkWormhole() internal view returns (IWormhole) { - return wormholeContracts[getForkChainId()]; - } - - function getForkMessageTransmitter() internal view returns (IMessageTransmitter) { - return messageTransmitterContracts[getForkChainId()]; - } - - function getForkWormholeRelayer() internal view returns (IWormholeRelayer) { - return wormholeRelayerContracts[getForkChainId()]; - } - - function getPastEncodedSignedVaas( - uint16 chainId, - uint64 deliveryVAASequence - ) public view returns (bytes[] memory) { - return - pastEncodedSignedVaas[ - keccak256(abi.encodePacked(chainId, deliveryVAASequence)) - ]; - } - - function getPastDeliveryVAA( - uint16 chainId, - uint64 deliveryVAASequence - ) public view returns (bytes memory) { - return - pastEncodedDeliveryVAA[ - keccak256(abi.encodePacked(chainId, deliveryVAASequence)) - ]; + using WormholeOverride for IWormhole; + using CctpOverride for IMessageTransmitter; + using CctpMessages for CctpTokenBurnMessage; + using VaaEncoding for IWormhole.VM; + using { toUniversalAddress } for address; + using { fromUniversalAddress } for bytes32; + + Vm constant vm = Vm(VM_ADDRESS); + + mapping(uint16 => IWormhole) wormholeContracts; + mapping(uint16 => IMessageTransmitter) messageTransmitterContracts; + mapping(uint16 => IWormholeRelayer) wormholeRelayerContracts; + mapping(uint16 => uint256) forks; + mapping(uint256 => uint16) chainIdFromFork; + mapping(bytes32 => bytes[]) pastEncodedVaas; + mapping(bytes32 => bytes) pastEncodedDeliveryVaa; + + function getForkChainId() internal view returns (uint16) { + uint16 chainId = chainIdFromFork[vm.activeFork()]; + require(chainId != 0, "Chain not registered with MockOffchainRelayer"); + return chainId; + } + + function getForkWormhole() internal view returns (IWormhole) { + return wormholeContracts[getForkChainId()]; + } + + function getForkMessageTransmitter() internal view returns (IMessageTransmitter) { + return messageTransmitterContracts[getForkChainId()]; + } + + function getForkWormholeRelayer() internal view returns (IWormholeRelayer) { + return wormholeRelayerContracts[getForkChainId()]; + } + + function getPastEncodedVaas( + uint16 chainId, + uint64 deliveryVaaSequence + ) public view returns (bytes[] memory) { + return pastEncodedVaas[keccak256(abi.encodePacked(chainId, deliveryVaaSequence))]; + } + + function getPastDeliveryVaa( + uint16 chainId, + uint64 deliveryVaaSequence + ) public view returns (bytes memory) { + return pastEncodedDeliveryVaa[keccak256(abi.encodePacked(chainId, deliveryVaaSequence))]; + } + + function registerChain( + uint16 chainId, + IWormhole wormholeContractAddress, + IMessageTransmitter messageTransmitterContractAddress, + IWormholeRelayer wormholeRelayerContractAddress, + uint256 fork + ) public { + wormholeContracts[chainId] = wormholeContractAddress; + messageTransmitterContracts[chainId] = messageTransmitterContractAddress; + wormholeRelayerContracts[chainId] = wormholeRelayerContractAddress; + forks[chainId] = fork; + chainIdFromFork[fork] = chainId; + } + + function cctpKeyMatchesCCTPMessage( + CCTPMessageLib.CCTPKey memory cctpKey, + CCTPMessageLib.CCTPMessage memory cctpMessage + ) internal pure returns (bool) { + (uint64 nonce,) = cctpMessage.message.asUint64(12); + (uint32 domain,) = cctpMessage.message.asUint32(4); + return nonce == cctpKey.nonce && domain == cctpKey.domain; + } + + function relay(Vm.Log[] memory logs, bool debugLogging) public { + relay(logs, bytes(""), debugLogging); + } + + function relay( + Vm.Log[] memory logs, + bytes memory deliveryOverrides, + bool debugLogging + ) public { + IWormhole emitterWormhole = getForkWormhole(); + PublishedMessage[] memory pms = emitterWormhole.fetchPublishedMessages(logs); + if (debugLogging) + console.log( + "Found %s wormhole messages in logs from %s", + pms.length, + address(emitterWormhole) + ); + + IWormhole.VM[] memory vaas = new IWormhole.VM[](pms.length); + for (uint256 i = 0; i < pms.length; ++i) + vaas[i] = emitterWormhole.sign(pms[i]); + + CCTPMessageLib.CCTPMessage[] memory cctpSignedMsgs = new CCTPMessageLib.CCTPMessage[](0); + IMessageTransmitter emitterMessageTransmitter = getForkMessageTransmitter(); + if (address(emitterMessageTransmitter) != address(0)) { + CctpTokenBurnMessage[] memory burnMsgs = + emitterMessageTransmitter.fetchBurnMessages(logs); + if (debugLogging) + console.log( + "Found %s circle messages in logs from %s", + burnMsgs.length, + address(emitterMessageTransmitter) + ); + + cctpSignedMsgs = new CCTPMessageLib.CCTPMessage[](burnMsgs.length); + for (uint256 i = 0; i < cctpSignedMsgs.length; ++i) { + cctpSignedMsgs[i].message = burnMsgs[i].encode(); + cctpSignedMsgs[i].signature = emitterMessageTransmitter.sign(burnMsgs[i]); + } } - function registerChain( - uint16 chainId, - IWormhole wormholeContractAddress, - IMessageTransmitter messageTransmitterContractAddress, - IWormholeRelayer wormholeRelayerContractAddress, - uint256 fork - ) public { - wormholeContracts[chainId] = wormholeContractAddress; - messageTransmitterContracts[chainId] = messageTransmitterContractAddress; - wormholeRelayerContracts[chainId] = wormholeRelayerContractAddress; - forks[chainId] = fork; - chainIdFromFork[fork] = chainId; + for (uint16 i = 0; i < vaas.length; ++i) { + if (debugLogging) + console.log( + "Found VAA from chain %s emitted from %s", + vaas[i].emitterChainId, + vaas[i].emitterAddress.fromUniversalAddress() + ); + + genericRelay( + vaas[i], + vaas, + cctpSignedMsgs, + deliveryOverrides + ); } - - function vaaKeyMatchesVAA( - VaaKey memory vaaKey, - bytes memory signedVaa - ) internal view returns (bool) { - IWormhole.VM memory parsedVaa = getForkWormhole().parseVM(signedVaa); - return - (vaaKey.chainId == parsedVaa.emitterChainId) && - (vaaKey.emitterAddress == parsedVaa.emitterAddress) && - (vaaKey.sequence == parsedVaa.sequence); - } - - function cctpKeyMatchesCCTPMessage( - CCTPMessageLib.CCTPKey memory cctpKey, - CCTPMessageLib.CCTPMessage memory cctpMessage - ) internal pure returns (bool) { - (uint64 nonce,) = cctpMessage.message.asUint64(12); - (uint32 domain,) = cctpMessage.message.asUint32(4); - return - nonce == cctpKey.nonce && domain == cctpKey.domain; - } - - function relay(Vm.Log[] memory logs, bool debugLogging) public { - relay(logs, bytes(""), debugLogging); - } - - function relay( - Vm.Log[] memory logs, - bytes memory deliveryOverrides, - bool debugLogging - ) public { - IWormhole emitterWormhole = getForkWormhole(); - PublishedMessage[] memory pms = emitterWormhole.fetchPublishedMessages(logs); - if (debugLogging) - console.log( - "Found %s wormhole messages in logs from %s", - pms.length, - address(emitterWormhole) - ); - - IWormhole.VM[] memory vaas = new IWormhole.VM[](pms.length); - 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)) { - CctpTokenBurnMessage[] memory burnMsgs = - emitterMessageTransmitter.fetchBurnMessages(logs); - if (debugLogging) - console.log( - "Found %s circle messages in logs from %s", - burnMsgs.length, - address(emitterMessageTransmitter) - ); - - cctpSignedMsgs = new CCTPMessageLib.CCTPMessage[](burnMsgs.length); - for (uint256 i = 0; i < cctpSignedMsgs.length; ++i) { - cctpSignedMsgs[i].message = burnMsgs[i].encode(); - cctpSignedMsgs[i].signature = emitterMessageTransmitter.sign(burnMsgs[i]); + } + + function storeDelivery( + uint16 chainId, + uint64 deliveryVaaSequence, + bytes[] memory encodedVaas, + bytes memory encodedDeliveryVaa + ) internal { + bytes32 key = keccak256(abi.encodePacked(chainId, deliveryVaaSequence)); + pastEncodedVaas[key] = encodedVaas; + pastEncodedDeliveryVaa[key] = encodedDeliveryVaa; + } + + function genericRelay( + IWormhole.VM memory deliveryVaa, + IWormhole.VM[] memory allVaas, + CCTPMessageLib.CCTPMessage[] memory cctpMsgs, + bytes memory deliveryOverrides + ) internal { + uint currentFork = vm.activeFork(); + + (uint8 payloadId, ) = deliveryVaa.payload.asUint8Unchecked(0); + if (payloadId == PAYLOAD_ID_DELIVERY_INSTRUCTION) { + DeliveryInstruction memory instruction = + decodeDeliveryInstruction(deliveryVaa.payload); + + bytes[] memory additionalMessages = new bytes[](instruction.messageKeys.length); + for (uint8 i = 0; i < instruction.messageKeys.length; ++i) { + if (instruction.messageKeys[i].keyType == VAA_KEY_TYPE) { + (VaaKey memory vaaKey, ) = + decodeVaaKey(instruction.messageKeys[i].encodedKey, 0); + for (uint8 j = 0; j < allVaas.length; ++j) + if ( + (vaaKey.chainId == allVaas[j].emitterChainId) && + (vaaKey.emitterAddress == allVaas[j].emitterAddress) && + (vaaKey.sequence == allVaas[j].sequence) + ) { + additionalMessages[i] = allVaas[j].encode(); + break; } } - - for (uint16 i = 0; i < vaas.length; ++i) { - if (debugLogging) { - console.log( - "Found VAA from chain %s emitted from %s", - vaas[i].emitterChainId, - vaas[i].emitterAddress.fromUniversalAddress() - ); + else if (instruction.messageKeys[i].keyType == CCTP_KEY_TYPE) { + (CCTPMessageLib.CCTPKey memory key,) = + decodeCCTPKey(instruction.messageKeys[i].encodedKey, 0); + for (uint8 j = 0; j < cctpMsgs.length; ++j) + if (cctpKeyMatchesCCTPMessage(key, cctpMsgs[j])) { + additionalMessages[i] = abi.encode(cctpMsgs[j].message, cctpMsgs[j].signature); + break; } - - // if ( - // vaas[i].emitterAddress == - // wormholeRelayerContracts[chainId].toUniversalAddress() && - // (vaas[i].emitterChainId == chainId) - // ) { - // if (debugLogging) { - // console.log("Relaying VAA to chain %s", chainId); - // } - // //vm.selectFork(forks[chainIdOfWormholeAndGuardianUtilities]); - genericRelay( - encodedSignedVaas[i], - encodedSignedVaas, - cctpSignedMsgs, - vaas[i], - deliveryOverrides - ); - // } } + if (additionalMessages[i].length == 0) + revert("Additional Message not found"); + } + + EvmExecutionInfoV1 memory executionInfo = + decodeEvmExecutionInfoV1(instruction.encodedExecutionInfo); + + uint256 budget = executionInfo.gasLimit * + executionInfo.targetChainRefundPerGasUnused + + instruction.requestedReceiverValue + + instruction.extraReceiverValue; + + uint16 targetChain = instruction.targetChain; + + vm.selectFork(forks[targetChain]); + + vm.deal(address(this), budget); + + vm.recordLogs(); + bytes memory encodedDeliveryVaa = deliveryVaa.encode(); + getForkWormholeRelayer().deliver{value: budget}( + additionalMessages, + encodedDeliveryVaa, + payable(address(this)), + deliveryOverrides + ); + + storeDelivery( + deliveryVaa.emitterChainId, + deliveryVaa.sequence, + additionalMessages, + encodedDeliveryVaa + ); } - - function setInfo( - uint16 chainId, - uint64 deliveryVAASequence, - bytes[] memory encodedSignedVaas, - bytes memory encodedDeliveryVAA - ) internal { - bytes32 key = keccak256(abi.encodePacked(chainId, deliveryVAASequence)); - pastEncodedSignedVaas[key] = encodedSignedVaas; - pastEncodedDeliveryVAA[key] = encodedDeliveryVAA; - } - - function genericRelay( - bytes memory encodedDeliveryVAA, - bytes[] memory encodedSignedVaas, - CCTPMessageLib.CCTPMessage[] memory cctpMessages, - IWormhole.VM memory parsedDeliveryVAA, - bytes memory deliveryOverrides - ) internal { - uint currentFork = vm.activeFork(); - - (uint8 payloadId, ) = parsedDeliveryVAA.payload.asUint8Unchecked(0); - if (payloadId == 1) { - DeliveryInstruction memory instruction = decodeDeliveryInstruction( - parsedDeliveryVAA.payload - ); - - bytes[] memory encodedSignedVaasToBeDelivered = new bytes[]( - instruction.messageKeys.length - ); - - for (uint8 i = 0; i < instruction.messageKeys.length; i++) { - if (instruction.messageKeys[i].keyType == 1) { - // VaaKey - (VaaKey memory vaaKey, ) = decodeVaaKey( - instruction.messageKeys[i].encodedKey, - 0 - ); - for (uint8 j = 0; j < encodedSignedVaas.length; j++) { - if (vaaKeyMatchesVAA(vaaKey, encodedSignedVaas[j])) { - encodedSignedVaasToBeDelivered[i] = encodedSignedVaas[j]; - break; - } - } - } else if (instruction.messageKeys[i].keyType == 2) { - // CCTP Key - (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); - break; - } - } - } - } - - EvmExecutionInfoV1 memory executionInfo = decodeEvmExecutionInfoV1( - instruction.encodedExecutionInfo - ); - - uint256 budget = executionInfo.gasLimit * - executionInfo.targetChainRefundPerGasUnused + - instruction.requestedReceiverValue + - instruction.extraReceiverValue; - - uint16 targetChain = instruction.targetChain; - - vm.selectFork(forks[targetChain]); - - vm.deal(address(this), budget); - - vm.recordLogs(); - getForkWormholeRelayer().deliver{value: budget}( - encodedSignedVaasToBeDelivered, - encodedDeliveryVAA, - payable(address(this)), - deliveryOverrides - ); - - setInfo( - parsedDeliveryVAA.emitterChainId, - parsedDeliveryVAA.sequence, - encodedSignedVaasToBeDelivered, - encodedDeliveryVAA - ); - } else if (payloadId == 2) { - RedeliveryInstruction - memory instruction = decodeRedeliveryInstruction( - parsedDeliveryVAA.payload - ); - - DeliveryOverride memory deliveryOverride = DeliveryOverride({ - newExecutionInfo: instruction.newEncodedExecutionInfo, - newReceiverValue: instruction.newRequestedReceiverValue, - redeliveryHash: parsedDeliveryVAA.hash - }); - - EvmExecutionInfoV1 memory executionInfo = decodeEvmExecutionInfoV1( - instruction.newEncodedExecutionInfo - ); - uint256 budget = executionInfo.gasLimit * - executionInfo.targetChainRefundPerGasUnused + - instruction.newRequestedReceiverValue; - - bytes memory oldEncodedDeliveryVAA = getPastDeliveryVAA( - instruction.deliveryVaaKey.chainId, - instruction.deliveryVaaKey.sequence - ); - bytes[] memory oldEncodedSignedVaas = getPastEncodedSignedVaas( - instruction.deliveryVaaKey.chainId, - instruction.deliveryVaaKey.sequence - ); - - uint16 targetChain = decodeDeliveryInstruction( - getForkWormhole().parseVM(oldEncodedDeliveryVAA).payload - ).targetChain; - - vm.selectFork(forks[targetChain]); - getForkWormholeRelayer().deliver{value: budget}( - oldEncodedSignedVaas, - oldEncodedDeliveryVAA, - payable(address(this)), - encode(deliveryOverride) - ); - } - vm.selectFork(currentFork); + else if (payloadId == PAYLOAD_ID_REDELIVERY_INSTRUCTION) { + RedeliveryInstruction memory instruction = + decodeRedeliveryInstruction(deliveryVaa.payload); + + DeliveryOverride memory deliveryOverride = DeliveryOverride({ + newExecutionInfo: instruction.newEncodedExecutionInfo, + newReceiverValue: instruction.newRequestedReceiverValue, + redeliveryHash: deliveryVaa.hash + }); + + EvmExecutionInfoV1 memory executionInfo = + decodeEvmExecutionInfoV1(instruction.newEncodedExecutionInfo); + + uint256 budget = executionInfo.gasLimit * + executionInfo.targetChainRefundPerGasUnused + + instruction.newRequestedReceiverValue; + + bytes memory oldEncodedDeliveryVaa = getPastDeliveryVaa( + instruction.deliveryVaaKey.chainId, + instruction.deliveryVaaKey.sequence + ); + + bytes[] memory oldEncodedVaas = getPastEncodedVaas( + instruction.deliveryVaaKey.chainId, + instruction.deliveryVaaKey.sequence + ); + + uint16 targetChain = decodeDeliveryInstruction( + getForkWormhole().parseVM(oldEncodedDeliveryVaa).payload + ).targetChain; + + vm.selectFork(forks[targetChain]); + getForkWormholeRelayer().deliver{value: budget}( + oldEncodedVaas, + oldEncodedDeliveryVaa, + payable(address(this)), + encode(deliveryOverride) + ); } + vm.selectFork(currentFork); + } - receive() external payable {} + receive() external payable {} } diff --git a/src/testing/WormholeRelayerTest.sol b/src/testing/WormholeRelayerTest.sol index dd2fd13..5f7613d 100644 --- a/src/testing/WormholeRelayerTest.sol +++ b/src/testing/WormholeRelayerTest.sol @@ -49,6 +49,7 @@ abstract contract WormholeRelayerTest is Test { using WormholeOverride for IWormhole; using CctpOverride for IMessageTransmitter; using UsdcDealer for IUSDC; + using VaaEncoding for IWormhole.VM; /** * @dev required override to initialize active forks before each test @@ -183,9 +184,9 @@ abstract contract WormholeRelayerTest is Test { vm.recordLogs(); home.tokenBridge.attestToken(address(token), 0); - (, bytes memory attestation) = home.wormhole.sign( + bytes memory tokenAttestationVaa = home.wormhole.sign( home.wormhole.fetchPublishedMessages(vm.getRecordedLogs())[0] - ); + ).encode(); for (uint256 i = 0; i < activeForksList.length; ++i) { if (activeForksList[i] == home.chainId) { @@ -193,7 +194,7 @@ abstract contract WormholeRelayerTest is Test { } ActiveFork memory fork = activeForks[activeForksList[i]]; vm.selectFork(fork.fork); - fork.tokenBridge.createWrapped(attestation); + fork.tokenBridge.createWrapped(tokenAttestationVaa); } vm.selectFork(originalFork); diff --git a/test/WormholeRelayer/CCTPAndTokenBridgeBase.t.sol b/test/WormholeRelayer/CCTPAndTokenBridgeBase.t.sol index 9db9995..e343d35 100644 --- a/test/WormholeRelayer/CCTPAndTokenBridgeBase.t.sol +++ b/test/WormholeRelayer/CCTPAndTokenBridgeBase.t.sol @@ -5,7 +5,7 @@ import "wormhole-sdk/interfaces/token/IERC20.sol"; import "wormhole-sdk/testing/WormholeRelayerTest.sol"; contract CCTPAndTokenBridgeToy is CCTPAndTokenSender, CCTPAndTokenReceiver { - uint256 constant GAS_LIMIT = 250_000; + uint256 constant GAS_LIMIT = 400_000; constructor( address _wormholeRelayer, @@ -130,7 +130,7 @@ contract CCTPAndTokenBridgeToy is CCTPAndTokenSender, CCTPAndTokenReceiver { } } -contract WormholeSDKTest is WormholeRelayerBasicTest { +contract CCTPAndTokenBridgeTest is WormholeRelayerBasicTest { CCTPAndTokenBridgeToy CCTPAndTokenBridgeToySource; CCTPAndTokenBridgeToy CCTPAndTokenBridgeToyTarget; ERC20Mock USDCSource; diff --git a/test/WormholeRelayer/CCTPBase.t.sol b/test/WormholeRelayer/CCTPBase.t.sol index d9dbca9..25810db 100644 --- a/test/WormholeRelayer/CCTPBase.t.sol +++ b/test/WormholeRelayer/CCTPBase.t.sol @@ -5,7 +5,7 @@ import "wormhole-sdk/interfaces/token/IERC20.sol"; import "wormhole-sdk/testing/WormholeRelayerTest.sol"; contract CCTPToy is CCTPSender, CCTPReceiver { - uint256 constant GAS_LIMIT = 250_000; + uint256 constant GAS_LIMIT = 400_000; constructor( address _wormholeRelayer, @@ -82,7 +82,7 @@ contract CCTPToy is CCTPSender, CCTPReceiver { } } -contract WormholeSDKTest is WormholeRelayerBasicTest { +contract CCTPBaseTest is WormholeRelayerBasicTest { CCTPToy CCTPToySource; CCTPToy CCTPToyTarget; ERC20Mock USDCSource; diff --git a/test/WormholeRelayer/Fork.t.sol b/test/WormholeRelayer/Fork.t.sol index 4b420dd..99c13e9 100644 --- a/test/WormholeRelayer/Fork.t.sol +++ b/test/WormholeRelayer/Fork.t.sol @@ -43,7 +43,7 @@ contract TokenToy is TokenSender, TokenReceiver { address _wormhole ) TokenBase(_wormholeRelayer, _bridge, _wormhole) {} - uint256 constant GAS_LIMIT = 250_000; + uint256 constant GAS_LIMIT = 400_000; function quoteCrossChainDeposit( uint16 targetChain @@ -137,7 +137,7 @@ contract TokenToy is TokenSender, TokenReceiver { } } -contract WormholeSDKTest is WormholeRelayerBasicTest { +contract ForkTest is WormholeRelayerBasicTest { Toy toySource; Toy toyTarget; TokenToy tokenToySource;