solana: fix after cctp upgrade; uptick 0.1.0-alpha.1

This commit is contained in:
A5 Pickle 2024-02-07 15:56:18 -06:00
parent 0fdd3f0ecf
commit 687cf29a29
No known key found for this signature in database
GPG Key ID: DD6C727938DE8E65
26 changed files with 6549 additions and 7893 deletions

4
solana/Cargo.lock generated
View File

@ -2377,7 +2377,7 @@ dependencies = [
[[package]]
name = "wormhole-cctp-solana"
version = "0.0.1-alpha.9"
version = "0.1.0-alpha.1"
dependencies = [
"anchor-lang",
"anchor-spl",
@ -2393,7 +2393,7 @@ dependencies = [
[[package]]
name = "wormhole-circle-integration-solana"
version = "0.0.1-alpha.9"
version = "0.1.0-alpha.1"
dependencies = [
"ahash 0.8.6",
"anchor-lang",

View File

@ -7,7 +7,7 @@ resolver = "2"
[workspace.package]
edition = "2021"
version = "0.0.1-alpha.9"
version = "0.1.0-alpha.1"
authors = ["Wormhole Contributors"]
license = "Apache-2.0"
homepage = "https://wormhole.com"

View File

@ -1,17 +1,15 @@
use anchor_lang::prelude::*;
/// Account context to invoke [receive_token_messenger_minter_message].
#[derive(Accounts)]
pub struct ReceiveTokenMessengerMinterMessage<'info> {
/// Mutable signer. Transaction payer.
#[account(mut, signer)]
pub payer: AccountInfo<'info>,
/// Signer. Specific caller, which must be encoded as the destination caller.
#[account(signer)]
pub caller: AccountInfo<'info>,
/// Seeds must be \["message_transmitter_authority"\] (CCTP Message Transmitter program).
/// Seeds must be \["message_transmitter_authority"\, token_messenger_minter_program] (CCTP
/// Message Transmitter program).
pub message_transmitter_authority: AccountInfo<'info>,
/// Seeds must be \["message_transmitter"\] (CCTP Message Transmitter program).
@ -19,7 +17,6 @@ pub struct ReceiveTokenMessengerMinterMessage<'info> {
/// Mutable. Seeds must be \["used_nonces", remote_domain.to_string(), first_nonce.to_string()\]
/// (CCTP Message Transmitter program).
#[account(mut)]
pub used_nonces: AccountInfo<'info>,
/// CCTP Token Messenger Minter program.
@ -27,6 +24,11 @@ pub struct ReceiveTokenMessengerMinterMessage<'info> {
pub system_program: AccountInfo<'info>,
/// Seeds must be \["__event_authority"\] (CCTP Message Transmitter program)).
pub message_transmitter_event_authority: AccountInfo<'info>,
pub message_transmitter_program: AccountInfo<'info>,
// The following accounts are expected to be passed in as remaining accounts. These accounts are
// meant for the Token Messenger Minter program because the Message Transmitter program performs
// CPI on this program so it can mint tokens.
@ -45,7 +47,6 @@ pub struct ReceiveTokenMessengerMinterMessage<'info> {
pub token_minter: AccountInfo<'info>,
/// Mutable. Seeds must be \["local_token", mint\] (CCTP Token Messenger Minter program).
#[account(mut)]
pub local_token: AccountInfo<'info>,
/// Seeds must be \["token_pair", remote_domain.to_string(), remote_token_address\] (CCTP Token
@ -54,14 +55,15 @@ pub struct ReceiveTokenMessengerMinterMessage<'info> {
/// Mutable. Mint recipient token account, which must be encoded as the mint recipient in the
/// CCTP mesage.
#[account(mut)]
pub mint_recipient: AccountInfo<'info>,
/// Mutable. Seeds must be \["custody", mint\] (CCTP Token Messenger Minter program).
#[account(mut)]
pub custody_token: AccountInfo<'info>,
pub token_program: AccountInfo<'info>,
/// Seeds must be \["__event_authority"\] (CCTP Token Messenger Minter program).
pub token_messenger_minter_event_authority: AccountInfo<'info>,
}
/// Method to call the receive message instruction on the CCTP Message Transmitter program, specific
@ -86,3 +88,53 @@ pub fn receive_token_messenger_minter_message<'info>(
)
.map_err(Into::into)
}
impl<'info> ToAccountMetas for ReceiveTokenMessengerMinterMessage<'info> {
fn to_account_metas(&self, _is_signer: Option<bool>) -> Vec<AccountMeta> {
vec![
AccountMeta::new(self.payer.key(), true),
AccountMeta::new_readonly(self.caller.key(), true),
AccountMeta::new_readonly(self.message_transmitter_authority.key(), false),
AccountMeta::new_readonly(self.message_transmitter_config.key(), false),
AccountMeta::new(self.used_nonces.key(), false),
AccountMeta::new_readonly(self.token_messenger_minter_program.key(), false),
AccountMeta::new_readonly(self.system_program.key(), false),
AccountMeta::new_readonly(self.message_transmitter_event_authority.key(), false),
AccountMeta::new_readonly(self.message_transmitter_program.key(), false),
AccountMeta::new_readonly(self.token_messenger.key(), false),
AccountMeta::new_readonly(self.remote_token_messenger.key(), false),
AccountMeta::new_readonly(self.token_minter.key(), false),
AccountMeta::new(self.local_token.key(), false),
AccountMeta::new_readonly(self.token_pair.key(), false),
AccountMeta::new(self.mint_recipient.key(), false),
AccountMeta::new(self.custody_token.key(), false),
AccountMeta::new_readonly(self.token_program.key(), false),
AccountMeta::new_readonly(self.token_messenger_minter_event_authority.key(), false),
AccountMeta::new_readonly(self.token_messenger_minter_program.key(), false),
]
}
}
impl<'info> ToAccountInfos<'info> for ReceiveTokenMessengerMinterMessage<'info> {
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![
self.payer.clone(),
self.caller.clone(),
self.message_transmitter_authority.clone(),
self.message_transmitter_config.clone(),
self.used_nonces.clone(),
self.token_messenger_minter_program.clone(),
self.system_program.clone(),
self.message_transmitter_event_authority.clone(),
self.token_messenger.clone(),
self.remote_token_messenger.clone(),
self.token_minter.clone(),
self.local_token.clone(),
self.token_pair.clone(),
self.mint_recipient.clone(),
self.custody_token.clone(),
self.token_program.clone(),
self.token_messenger_minter_event_authority.clone(),
]
}
}

