Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CCTP refactor and message parsing unification #73

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/RawDispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Contracts using this base class have to override the associated virtual function

Of course, integrations have to actually make use of these custom dispatch functions to reap their benefits. To this end, contracts using the RawDispatcher pattern/base call should come with two additional "SDKs":
1. For on-chain integrations: A Solidity integrator `library` that fills the role of what is otherwise provided by an `interface`. That is, a set of encoding and decoding functions that mirror the contract's ABI but implement the custom call format of the contract decoding its returned `bytes` under the hood.
2. For off-chain integrations: A Typescript analog of the integrator library. The [layouting mechanism in the Wormhole Typescript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/base/src/utils/layout) offers an easy, declarative way to specify such custom encodings. It also provides [definitions of common types](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/layout-items) and various examples of its use can be found in [the protocols defined in the SDK itself](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/protocols) (e.g. [TokenBridge Messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts), [WormholeRelayer Messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/relayer/relayerLayout.ts), [CCTP messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/circleBridge/circleBridgeLayout.ts)) or strewn throughout the various example repos e.g. [example-swap-layer](https://github.com/wormhole-foundation/example-swap-layer/blob/main/evm/ts-sdk/src/layout.ts) or [example-native-token-transfers](https://github.com/wormhole-foundation/example-native-token-transfers/tree/main/sdk/definitions/src/layouts). (A proper, standalone tutorial is sadly still outstanding at the time of writing.)
2. For off-chain integrations: A Typescript analog of the integrator library. The [layouting package](https://www.npmjs.com/package/binary-layout) offers an easy, declarative way to specify such custom encodings. It is also used in [the Wormhole Typescript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/base/src/utils/layout.ts) to [define common types](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/layout-items) and various other layout examples can be found in [the protocols defined within the SDK itself](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/protocols) (e.g. [TokenBridge Messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts), [WormholeRelayer Messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/relayer/relayerLayout.ts), [CCTP messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/circleBridge/circleBridgeLayout.ts)) or strewn throughout the various example repos e.g. [example-swap-layer](https://github.com/wormhole-foundation/example-swap-layer/blob/main/evm/ts-sdk/src/layout.ts) or [example-native-token-transfers](https://github.com/wormhole-foundation/example-native-token-transfers/tree/main/sdk/definitions/src/layouts).

### Limitations

Expand Down
4 changes: 0 additions & 4 deletions docs/Testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ The `WormholeCctpSimulator` contract can be deployed to simulate a virtual `Worm

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.
Expand Down
4 changes: 4 additions & 0 deletions src/Utils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ function fromUniversalAddress(bytes32 universalAddr) pure returns (address addr)
}
}

function minSigsForQuorum(uint numGuardians) pure returns (uint) { unchecked {
return numGuardians * 2 / 3 + 1;
}}

/**
* Reverts with a given buffer data.
* Meant to be used to easily bubble up errors from low level calls when they fail.
Expand Down
74 changes: 26 additions & 48 deletions src/WormholeCctpTokenMessenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import {IMessageTransmitter} from "wormhole-sdk/interfaces/cctp/IMessageTransmit
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 {toUniversalAddress,
eagerAnd,
eagerOr} from "wormhole-sdk/Utils.sol";
import {WormholeCctpMessageLib} from "wormhole-sdk/libraries/WormholeCctpMessages.sol";
import {CONSISTENCY_LEVEL_FINALIZED} from "wormhole-sdk/constants/ConsistencyLevel.sol";

/**
Expand Down Expand Up @@ -37,7 +39,7 @@ abstract contract WormholeCctpTokenMessenger {
/// @dev The emitter of the VAA must match the expected emitter.
error UnexpectedEmitter(bytes32, bytes32);

/// @dev Wormhole Core Bridge contract address.
/// @dev Wormhole Core Bridge contract address.
IWormhole immutable _wormhole;

/// @dev Wormhole Chain ID. NOTE: This is NOT the EVM chain ID.
Expand Down Expand Up @@ -104,7 +106,7 @@ abstract contract WormholeCctpTokenMessenger {
// Publish deposit message via Wormhole Core Bridge.
wormholeSequence = _wormhole.publishMessage{value: wormholeFee}(
wormholeNonce,
WormholeCctpMessages.encodeDeposit(
WormholeCctpMessageLib.encodeDeposit(
token.toUniversalAddress(),
amount,
_localCctpDomain, // sourceCctpDomain
Expand All @@ -114,7 +116,7 @@ abstract contract WormholeCctpTokenMessenger {
mintRecipient,
payload
),
CONSISTENCY_LEVEL_FINALIZED
CONSISTENCY_LEVEL_FINALIZED
);
}

Expand All @@ -138,7 +140,7 @@ abstract contract WormholeCctpTokenMessenger {
bytes memory payload
) {
// First parse and verify VAA.
vaa = _parseAndVerifyVaa( encodedVaa, true /*revertCustomErrors*/);
vaa = _parseAndVerifyVaa(encodedVaa);

// Decode the deposit message so we can match the Wormhole message with the CCTP message.
uint32 sourceCctpDomain;
Expand All @@ -153,7 +155,7 @@ abstract contract WormholeCctpTokenMessenger {
burnSource,
mintRecipient,
payload
) = WormholeCctpMessages.decodeDeposit(vaa.payload);
) = WormholeCctpMessageLib.decodeDepositMem(vaa.payload);

// Finally reconcile messages and mint tokens to the mint recipient.
token = _matchMessagesAndMint(
Expand All @@ -162,8 +164,7 @@ abstract contract WormholeCctpTokenMessenger {
sourceCctpDomain,
destinationCctpDomain,
cctpNonce,
token,
true // revertCustomErrors
token
);
}

Expand Down Expand Up @@ -191,7 +192,7 @@ abstract contract WormholeCctpTokenMessenger {
bytes memory payload
) {
// First parse and verify VAA.
vaa = _parseAndVerifyVaa(encodedVaa, false /*revertCustomErrors*/);
vaa = _parseAndVerifyVaa(encodedVaa);

// Decode the deposit message so we can match the Wormhole message with the CCTP message.
(
Expand All @@ -203,7 +204,7 @@ abstract contract WormholeCctpTokenMessenger {
burnSource,
mintRecipient,
payload
) = WormholeCctpMessages.decodeDeposit(vaa.payload);
) = WormholeCctpMessageLib.decodeDepositMem(vaa.payload);

// Finally reconcile messages and mint tokens to the mint recipient.
token = _matchMessagesAndMint(
Expand All @@ -212,8 +213,7 @@ abstract contract WormholeCctpTokenMessenger {
sourceCctpDomain,
destinationCctpDomain,
cctpNonce,
token,
false // revertCustomErrors
token
);
}

Expand All @@ -238,37 +238,20 @@ abstract contract WormholeCctpTokenMessenger {
* NOTE: Reverts with `UnexpectedEmitter(bytes32, bytes32)`.
*/
function requireEmitter(IWormhole.VM memory vaa, bytes32 expectedEmitter) internal pure {
if (expectedEmitter != 0 && vaa.emitterAddress != expectedEmitter)
if (eagerAnd(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
// ----- private methods -----

function _parseAndVerifyVaa(
bytes calldata encodedVaa,
bool revertCustomErrors
bytes calldata encodedVaa
) 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);
}
(vaa, valid, ) = _wormhole.parseAndVerifyVM(encodedVaa);

if (!valid)
revert InvalidVaa();
}

function _matchMessagesAndMint(
Expand All @@ -277,8 +260,7 @@ abstract contract WormholeCctpTokenMessenger {
uint32 vaaSourceCctpDomain,
uint32 vaaDestinationCctpDomain,
uint64 vaaCctpNonce,
bytes32 burnToken,
bool revertCustomErrors
bytes32 burnToken
) private returns (bytes32 mintToken) {
// Confirm that the caller passed the correct message pair.
{
Expand All @@ -301,16 +283,12 @@ abstract contract WormholeCctpTokenMessenger {
nonce := shr(96, ptr)
}

if (
vaaSourceCctpDomain != sourceDomain ||
vaaDestinationCctpDomain != destinationDomain||
//avoid short-circuiting (more gas and bytecode efficient)
if (eagerOr(
eagerOr(vaaSourceCctpDomain != sourceDomain, vaaDestinationCctpDomain != destinationDomain),
vaaCctpNonce != nonce
) {
if (revertCustomErrors)
revert CctpVaaMismatch(sourceDomain, destinationDomain, nonce);
else
require(false, "invalid message pair");
}
))
revert CctpVaaMismatch(sourceDomain, destinationDomain, nonce);
}

// Call the circle bridge to mint tokens to the recipient.
Expand Down
Loading
Loading