Skip to content

Commit

Permalink
evm: calldata ftw
Browse files Browse the repository at this point in the history
  • Loading branch information
a5-pickle committed Dec 19, 2023
1 parent 3f5bcc5 commit c43e1bf
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 155 deletions.
20 changes: 13 additions & 7 deletions evm/forge/tests/CircleIntegration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

import {IWormhole} from "src/interfaces/IWormhole.sol";
import {ICircleIntegration, RedeemParameters} from "src/interfaces/ICircleIntegration.sol";
import {ICircleIntegration} from "src/interfaces/ICircleIntegration.sol";

import {Utils} from "src/libraries/Utils.sol";
import {Deposit, WormholeCctpMessages} from "src/libraries/WormholeCctpMessages.sol";
Expand Down Expand Up @@ -264,7 +264,8 @@ contract CircleIntegrationTest is Test {
uint32 remoteDomain = 1;
uint16 emitterChain = 6;

RedeemParameters memory redeemParams = circleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams = circleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 1,
Expand Down Expand Up @@ -292,7 +293,8 @@ contract CircleIntegrationTest is Test {

(uint16 emitterChain, uint32 remoteDomain) = _registerEmitterAndDomain();

RedeemParameters memory redeemParams = circleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams = circleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 1,
Expand All @@ -317,7 +319,8 @@ contract CircleIntegrationTest is Test {

(uint16 emitterChain, uint32 remoteDomain) = _registerEmitterAndDomain();

RedeemParameters memory redeemParams = circleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams = circleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 1,
Expand All @@ -340,7 +343,8 @@ contract CircleIntegrationTest is Test {
function test_CannotRedeemTokensWithPayloadInvalidMessagePair() public {
(uint16 emitterChain, uint32 remoteDomain) = _registerEmitterAndDomain();

RedeemParameters memory redeemParams1 = circleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams1 = circleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 2,
Expand All @@ -353,7 +357,8 @@ contract CircleIntegrationTest is Test {
abi.encodePacked("Somebody set up us the bomb")
);

RedeemParameters memory redeemParams2 = circleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams2 = circleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 1,
Expand Down Expand Up @@ -393,7 +398,8 @@ contract CircleIntegrationTest is Test {
payload: abi.encodePacked("Somebody set up us the bomb")
});

RedeemParameters memory redeemParams = circleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams = circleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: expected.sourceDomain,
nonce: expected.nonce,
Expand Down
21 changes: 14 additions & 7 deletions evm/forge/tests/gas/CircleIntegrationComparison.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

import {IWormhole} from "src/interfaces/IWormhole.sol";
import {ICircleIntegration, RedeemParameters} from "src/interfaces/ICircleIntegration.sol";
import {ICircleIntegration} from "src/interfaces/ICircleIntegration.sol";

import {Utils} from "src/libraries/Utils.sol";
import {Deposit, WormholeCctpMessages} from "src/libraries/WormholeCctpMessages.sol";
Expand Down Expand Up @@ -211,7 +211,8 @@ contract CircleIntegrationComparison is Test {

(uint16 emitterChain, uint32 remoteDomain) = _registerEmitterAndDomain(circleIntegration);

RedeemParameters memory redeemParams = circleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams = circleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 1,
Expand All @@ -228,7 +229,9 @@ contract CircleIntegrationComparison is Test {
address(inheritedContract).toUniversalAddress() // destinationCaller
);

inheritedContract.redeemUsdc(redeemParams);
inheritedContract.redeemUsdc(
redeemParams.cctpMessage, redeemParams.cctpAttestation, redeemParams.encodedVaa
);
}

function test_Composed__RedeemUsdc(uint256 amount, bytes32 fromAddress, bytes32 data) public {
Expand All @@ -238,7 +241,8 @@ contract CircleIntegrationComparison is Test {

(uint16 emitterChain, uint32 remoteDomain) = _registerEmitterAndDomain(circleIntegration);

RedeemParameters memory redeemParams = circleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams = circleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 1,
Expand All @@ -263,7 +267,8 @@ contract CircleIntegrationComparison is Test {

(uint16 emitterChain, uint32 remoteDomain) = _registerEmitterAndDomain(circleIntegration);

RedeemParameters memory redeemParams = circleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams = circleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 1,
Expand All @@ -289,7 +294,8 @@ contract CircleIntegrationComparison is Test {
(uint16 emitterChain, uint32 remoteDomain) =
_registerEmitterAndDomain(forkedCircleIntegration);

RedeemParameters memory redeemParams = forkedCircleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams = forkedCircleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 1,
Expand Down Expand Up @@ -317,7 +323,8 @@ contract CircleIntegrationComparison is Test {
(uint16 emitterChain, uint32 remoteDomain) =
_registerEmitterAndDomain(forkedCircleIntegration);

RedeemParameters memory redeemParams = forkedCircleIntegration.craftRedeemParameters(
ICircleIntegration.RedeemParameters memory redeemParams = forkedCircleIntegration
.craftRedeemParameters(
CraftedCctpMessageParams({
remoteDomain: remoteDomain,
nonce: 2 ** 64 - 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {IWormhole} from "src/interfaces/IWormhole.sol";
import {BytesParsing} from "src/libraries/BytesParsing.sol";
import {Utils} from "src/libraries/Utils.sol";
import {Deposit, WormholeCctpMessages} from "src/libraries/WormholeCctpMessages.sol";
import {RedeemParameters} from "src/libraries/WormholeCctpStructs.sol";

import "forge-std/Test.sol";
import "forge-std/console.sol";
Expand Down Expand Up @@ -246,7 +245,7 @@ library CircleIntegrationOverride {
bytes memory payload,
bytes32 messageSender,
bytes32 destinationCaller
) internal view returns (RedeemParameters memory params) {
) internal view returns (ICircleIntegration.RedeemParameters memory params) {
params = _craftRedeemParameters(
circleIntegration,
cctpParams,
Expand All @@ -265,7 +264,7 @@ library CircleIntegrationOverride {
bytes32 fromAddress,
bytes memory payload,
bytes32 messageSender
) internal view returns (RedeemParameters memory params) {
) internal view returns (ICircleIntegration.RedeemParameters memory params) {
params = _craftRedeemParameters(
circleIntegration,
cctpParams,
Expand All @@ -283,7 +282,7 @@ library CircleIntegrationOverride {
CraftedVaaParams memory vaaParams,
bytes32 fromAddress,
bytes memory payload
) internal view returns (RedeemParameters memory params) {
) internal view returns (ICircleIntegration.RedeemParameters memory params) {
params = _craftRedeemParameters(
circleIntegration,
cctpParams,
Expand Down Expand Up @@ -316,7 +315,7 @@ library CircleIntegrationOverride {
bytes memory payload,
bytes32 messageSender,
bytes32 destinationCaller
) private view returns (RedeemParameters memory params) {
) private view returns (ICircleIntegration.RedeemParameters memory params) {
CctpTokenBurnMessage memory burnMsg;
(burnMsg, params.cctpMessage, params.cctpAttestation) = _craftCctpTokenBurnMessage(
circleIntegration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {ICircleIntegration} from "src/interfaces/ICircleIntegration.sol";
import {IWormhole} from "src/interfaces/IWormhole.sol";

import {Utils} from "src/libraries/Utils.sol";
import {RedeemParameters} from "src/libraries/WormholeCctpStructs.sol";

contract ComposingWithCircleIntegration {
using SafeERC20 for IERC20;
Expand Down Expand Up @@ -50,7 +49,7 @@ contract ComposingWithCircleIntegration {
);
}

function redeemUsdc(RedeemParameters calldata params) public {
function redeemUsdc(ICircleIntegration.RedeemParameters calldata params) public {
ICircleIntegration.DepositWithPayload memory deposit =
_circleIntegration.redeemTokensWithPayload(params);

Expand Down
9 changes: 6 additions & 3 deletions evm/forge/tests/integrations/InheritingWormholeCctp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ pragma solidity ^0.8.19;
import {IWormhole} from "src/interfaces/IWormhole.sol";

import {Utils} from "src/libraries/Utils.sol";
import {RedeemParameters} from "src/libraries/WormholeCctpStructs.sol";

import {WormholeCctp} from "src/contracts/WormholeCctp.sol";

Expand Down Expand Up @@ -44,8 +43,12 @@ contract InheritingWormholeCctp is WormholeCctp {
);
}

function redeemUsdc(RedeemParameters calldata params) public {
verifyVaaAndMint(params);
function redeemUsdc(
bytes calldata encodedCctpMessage,
bytes calldata cctpAttestation,
bytes calldata encodedVaa
) public {
verifyVaaAndMint(encodedCctpMessage, cctpAttestation, encodedVaa);
}

function myBffDomain() public pure returns (uint32 domain) {
Expand Down
4 changes: 2 additions & 2 deletions evm/src/contracts/CircleIntegration/Logic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {ICircleIntegration} from "src/interfaces/ICircleIntegration.sol";

import {Utils} from "src/libraries/Utils.sol";
import {Deposit, WormholeCctpMessages} from "src/libraries/WormholeCctpMessages.sol";
import {RedeemParameters} from "src/libraries/WormholeCctpStructs.sol";

import {Governance} from "./Governance.sol";
import {
Expand Down Expand Up @@ -68,7 +67,8 @@ abstract contract Logic is ICircleIntegration, Governance {
bytes32 emitter;
bytes32 vaaHash;
Deposit memory depositHeader;
(chain, emitter, vaaHash, depositHeader, deposit.payload) = verifyVaaAndMintLegacy(params);
(chain, emitter, vaaHash, depositHeader, deposit.payload) =
verifyVaaAndMintLegacy(params.cctpMessage, params.cctpAttestation, params.encodedVaa);

// NOTE: Reverting with Error(string) comes from the old implementation, so we preserve it.
require(emitter != 0 && emitter == getRegisteredEmitters()[chain], "unknown emitter");
Expand Down
72 changes: 50 additions & 22 deletions evm/src/contracts/WormholeCctp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {ITokenMinter} from "src/interfaces/ITokenMinter.sol";

import {Utils} from "src/libraries/Utils.sol";
import {Deposit, WormholeCctpMessages} from "src/libraries/WormholeCctpMessages.sol";
import {RedeemParameters, RedeemedDeposit} from "src/libraries/WormholeCctpStructs.sol";

abstract contract WormholeCctp {
using Utils for address;
Expand All @@ -21,7 +20,19 @@ abstract contract WormholeCctp {

error InvalidVaa();
error CallerNotMintRecipient(bytes32, bytes32);
error CctpVaaMismatch();
error CctpVaaMismatch(uint32, uint32, uint64);

struct RedeemedDeposit {
bytes32 token;
uint256 amount;
bytes32 burnSource;
uint32 vaaTimestamp;
uint32 vaaNonce;
uint16 emitterChain;
bytes32 emitterAddress;
uint64 vaaSequence;
bytes32 vaaHash;
}

/**
* @notice Emitted when Circle-supported assets have been minted to the mintRecipient
Expand Down Expand Up @@ -95,10 +106,11 @@ abstract contract WormholeCctp {
);
}

function verifyVaaAndMint(RedeemParameters calldata params)
internal
returns (RedeemedDeposit memory redeemed, bytes memory payload)
{
function verifyVaaAndMint(
bytes calldata encodedCctpMessage,
bytes calldata cctpAttestation,
bytes calldata encodedVaa
) internal returns (RedeemedDeposit memory redeemed, bytes memory payload) {
Deposit memory deposit;
(
redeemed.vaaTimestamp,
Expand All @@ -110,7 +122,9 @@ abstract contract WormholeCctp {
deposit,
payload
) = _verifyVaaAndMint(
params,
encodedCctpMessage,
cctpAttestation,
encodedVaa,
true // revertCustomErrors
);

Expand All @@ -120,7 +134,11 @@ abstract contract WormholeCctp {
redeemed.burnSource = deposit.burnSource;
}

function verifyVaaAndMintLegacy(RedeemParameters calldata params)
function verifyVaaAndMintLegacy(
bytes calldata encodedCctpMessage,
bytes calldata cctpAttestation,
bytes calldata encodedVaa
)
internal
returns (
uint16 emitterChain,
Expand All @@ -131,7 +149,9 @@ abstract contract WormholeCctp {
)
{
(,, emitterChain, emitterAddress,, vaaHash, deposit, payload) = _verifyVaaAndMint(
params,
encodedCctpMessage,
cctpAttestation,
encodedVaa,
false // revertCustomErrors
);
}
Expand All @@ -148,7 +168,12 @@ abstract contract WormholeCctp {

// private

function _verifyVaaAndMint(RedeemParameters calldata params, bool revertCustomErrors)
function _verifyVaaAndMint(
bytes calldata encodedCctpMessage,
bytes calldata cctpAttestation,
bytes calldata encodedVaa,
bool revertCustomErrors
)
private
returns (
uint32 vaaTimestamp,
Expand All @@ -163,7 +188,7 @@ abstract contract WormholeCctp {
{
// Parse and Verify VAA.
(IWormhole.VM memory vaa, bool valid, string memory reason) =
_wormhole.parseAndVerifyVM(params.encodedVaa);
_wormhole.parseAndVerifyVM(encodedVaa);

// Confirm that the core layer verified the message.
if (!valid) {
Expand Down Expand Up @@ -191,36 +216,39 @@ abstract contract WormholeCctp {

// Confirm that the caller passed the correct message pair.
{
bytes memory encodedCctpMessage = params.cctpMessage;

uint32 sourceDomain;
uint32 targetDomain;
uint64 nonce;

// NOTE: First four bytes is the CCTP message version.
assembly ("memory-safe") {
// source domain is 4 bytes after the version
sourceDomain := mload(add(encodedCctpMessage, 8))
// target domain is 4 bytes after the source domain
targetDomain := mload(add(encodedCctpMessage, 12))
// nonce is 8 bytes after the target version
nonce := mload(add(encodedCctpMessage, 20))
// 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
// YUL block will already perform big-endian masking.

// Source domain is bytes 4..8, so shift 24 bytes to the right.
sourceDomain := shr(192, ptr)
// Target domain is bytes 8..12, so shift 20 bytes to the right.
targetDomain := shr(160, ptr)
// Nonce is bytes 12..20, so shift 12 bytes to the right.
nonce := shr(96, ptr)
}

if (
deposit.sourceCctpDomain != sourceDomain || deposit.targetCctpDomain != targetDomain
|| deposit.cctpNonce != nonce
) {
if (revertCustomErrors) {
revert CctpVaaMismatch();
revert CctpVaaMismatch(sourceDomain, targetDomain, nonce);
} else {
_revertBuiltIn("invalid message pair");
}
}
}

// Call the circle bridge to mint tokens to the recipient.
_messageTransmitter.receiveMessage(params.cctpMessage, params.cctpAttestation);
_messageTransmitter.receiveMessage(encodedCctpMessage, cctpAttestation);

// Overwrite the token address in the deposit with the local token address. We should trust
// that this getter will not return the zero address because the Token Minter will have
Expand Down
Loading

0 comments on commit c43e1bf

Please sign in to comment.