View File

@ -13,7 +13,6 @@ pub struct MessageTransmitterConfig {
pub enabled_attesters: Vec<[u8; 32]>,
pub max_message_body_size: u64,
pub next_available_nonce: u64,
pub authority_bump: u8,
}
impl anchor_lang::Discriminator for MessageTransmitterConfig {

View File

@ -1,21 +1,23 @@
use anchor_lang::prelude::*;
/// Account context to invoke [deposit_for_burn_with_caller].
#[derive(Accounts)]
pub struct DepositForBurnWithCaller<'info> {
/// Signer. This account must be the owner of `src_token`.
#[account(signer)]
pub src_token_owner: AccountInfo<'info>,
/// Signer. This account must be the owner of `burn_token`.
//#[account(signer)]
pub burn_token_owner: AccountInfo<'info>,
//#[account(mut, signer)]
pub payer: AccountInfo<'info>,
/// Seeds must be \["sender_authority"\] (CCTP Token Messenger Minter program).
pub token_messenger_minter_sender_authority: AccountInfo<'info>,
/// Mutable. This token account must be owned by `src_token_owner`.
#[account(mut)]
pub src_token: AccountInfo<'info>,
/// Mutable. This token account must be owned by `burn_token_owner`.
//#[account(mut)]
pub burn_token: AccountInfo<'info>,
/// Mutable. Seeds must be \["message_transmitter"\] (CCTP Message Transmitter program).
#[account(mut)]
//#[account(mut)]
pub message_transmitter_config: AccountInfo<'info>,
/// Seeds must be \["token_messenger"\] (CCTP Token Messenger Minter program).
@ -29,13 +31,16 @@ pub struct DepositForBurnWithCaller<'info> {
pub token_minter: AccountInfo<'info>,
/// Mutable. Seeds must be \["local_token", mint\] (CCTP Token Messenger Minter program).
#[account(mut)]
//#[account(mut)]
pub local_token: AccountInfo<'info>,
/// Mutable. Mint to be burned via CCTP.
#[account(mut)]
//#[account(mut)]
pub mint: AccountInfo<'info>,
//#[account(mut, signer)]
pub cctp_message: AccountInfo<'info>,
/// CCTP Message Transmitter program.
pub message_transmitter_program: AccountInfo<'info>,
@ -43,6 +48,11 @@ pub struct DepositForBurnWithCaller<'info> {
pub token_messenger_minter_program: AccountInfo<'info>,
pub token_program: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
/// Seeds must be \["__event_authority"\] (CCTP Token Messenger Minter program).
pub event_authority: AccountInfo<'info>,
}
/// Parameters to invoke [deposit_for_burn_with_caller].
@ -91,3 +101,49 @@ pub fn deposit_for_burn_with_caller<'info>(
)
.map_err(Into::into)
}
impl<'info> ToAccountMetas for DepositForBurnWithCaller<'info> {
fn to_account_metas(&self, _is_signer: Option<bool>) -> Vec<AccountMeta> {
vec![
AccountMeta::new_readonly(self.burn_token_owner.key(), true),
AccountMeta::new(self.payer.key(), true),
AccountMeta::new_readonly(self.token_messenger_minter_sender_authority.key(), false),
AccountMeta::new(self.burn_token.key(), false),
AccountMeta::new(self.message_transmitter_config.key(), false),
AccountMeta::new_readonly(self.token_messenger.key(), false),
AccountMeta::new_readonly(self.remote_token_messenger.key(), false),
AccountMeta::new_readonly(self.token_minter.key(), false),
AccountMeta::new(self.local_token.key(), false),
AccountMeta::new(self.mint.key(), false),
AccountMeta::new(self.cctp_message.key(), true),
AccountMeta::new_readonly(self.message_transmitter_program.key(), false),
AccountMeta::new_readonly(self.token_messenger_minter_program.key(), false),
AccountMeta::new_readonly(self.token_program.key(), false),
AccountMeta::new_readonly(self.system_program.key(), false),
AccountMeta::new_readonly(self.event_authority.key(), false),
AccountMeta::new_readonly(self.token_messenger_minter_program.key(), false),
]
}
}
impl<'info> ToAccountInfos<'info> for DepositForBurnWithCaller<'info> {
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![
self.burn_token_owner.clone(),
self.payer.clone(),
self.token_messenger_minter_sender_authority.clone(),
self.burn_token.clone(),
self.message_transmitter_config.clone(),
self.token_messenger.clone(),
self.remote_token_messenger.clone(),
self.token_minter.clone(),
self.local_token.clone(),
self.mint.clone(),
self.cctp_message.clone(),
self.message_transmitter_program.clone(),
self.token_program.clone(),
self.system_program.clone(),
self.event_authority.clone(),
]
}
}

View File

@ -7,8 +7,8 @@ pub struct LocalToken {
pub burn_limit_per_message: u64,
pub messages_sent: u64,
pub messages_received: u64,
pub amount_sent: u64,
pub amount_received: u64,
pub amount_sent: u128,
pub amount_received: u128,
pub bump: u8,
pub custody_bump: u8,
}

View File

