wormhole-circle-integration/DESIGN.md

6.6 KiB

Wormhole Circle Integration

Objective

Integrate Wormhole's Generic-Messaging Protocol with Circle's Cross-Chain Transfer Protocol to facilitate composable cross-chain transfers of Circle-supported assets.

Background

The Circle Bridge allows users to send USDC and other Circle-supported assets cross chain by burning tokens on the source chain and minting the related token on the target chain (e.g. burn x amount of USDC on Ethereum and mint x amount of USDC on Avalanche).

Integrating Wormhole with Circle Bridge will permit users (xDapps) to transfer Circle-supported assets cross chain with an arbitrary message payload. This arbitrary payload can be used to disseminate instructions to the target contract to execute upon receiving minted tokens from the Circle Bridge.

Goals

Implement smart contracts to perform the following actions:

  • Interact with Circle Bridge to burn and mint Circle-supported assets.
  • Emit Wormhole messages containing information about cross-chain transfers for Circle-supported assets.
    • Allow integrators to send an arbitrary message payload to the receiving contract.
  • Mint tokens to a user-specified mintRecipient on the target chain.
  • Ensure that only WormholeCircleIntegration contracts can invoke the Circle Transmitter contract on the target chain to mint tokens to the mintRecipient.

Non-Goals

  • Automatically relay Circle-supported asset transfers to the target chain.
  • Automatically retrieve attestations from Circle's off-chain attestation service.
  • Support assets not supported by the Circle Bridge.

Detailed Design

To initiate a cross-chain transfer of Circle-supported assets, an integrator will invoke the transferTokensWithPayload method on the WormholeCircleIntegration contract. The transferTokensWithPayload method takes three arguments:

  • TransferParameters - See Structs section of this document.
  • batchId - ID for Wormhole message batching.
  • payload - Arbitrary message payload to be delivered to the target chain.

The transferTokensWithPayload method will then complete the following actions in order:

  1. Verify that the caller has provided enough native asset to pay the Wormhole protocol fee.
  2. Transfer the amount of specified token from the caller to the contract.
  3. Call the depositForBurnWithCaller method on the Circle Bridge to burn the token. This method takes an argument _destinationCaller which dictates who can complete the mint on the target chain. This argument is set to the target WormholeCircleIntegration contract address.
  4. Encode and send the DepositWithPayload message (see Payloads) via Wormhole.

Once the integrator has initiated a transfer (i.e. integrator submits transaction), they must fetch the attested Wormhole message and parse the transaction logs to locate a transfer message emitted by the Circle Bridge contract. Then the integrator must send a request to Circle's off-chain process with the transfer message to grab the attestation from the process's response (serialized EC signatures), which validates the token mint on the target chain. Integrators interacting with the WormholeCircleIntegration can streamline this process by writing a specialized off-chain relayer.

To complete the cross-chain transfer, the integrator invokes the redeemTokensWithPayload method on the target WormholeCircleIntegration contract, passing the RedeemParameters struct (see Structs) as an argument. The redeemTokensWithPayload method will complete the transfer by completing the following actions in order:

  1. Verify that the Wormhole message was attested
  2. Decode the Wormhole payload into the DepositWithPayload struct.
  3. Verify that the contract caller is the mintRecipient encoded in the DepositWithPayload message.
  4. Verify that message sender (fromAddress) is a registered WormholeCircleIntegration contract.
  5. Verify that the correct message pair (Circle transfer message and Wormhole message) was delivered to the contract by comparing the nonce, sourceDomain and targetDomain values encoded in both messages.
  6. Calls the Circle Transmitter contract method receiveMessage to mint tokens to the mintRecipient.
  7. Return the DepositWithPayload struct to the caller.

After successfully executing the redeemTokensWithPayload method, the caller will now have custody of the newly minted tokens. They can then execute any additional instructions that were encoded in the DepositWithPayload message.

API

function transferTokensWithPayload(TransferParameters transferParams, uint32 batchId, bytes payload)

function redeemTokensWithPayload(RedeemParameters redeemParams)

Governance API

function updateWormholeFinality(bytes attestedGovernanceMessage)

function registerEmitterAndDomain(bytes attestedGovernanceMessage)

function upgradeContract(bytes attestedGovernanceMessage)

function verifyGovernanceMessage(bytes attestedGovernanceMessage, uint8 action)

Structs

struct TransferParameters {
    // address of token to be minted (32 bytes for non-EVM chains)
    address token;
    // amount of token to be minted
    uint256 amount;
    // Wormhole chain ID of target blockchain
    uint16 targetChain;
    // Recipient of minted tokens on the target blockchain
    bytes32 mintRecipient;
}

struct RedeemParameters {
    // Wormhole message emitted containing encoded `DepositWithPayload` message
    bytes encodedWormholeMessage;
    // Circle message emitted by the Circle Bridge after burning Circle-support assets
    bytes circleBridgeMessage;
    // Circle attestation (serialized EC Signatures)
    bytes circleAttestation;
}

struct DepositWithPayload {
    bytes32 token;
    uint256 amount;
    uint32 sourceDomain;
    uint32 targetDomain;
    uint64 nonce;
    bytes32 fromAddress;
    bytes32 mintRecipient;
    bytes payload;
}

Payloads

DepositWithPayload:

// payloadID uint8 = 1;
uint8 payloadID;

// Circle-supported token address, zero-left-padded for addresses less than 32 bytes long
bytes32 token;

// token amount
uint256 amount;

// Circle source domain
uint32 sourceDomain;

// Circle target domain
uint32 targetDomain;

// Circle transfer nonce
uint64 nonce;

// address of the `WormholeCircleIntegration` caller
bytes32 fromAddress;

// address of the token mint recipient on the target chain
bytes32 mintRecipient;

// length of the arbitrary message payload passed by the caller
uint16 payloadLength;

// arbitrary message payload passed by the caller
bytes payload;