@ -78,7 +78,7 @@ where
let token_address = cctp_ctx.accounts.mint.key.to_bytes();
let burn_source = burn_source
.unwrap_or(cctp_ctx.accounts.src_token.key())
.unwrap_or(cctp_ctx.accounts.burn_token.key())
.to_bytes();
// We want to make this call as early as possible because the deposit for burn

View File

@ -73,13 +73,16 @@ pub struct RedeemTokensWithPayload<'info> {
message_transmitter_authority: UncheckedAccount<'info>,
/// CHECK: Seeds must be \["message_transmitter"\] (CCTP Message Transmitter program).
message_transmitter_config: AccountInfo<'info>,
message_transmitter_config: UncheckedAccount<'info>,
/// CHECK: Mutable. Seeds must be \["used_nonces", remote_domain.to_string(),
/// first_nonce.to_string()\] (CCTP Message Transmitter program).
#[account(mut)]
used_nonces: UncheckedAccount<'info>,
/// CHECK: Seeds must be \["__event_authority"\] (CCTP Message Transmitter program).
message_transmitter_event_authority: UncheckedAccount<'info>,
/// CHECK: Seeds must be \["token_messenger"\] (CCTP Token Messenger Minter program).
token_messenger: UncheckedAccount<'info>,
@ -107,11 +110,14 @@ pub struct RedeemTokensWithPayload<'info> {
/// CHECK: Seeds must be \["token_pair", remote_domain.to_string(), remote_token_address\] (CCTP
/// Token Messenger Minter program).
token_pair: AccountInfo<'info>,
token_pair: UncheckedAccount<'info>,
/// CHECK: Mutable. Seeds must be \["custody", mint\] (CCTP Token Messenger Minter program).
#[account(mut)]
token_messenger_minter_custody_token: AccountInfo<'info>,
token_messenger_minter_custody_token: UncheckedAccount<'info>,
/// CHECK: Seeds must be \["__event_authority"\] (CCTP Token Messenger Minter program).
token_messenger_minter_event_authority: UncheckedAccount<'info>,
token_messenger_minter_program:
Program<'info, token_messenger_minter_program::TokenMessengerMinter>,
@ -159,6 +165,14 @@ pub fn redeem_tokens_with_payload(
.token_messenger_minter_program
.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
message_transmitter_event_authority: ctx
.accounts
.message_transmitter_event_authority
.to_account_info(),
message_transmitter_program: ctx
.accounts
.message_transmitter_program
.to_account_info(),
token_messenger: ctx.accounts.token_messenger.to_account_info(),
remote_token_messenger: ctx.accounts.remote_token_messenger.to_account_info(),
token_minter: ctx.accounts.token_minter.to_account_info(),
@ -170,6 +184,10 @@ pub fn redeem_tokens_with_payload(
.token_messenger_minter_custody_token
.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
token_messenger_minter_event_authority: ctx
.accounts
.token_messenger_minter_event_authority
.to_account_info(),
},
&[&[Custodian::SEED_PREFIX, &[ctx.accounts.custodian.bump]]],
),

View File

@ -82,6 +82,10 @@ pub struct TransferTokensWithPayload<'info> {
#[account(mut)]
core_message: Signer<'info>,
/// CHECK: Mutable signer to create CCTP message.
#[account(mut)]
cctp_message: Signer<'info>,
/// CHECK: Seeds must be \["Sequence"\, custodian] (Wormhole Core Bridge program).
#[account(mut)]
core_emitter_sequence: UncheckedAccount<'info>,
@ -104,7 +108,7 @@ pub struct TransferTokensWithPayload<'info> {
/// Messenger Minter program).
remote_token_messenger: UncheckedAccount<'info>,
/// CHECK Seeds must be \["token_minter"\] (CCTP Token Messenger Minter program).
/// CHECK: Seeds must be \["token_minter"\] (CCTP Token Messenger Minter program).
token_minter: UncheckedAccount<'info>,
/// Local token account, which this program uses to validate the `mint` used to burn.
@ -113,6 +117,9 @@ pub struct TransferTokensWithPayload<'info> {
#[account(mut)]
local_token: Box<Account<'info, ExternalAccount<token_messenger_minter_program::LocalToken>>>,
/// CHECK: Seeds must be \["__event_authority"\] (CCTP Token Messenger Minter program).
token_messenger_minter_event_authority: UncheckedAccount<'info>,
core_bridge_program: Program<'info, core_bridge_program::CoreBridge>,
token_messenger_minter_program:
Program<'info, token_messenger_minter_program::TokenMessengerMinter>,
@ -148,7 +155,7 @@ pub struct TransferTokensWithPayloadArgs {
}
#[derive(Debug, Clone)]
pub struct WrappedVec(Vec<u8>);
struct WrappedVec(Vec<u8>);
impl Readable for WrappedVec {
const SIZE: Option<usize> = None;
@ -218,12 +225,13 @@ pub fn transfer_tokens_with_payload(
.token_messenger_minter_program
.to_account_info(),
wormhole_cctp_solana::cpi::DepositForBurnWithCaller {
src_token_owner: ctx.accounts.custodian.to_account_info(),
burn_token_owner: ctx.accounts.custodian.to_account_info(),
payer: ctx.accounts.payer.to_account_info(),
token_messenger_minter_sender_authority: ctx
.accounts
.token_messenger_minter_sender_authority
.to_account_info(),
src_token: ctx.accounts.custody_token.to_account_info(),
burn_token: ctx.accounts.custody_token.to_account_info(),
message_transmitter_config: ctx
.accounts
.message_transmitter_config
@ -233,6 +241,7 @@ pub fn transfer_tokens_with_payload(
token_minter: ctx.accounts.token_minter.to_account_info(),
local_token: ctx.accounts.local_token.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
cctp_message: ctx.accounts.cctp_message.to_account_info(),
message_transmitter_program: ctx
.accounts
.message_transmitter_program
@ -242,6 +251,11 @@ pub fn transfer_tokens_with_payload(
.token_messenger_minter_program
.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
event_authority: ctx
.accounts
.token_messenger_minter_event_authority
.to_account_info(),
},
&[custodian_seeds],
),

View File

@ -1,6 +1,3 @@
export { MessageTransmitterProgram } from "./messageTransmitter";
export { CctpMessage, CctpTokenBurnMessage } from "./messages";
export {
DEPOSIT_FOR_BURN_WITH_CALLER_IX_SELECTOR,
TokenMessengerMinterProgram,
} from "./tokenMessengerMinter";
export { TokenMessengerMinterProgram } from "./tokenMessengerMinter";

View File

@ -0,0 +1,11 @@
import { PublicKey } from "@solana/web3.js";
export class MessageSent {
rentPayer: PublicKey;
message: Buffer;
constructor(rentPayer: PublicKey, message: Buffer) {
this.rentPayer = rentPayer;
this.message = message;
}
}

View File

@ -1,3 +1,4 @@
import { BN } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
export class MessageTransmitterConfig {
@ -9,10 +10,9 @@ export class MessageTransmitterConfig {
localDomain: number;
version: number;
signatureThreshold: number;
enabledAttesters: Array<Array<number>>;
maxMessageBodySize: bigint;
nextAvailableNonce: bigint;
authorityBump: number;
enabledAttesters: Array<PublicKey>;
maxMessageBodySize: BN;
nextAvailableNonce: BN;
constructor(
owner: PublicKey,
@ -23,10 +23,9 @@ export class MessageTransmitterConfig {
localDomain: number,
version: number,
signatureThreshold: number,
enabledAttesters: Array<Array<number>>,
maxMessageBodySize: bigint,
nextAvailableNonce: bigint,
authorityBump: number,
enabledAttesters: Array<PublicKey>,
maxMessageBodySize: BN,
nextAvailableNonce: BN,
) {
this.owner = owner;
this.pendingOwner = pendingOwner;
@ -39,7 +38,6 @@ export class MessageTransmitterConfig {
this.enabledAttesters = enabledAttesters;
this.maxMessageBodySize = maxMessageBodySize;
this.nextAvailableNonce = nextAvailableNonce;
this.authorityBump = authorityBump;
}
static address(programId: PublicKey) {

View File

@ -1,9 +1,9 @@
import { Program } from "@coral-xyz/anchor";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { Connection, PublicKey } from "@solana/web3.js";
import { CctpTokenBurnMessage } from "../messages";
import { TokenMessengerMinterProgram } from "../tokenMessengerMinter";
import { IDL, MessageTransmitter } from "../types/message_transmitter";
import { MessageSent } from "./MessageSent";
import { MessageTransmitterConfig } from "./MessageTransmitterConfig";
import { UsedNonses } from "./UsedNonces";
@ -11,18 +11,20 @@ export const PROGRAM_IDS = ["CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd"] as c
export type ProgramId = (typeof PROGRAM_IDS)[number];
export type ReceiveMessageAccounts = {
export type ReceiveTokenMessengerMinterMessageAccounts = {
authority: PublicKey;
messageTransmitterConfig: PublicKey;
usedNonces: PublicKey;
tokenMessengerMinterProgram: PublicKey;
messageTransmitterEventAuthority: PublicKey;
messageTransmitterProgram: PublicKey;
tokenMessenger: PublicKey;
remoteTokenMessenger: PublicKey;
tokenMinter: PublicKey;
localToken: PublicKey;
tokenPair: PublicKey;
custodyToken: PublicKey;
tokenProgram: PublicKey;
eventAuthority: PublicKey;
};
export class MessageTransmitterProgram {
@ -46,48 +48,28 @@ export class MessageTransmitterProgram {
}
async fetchMessageTransmitterConfig(addr: PublicKey): Promise<MessageTransmitterConfig> {
const {
owner,
pendingOwner,
attesterManager,
pauser,
paused,
localDomain,
version,
signatureThreshold,
enabledAttesters,
maxMessageBodySize,
nextAvailableNonce,
authorityBump,
} = await this.program.account.messageTransmitter.fetch(addr);
return new MessageTransmitterConfig(
owner,
pendingOwner,
attesterManager,
pauser,
paused,
localDomain,
version,
signatureThreshold,
enabledAttesters.map((addr) => Array.from(addr.toBuffer())),
BigInt(maxMessageBodySize.toString()),
BigInt(nextAvailableNonce.toString()),
authorityBump,
);
return this.program.account.messageTransmitter.fetch(addr);
}
usedNoncesAddress(remoteDomain: number, nonce: bigint): PublicKey {
return UsedNonses.address(this.ID, remoteDomain, nonce);
}
authorityAddress(): PublicKey {
authorityAddress(cpiProgramId: PublicKey): PublicKey {
return PublicKey.findProgramAddressSync(
[Buffer.from("message_transmitter_authority")],
[Buffer.from("message_transmitter_authority"), cpiProgramId.toBuffer()],
this.ID,
)[0];
}
eventAuthorityAddress(): PublicKey {
return PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], this.ID)[0];
}
fetchMessageSent(addr: PublicKey): Promise<MessageSent> {
return this.program.account.messageSent.fetch(addr);
}
tokenMessengerMinterProgram(): TokenMessengerMinterProgram {
switch (this._programId) {
case testnet(): {
@ -108,10 +90,10 @@ export class MessageTransmitterProgram {
}
}
receiveMessageAccounts(
receiveTokenMessengerMinterMessageAccounts(
mint: PublicKey,
circleMessage: CctpTokenBurnMessage | Buffer,
): ReceiveMessageAccounts {
): ReceiveTokenMessengerMinterMessageAccounts {
const {
cctp: { sourceDomain, nonce },
burnTokenAddress,
@ -119,10 +101,12 @@ export class MessageTransmitterProgram {
const tokenMessengerMinterProgram = this.tokenMessengerMinterProgram();
return {
authority: this.authorityAddress(),
authority: this.authorityAddress(tokenMessengerMinterProgram.ID),
messageTransmitterConfig: this.messageTransmitterConfigAddress(),
usedNonces: this.usedNoncesAddress(sourceDomain, nonce),
tokenMessengerMinterProgram: tokenMessengerMinterProgram.ID,
messageTransmitterEventAuthority: this.eventAuthorityAddress(),
messageTransmitterProgram: this.ID,
tokenMessenger: tokenMessengerMinterProgram.tokenMessengerAddress(),
remoteTokenMessenger:
tokenMessengerMinterProgram.remoteTokenMessengerAddress(sourceDomain),
@ -130,7 +114,7 @@ export class MessageTransmitterProgram {
localToken: tokenMessengerMinterProgram.localTokenAddress(mint),
tokenPair: tokenMessengerMinterProgram.tokenPairAddress(sourceDomain, burnTokenAddress),
custodyToken: tokenMessengerMinterProgram.custodyTokenAddress(mint),
tokenProgram: TOKEN_PROGRAM_ID,
eventAuthority: tokenMessengerMinterProgram.eventAuthorityAddress(),
};
}
}

View File

@ -1,5 +1,4 @@
import { Program } from "@coral-xyz/anchor";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { Connection, PublicKey } from "@solana/web3.js";
import { MessageTransmitterProgram } from "../messageTransmitter";
import { IDL, TokenMessengerMinter } from "../types/token_messenger_minter";
@ -7,10 +6,6 @@ import { RemoteTokenMessenger } from "./RemoteTokenMessenger";
export const PROGRAM_IDS = ["CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3"] as const;
export const DEPOSIT_FOR_BURN_WITH_CALLER_IX_SELECTOR = Uint8Array.from([
167, 222, 19, 114, 85, 21, 14, 118,
]);
export type ProgramId = (typeof PROGRAM_IDS)[number];
export type DepositForBurnWithCallerAccounts = {
@ -20,9 +15,9 @@ export type DepositForBurnWithCallerAccounts = {
remoteTokenMessenger: PublicKey;
tokenMinter: PublicKey;
localToken: PublicKey;
tokenMessengerMinterEventAuthority: PublicKey;
messageTransmitterProgram: PublicKey;
tokenMessengerMinterProgram: PublicKey;
tokenProgram: PublicKey;
};
export class TokenMessengerMinterProgram {
@ -84,10 +79,14 @@ export class TokenMessengerMinterProgram {
)[0];
}
senderAuthority(): PublicKey {
senderAuthorityAddress(): PublicKey {
return PublicKey.findProgramAddressSync([Buffer.from("sender_authority")], this.ID)[0];
}
eventAuthorityAddress(): PublicKey {
return PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], this.ID)[0];
}
messageTransmitterProgram(): MessageTransmitterProgram {
switch (this._programId) {
case testnet(): {
@ -114,15 +113,15 @@ export class TokenMessengerMinterProgram {
): DepositForBurnWithCallerAccounts {
const messageTransmitterProgram = this.messageTransmitterProgram();
return {
senderAuthority: this.senderAuthority(),
senderAuthority: this.senderAuthorityAddress(),
messageTransmitterConfig: messageTransmitterProgram.messageTransmitterConfigAddress(),
tokenMessenger: this.tokenMessengerAddress(),
remoteTokenMessenger: this.remoteTokenMessengerAddress(remoteDomain),
tokenMinter: this.tokenMinterAddress(),
localToken: this.localTokenAddress(mint),
tokenMessengerMinterEventAuthority: this.eventAuthorityAddress(),
messageTransmitterProgram: messageTransmitterProgram.ID,
tokenMessengerMinterProgram: this.ID,
tokenProgram: TOKEN_PROGRAM_ID,
};
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,31 @@
export * from "./circle";
export * from "./cctp";
export * from "./consts";
export * from "./messages";
export * from "./state";
export * from "./wormhole";
import { BN, EventParser, Program, utils as anchorUtils } from "@coral-xyz/anchor";
import { BN, Program } from "@coral-xyz/anchor";
import * as splToken from "@solana/spl-token";
import {
AddressLookupTableAccount,
Connection,
PublicKey,
SYSVAR_CLOCK_PUBKEY,
SYSVAR_RENT_PUBKEY,
SystemProgram,
TransactionInstruction,
VersionedTransactionResponse,
} from "@solana/web3.js";
import {
IDL,
WormholeCircleIntegrationSolana,
} from "../../target/types/wormhole_circle_integration_solana";
import {
CctpMessage,
CctpTokenBurnMessage,
MessageTransmitterProgram,
TokenMessengerMinterProgram,
} from "./circle";
} from "./cctp";
import { BPF_LOADER_UPGRADEABLE_ID } from "./consts";
import { Custodian, RegisteredEmitter } from "./state";
import { Claim, VaaAccount } from "./wormhole";
import { PostedMessageData } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
import { Deposit } from "./messages";
export const PROGRAM_IDS = [
"Wormho1eCirc1e1ntegration111111111111111111", // mainnet placeholder
@ -83,10 +78,10 @@ export type TransferTokensWithPayloadAccounts = PublishMessageAccounts & {
remoteTokenMessenger: PublicKey;
tokenMinter: PublicKey;
localToken: PublicKey;
tokenMessengerMinterEventAuthority: PublicKey;
coreBridgeProgram: PublicKey;
tokenMessengerMinterProgram: PublicKey;
messageTransmitterProgram: PublicKey;
tokenProgram: PublicKey;
};
export type RedeemTokensWithPayloadAccounts = {
@ -98,15 +93,16 @@ export type RedeemTokensWithPayloadAccounts = {
messageTransmitterAuthority: PublicKey;
messageTransmitterConfig: PublicKey;
usedNonces: PublicKey;
messageTransmitterEventAuthority: PublicKey;
tokenMessenger: PublicKey;
remoteTokenMessenger: PublicKey;
tokenMinter: PublicKey;
localToken: PublicKey;
tokenPair: PublicKey;
tokenMessengerMinterCustodyToken: PublicKey;
tokenMessengerMinterEventAuthority: PublicKey;
tokenMessengerMinterProgram: PublicKey;
messageTransmitterProgram: PublicKey;
tokenProgram: PublicKey;
};
export type SolanaWormholeCctpTxData = {
@ -197,9 +193,12 @@ export class CircleIntegrationProgram {
coreBridgeProgram,
tokenMessenger: tokenMessengerMinterProgram.tokenMessengerAddress(),
tokenMinter: tokenMessengerMinterProgram.tokenMinterAddress(),
tokenMessengerMinterSenderAuthority: tokenMessengerMinterProgram.senderAuthority(),
tokenMessengerMinterSenderAuthority:
tokenMessengerMinterProgram.senderAuthorityAddress(),
tokenMessengerMinterProgram: tokenMessengerMinterProgram.ID,
messageTransmitterAuthority: messageTransmitterProgram.authorityAddress(),
messageTransmitterAuthority: messageTransmitterProgram.authorityAddress(
tokenMessengerMinterProgram.ID,
),
messageTransmitterConfig: messageTransmitterProgram.messageTransmitterConfigAddress(),
messageTransmitterProgram: messageTransmitterProgram.ID,
tokenProgram: splToken.TOKEN_PROGRAM_ID,
@ -311,9 +310,9 @@ export class CircleIntegrationProgram {
remoteTokenMessenger,
tokenMinter,
localToken,
tokenMessengerMinterEventAuthority,
messageTransmitterProgram,
tokenMessengerMinterProgram,
tokenProgram,
} = this.tokenMessengerMinterProgram().depositForBurnWithCallerAccounts(mint, remoteDomain);
const custodian = this.custodianAddress();
@ -333,10 +332,10 @@ export class CircleIntegrationProgram {
remoteTokenMessenger,
tokenMinter,
localToken,
tokenMessengerMinterEventAuthority,
coreBridgeProgram,
tokenMessengerMinterProgram,
messageTransmitterProgram,
tokenProgram,
};
}
@ -346,10 +345,11 @@ export class CircleIntegrationProgram {
mint: PublicKey;
burnSource: PublicKey;
coreMessage: PublicKey;
cctpMessage: PublicKey;
},
args: TransferTokensWithPayloadArgs,
): Promise<TransactionInstruction> {
let { payer, burnSource, mint, coreMessage } = accounts;
let { payer, burnSource, mint, coreMessage, cctpMessage } = accounts;
const { amount, targetChain, mintRecipient, wormholeMessageNonce, payload } = args;
@ -367,9 +367,9 @@ export class CircleIntegrationProgram {
remoteTokenMessenger,
tokenMinter,
localToken,
tokenMessengerMinterEventAuthority,
tokenMessengerMinterProgram,
messageTransmitterProgram,
tokenProgram,
} = await this.transferTokensWithPayloadAccounts(mint, targetChain);
return this.program.methods
@ -388,6 +388,7 @@ export class CircleIntegrationProgram {
registeredEmitter,
coreBridgeConfig,
coreMessage,
cctpMessage,
coreEmitterSequence,
coreFeeCollector,
tokenMessengerMinterSenderAuthority,
@ -396,10 +397,10 @@ export class CircleIntegrationProgram {
remoteTokenMessenger,
tokenMinter,
localToken,
tokenMessengerMinterEventAuthority,
coreBridgeProgram,
tokenMessengerMinterProgram,
messageTransmitterProgram,
tokenProgram,
})
.instruction();
}
@ -419,20 +420,21 @@ export class CircleIntegrationProgram {
const { chain, address, sequence } = vaaAcct.emitterInfo();
const claim = Claim.address(this.ID, address, chain, sequence);
const messageTransmitterProgram = this.messageTransmitterProgram();
const {
authority: messageTransmitterAuthority,
messageTransmitterConfig,
usedNonces,
tokenMessengerMinterProgram,
messageTransmitterEventAuthority,
messageTransmitterProgram,
tokenMessenger,
remoteTokenMessenger,
tokenMinter,
localToken,
tokenPair,
custodyToken: tokenMessengerMinterCustodyToken,
tokenProgram,
} = messageTransmitterProgram.receiveMessageAccounts(mint, msg);
eventAuthority: tokenMessengerMinterEventAuthority,
} = this.messageTransmitterProgram().receiveTokenMessengerMinterMessageAccounts(mint, msg);
return {
custodian: this.custodianAddress(),
@ -443,15 +445,16 @@ export class CircleIntegrationProgram {
messageTransmitterAuthority,
messageTransmitterConfig,
usedNonces,
messageTransmitterEventAuthority,
tokenMessenger,
remoteTokenMessenger,
tokenMinter,
localToken,
tokenPair,
tokenMessengerMinterCustodyToken,
tokenMessengerMinterEventAuthority,
tokenMessengerMinterProgram,
messageTransmitterProgram: messageTransmitterProgram.ID,
tokenProgram,
messageTransmitterProgram,
};
}
@ -479,15 +482,16 @@ export class CircleIntegrationProgram {
messageTransmitterAuthority,
messageTransmitterConfig,
usedNonces,
messageTransmitterEventAuthority,
tokenMessenger,
remoteTokenMessenger,
tokenMinter,
localToken,
tokenPair,
tokenMessengerMinterCustodyToken,
tokenMessengerMinterEventAuthority,
tokenMessengerMinterProgram,
messageTransmitterProgram,
tokenProgram,
} = await this.redeemTokensWithPayloadAccounts(vaa, encodedCctpMessage);
return this.program.methods
@ -503,15 +507,16 @@ export class CircleIntegrationProgram {
messageTransmitterAuthority,
messageTransmitterConfig,
usedNonces,
messageTransmitterEventAuthority,
tokenMessenger,
remoteTokenMessenger,
tokenMinter,
localToken,
tokenPair,
tokenMessengerMinterCustodyToken,
tokenMessengerMinterEventAuthority,
tokenMessengerMinterProgram,
messageTransmitterProgram,
tokenProgram,
})
.instruction();
}
@ -589,108 +594,6 @@ export class CircleIntegrationProgram {
}
}
}
async parseTransactionReceipt(
txReceipt: VersionedTransactionResponse,
addressLookupTableAccounts?: AddressLookupTableAccount[],
): Promise<SolanaWormholeCctpTxData[]> {
if (txReceipt.meta === null) {
throw new Error("meta not found in tx");
}
const txMeta = txReceipt.meta;
if (txMeta.logMessages === undefined || txMeta.logMessages === null) {
throw new Error("logMessages not found in tx");
}
const txLogMessages = txMeta.logMessages;
// Decode message field from MessageSent event.
const messageTransmitterProgram = this.messageTransmitterProgram();
const parser = new EventParser(
messageTransmitterProgram.ID,
messageTransmitterProgram.program.coder,
);
// Map these puppies based on nonce.
const encodedCctpMessages = new Map<bigint, Buffer>();
for (const parsed of parser.parseLogs(txLogMessages, false)) {
const msg = parsed.data.message as Buffer;
encodedCctpMessages.set(CctpMessage.decode(msg).cctp.nonce, msg);
}
const fetchedKeys = txReceipt.transaction.message.getAccountKeys({
addressLookupTableAccounts,
});
const accountKeys = fetchedKeys.staticAccountKeys;
if (fetchedKeys.accountKeysFromLookups !== undefined) {
accountKeys.push(
...fetchedKeys.accountKeysFromLookups.writable,
...fetchedKeys.accountKeysFromLookups.readonly,
);
}
const coreBridgeProgramIndex = accountKeys.findIndex((key) =>
key.equals(this.coreBridgeProgramId()),
);
const tokenMessengerMinterProgramIndex = accountKeys.findIndex((key) =>
key.equals(this.tokenMessengerMinterProgram().ID),
);
const messageTransmitterProgramIndex = accountKeys.findIndex((key) =>
key.equals(this.messageTransmitterProgram().ID),
);
if (
coreBridgeProgramIndex == -1 &&
tokenMessengerMinterProgramIndex == -1 &&
messageTransmitterProgramIndex == -1
) {
return [];
}
if (txMeta.innerInstructions === undefined || txMeta.innerInstructions === null) {
throw new Error("innerInstructions not found in tx");
}
const txInnerInstructions = txMeta.innerInstructions;
const custodian = this.custodianAddress();
const postedMessageKeys: PublicKey[] = [];
for (const innerIx of txInnerInstructions) {
// Traverse instructions to find messages posted by the Wormhole Circle Integration program.
for (const ixInfo of innerIx.instructions) {
if (
ixInfo.programIdIndex == coreBridgeProgramIndex &&
anchorUtils.bytes.bs58.decode(ixInfo.data)[0] == 1 &&
accountKeys[ixInfo.accounts[2]].equals(custodian)
) {
postedMessageKeys.push(accountKeys[ixInfo.accounts[1]]);
}
}
}
return this.program.provider.connection
.getMultipleAccountsInfo(postedMessageKeys)
.then((infos) =>
infos.map((info, i) => {
if (info === null) {
throw new Error("message info is null");
}
const payload = info.data.subarray(95);
const nonce = Deposit.decode(payload).deposit.cctpNonce;
const encodedCctpMessage = encodedCctpMessages.get(nonce);
if (encodedCctpMessage === undefined) {
throw new Error(
`cannot find CCTP message with nonce ${nonce} in tx receipt`,
);
}
return {
coreMessageAccount: postedMessageKeys[i],
coreMessageSequence: info.data.readBigUInt64LE(49),
encodedCctpMessage,
};
}),
);
}
}
export function mainnet(): ProgramId {

View File

@ -4,14 +4,7 @@ import { getPostedMessage } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhol
import * as anchor from "@coral-xyz/anchor";
import * as splToken from "@solana/spl-token";
import { expect } from "chai";
import {
CctpTokenBurnMessage,
Deposit,
DepositHeader,
CircleIntegrationProgram,
VaaAccount,
Claim,
} from "../src";
import { CctpTokenBurnMessage, CircleIntegrationProgram, Deposit, DepositHeader } from "../src";
import {
CircleAttester,
ETHEREUM_USDC_ADDRESS,
@ -269,13 +262,15 @@ describe("Circle Integration -- Localnet", () => {
const wormholeMessageNonce = 420;
const inputPayload = Buffer.from("All your base are belong to us.");
const message = anchor.web3.Keypair.generate();
const coreMessage = anchor.web3.Keypair.generate();
const cctpMessage = anchor.web3.Keypair.generate();
const ix = await circleIntegration.transferTokensWithPayloadIx(
{
payer: payer.publicKey,
mint: USDC_MINT_ADDRESS,
burnSource: payerToken,
coreMessage: message.publicKey,
coreMessage: coreMessage.publicKey,
cctpMessage: cctpMessage.publicKey,
},
{
amount: 0n,
@ -301,7 +296,7 @@ describe("Circle Integration -- Localnet", () => {
await expectIxErr(
connection,
[approveIx, ix],
[payer, message],
[payer, coreMessage, cctpMessage],
"Error Code: InvalidAmount",
{
addressLookupTableAccounts: [lookupTableAccount],
@ -320,13 +315,15 @@ describe("Circle Integration -- Localnet", () => {
const wormholeMessageNonce = 420;
const inputPayload = Buffer.from("All your base are belong to us.");
const message = anchor.web3.Keypair.generate();
const coreMessage = anchor.web3.Keypair.generate();
const cctpMessage = anchor.web3.Keypair.generate();
const ix = await circleIntegration.transferTokensWithPayloadIx(
{
payer: payer.publicKey,
mint: USDC_MINT_ADDRESS,
burnSource: payerToken,
coreMessage: message.publicKey,
coreMessage: coreMessage.publicKey,
cctpMessage: cctpMessage.publicKey,
},
{
amount,
@ -352,7 +349,7 @@ describe("Circle Integration -- Localnet", () => {
await expectIxErr(
connection,
[approveIx, ix],
[payer, message],
[payer, coreMessage, cctpMessage],
"Error Code: InvalidMintRecipient",
{
addressLookupTableAccounts: [lookupTableAccount],
@ -372,13 +369,15 @@ describe("Circle Integration -- Localnet", () => {
const wormholeMessageNonce = 420;
const inputPayload = Buffer.from("All your base are belong to us.");
const message = anchor.web3.Keypair.generate();
const coreMessage = anchor.web3.Keypair.generate();
const cctpMessage = anchor.web3.Keypair.generate();
const ix = await circleIntegration.transferTokensWithPayloadIx(
{
payer: payer.publicKey,
mint: USDC_MINT_ADDRESS,
burnSource: payerToken,
coreMessage: message.publicKey,
coreMessage: coreMessage.publicKey,
cctpMessage: cctpMessage.publicKey,
},
{
amount,
@ -394,9 +393,15 @@ describe("Circle Integration -- Localnet", () => {
.then((resp) => resp.value);
// NOTE: This is an SPL Token program error.
await expectIxErr(connection, [ix], [payer, message], "Error: owner does not match", {
addressLookupTableAccounts: [lookupTableAccount],
});
await expectIxErr(
connection,
[ix],
[payer, coreMessage, cctpMessage],
"Error: owner does not match",
{
addressLookupTableAccounts: [lookupTableAccount],
},
);
});
it("Invoke `transfer_tokens_with_payload`", async () => {
@ -411,13 +416,15 @@ describe("Circle Integration -- Localnet", () => {
const wormholeMessageNonce = 420;
const inputPayload = Buffer.from("All your base are belong to us.");
const message = anchor.web3.Keypair.generate();
const coreMessage = anchor.web3.Keypair.generate();
const cctpMessage = anchor.web3.Keypair.generate();
const ix = await circleIntegration.transferTokensWithPayloadIx(
{
payer: payer.publicKey,
mint: USDC_MINT_ADDRESS,
burnSource: payerToken,
coreMessage: message.publicKey,
coreMessage: coreMessage.publicKey,
cctpMessage: cctpMessage.publicKey,
},
{
amount,
@ -445,7 +452,7 @@ describe("Circle Integration -- Localnet", () => {
const txReceipt = await expectIxOkDetails(
connection,
[approveIx, ix],
[payer, message],
[payer, coreMessage, cctpMessage],
{
addressLookupTableAccounts: [lookupTableAccount],
},
@ -458,19 +465,15 @@ describe("Circle Integration -- Localnet", () => {
expect(balanceAfter + amount).to.equal(balanceBefore);
// Check messages.
const posted = await getPostedMessage(connection, message.publicKey);
const posted = await getPostedMessage(connection, coreMessage.publicKey);
const { deposit, payload } = Deposit.decode(posted.message.payload);
expect(payload).to.eql(inputPayload);
const parsedTxData = await circleIntegration.parseTransactionReceipt(txReceipt, [
lookupTableAccount,
]);
expect(parsedTxData).has.length(1);
const { message: encodedCctpMessage } = await circleIntegration
.messageTransmitterProgram()
.fetchMessageSent(cctpMessage.publicKey);
const txData = parsedTxData[0];
expect(txData.coreMessageAccount).is.eql(message.publicKey);
const burnMessage = CctpTokenBurnMessage.decode(txData.encodedCctpMessage);
const burnMessage = CctpTokenBurnMessage.decode(encodedCctpMessage);
expect(burnMessage.sender).to.eql(
Array.from(circleIntegration.custodianAddress().toBuffer()),
);
@ -1150,12 +1153,18 @@ describe("Circle Integration -- Localnet", () => {
{ encodedCctpMessage, cctpAttestation },
);
const lookupTableAccount = await connection
.getAddressLookupTable(lookupTableAddress)
.then((resp) => resp.value);
// NOTE: This is a CCTP Message Transmitter program error.
await expectIxErr(
connection,
[ix],
[payer, mintRecipientAuthority],
"Error Code: NonceAlreadyUsed",
{
addressLookupTableAccounts: [lookupTableAccount],
},
);
});
});

View File

@ -1,14 +1,14 @@
{
"pubkey": "4xt9P42CcMHXAgvemTnzineHp6owfGUcrg1xD9V7mdk1",
"account": {
"lamports": 1684320,
"lamports": 1795680,
"data": [
"n4M6qsFUgLaJOQ9DWBY8Pr5GkgAm5md6yYsX7D3P0LYdBflhsqtwJDtELLORIVfxOpM9ATQoLQMrX/7NAaLb8bd5BgjfAC6nABCl1OgAAAAyAAAAAAAAAIYAAAAAAAAAjaVmAQAAAAAhU6gjdg8AAP//",
"n4M6qsFUgLaJOQ9DWBY8Pr5GkgAm5md6yYsX7D3P0LYdBflhsqtwJDtELLORIVfxOpM9ATQoLQMrX/7NAaLb8bd5BgjfAC6nABCl1OgAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAD//w==",
"base64"
],
"owner": "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3",
"executable": false,
"rentEpoch": 18446744073709551615,
"space": 114
"space": 130
}
}
}