diff --git a/solana/Cargo.toml b/solana/Cargo.toml index fc20f657a..d1a435385 100644 --- a/solana/Cargo.toml +++ b/solana/Cargo.toml @@ -58,3 +58,6 @@ codegen-units = 1 opt-level = 3 incremental = false codegen-units = 1 + +[workspace.lints.clippy] +cast_possible_truncation = "deny" \ No newline at end of file diff --git a/solana/Makefile b/solana/Makefile index 4c2d57426..738412dd7 100644 --- a/solana/Makefile +++ b/solana/Makefile @@ -1,27 +1,27 @@ -SOLANA_CLI="v1.16.16" -ANCHOR_CLI="v0.28.0" - out_mainnet=artifacts-mainnet out_testnet=artifacts-testnet out_localnet=artifacts-localnet -.PHONY: all clean check build test lint ci cargo-test - +.PHONY: all all: check +.PHONY: check check: - cargo check --all-features + cargo check --workspace --all-targets --all-features +.PHONY: clean clean: anchor clean rm -rf node_modules artifacts-mainnet artifacts-testnet artifacts-localnet ts/tests/artifacts -node_modules: +node_modules: package-lock.json npm ci -cargo-test $(NETWORK): - cargo test --features "$(NETWORK)" --no-default-features +.PHONY: cargo-test +cargo-test: + cargo test --workspace --all-targets --features $(NETWORK) +.PHONY: build build: $(out_$(NETWORK)) $(out_$(NETWORK)): cargo-test ifdef out_$(NETWORK) @@ -30,6 +30,7 @@ ifdef out_$(NETWORK) cp target/deploy/*.so $(out_$(NETWORK))/ endif +.PHONY: test test: node_modules NETWORK=localnet $(MAKE) cargo-test NETWORK=testnet $(MAKE) cargo-test @@ -41,13 +42,12 @@ test: node_modules anchor build --arch sbf -- --features integration-test anchor test --skip-build +.PHONY: clippy +clippy: + cargo clippy --workspace --no-deps --all-targets --features $(NETWORK) -- -Dwarnings + +.PHONY: lint lint: cargo fmt --check - cargo clippy --no-deps --all-targets --features testnet -- -D warnings - cargo clippy --no-deps --all-targets --features localnet -- -D warnings - -ci: - DOCKER_BUILDKIT=1 docker build -f Dockerfile.ci \ - --build-arg SOLANA_CLI=$(SOLANA_CLI) \ - --build-arg ANCHOR_CLI=$(ANCHOR_CLI) \ - . + NETWORK=localnet $(MAKE) clippy + NETWORK=testnet $(MAKE) clippy \ No newline at end of file diff --git a/solana/modules/common/src/admin/utils/assistant.rs b/solana/modules/common/src/admin/utils/assistant.rs index a3dcb2ef1..576e474c8 100644 --- a/solana/modules/common/src/admin/utils/assistant.rs +++ b/solana/modules/common/src/admin/utils/assistant.rs @@ -1,23 +1,41 @@ use crate::admin::OwnerAssistant; use anchor_lang::prelude::*; -pub fn only_owner_assistant(acct: &Account, owner_assistant: &Pubkey) -> bool +pub fn only_owner_assistant( + acct: &Account, + owner_assistant: &Signer, + custom_error: Error, +) -> Result where A: OwnerAssistant + Clone + AccountSerialize + AccountDeserialize, { - acct.owner_assistant() == owner_assistant + if acct.owner_assistant() == &owner_assistant.key() { + Ok(true) + } else { + Err(custom_error.with_pubkeys((*acct.owner_assistant(), owner_assistant.key()))) + } } -pub fn only_authorized(acct: &Account, owner_or_assistant: &Pubkey) -> bool +pub fn only_authorized( + acct: &Account, + owner_or_assistant: &Signer, + custom_error: Error, +) -> Result where A: OwnerAssistant + Clone + AccountSerialize + AccountDeserialize, { - acct.owner() == owner_or_assistant || acct.owner_assistant() == owner_or_assistant + if acct.owner() == &owner_or_assistant.key() + || acct.owner_assistant() == &owner_or_assistant.key() + { + Ok(true) + } else { + Err(custom_error) + } } -pub fn transfer_owner_assistant(acct: &mut Account, new_assistant: &Pubkey) +pub fn transfer_owner_assistant(acct: &mut Account, new_assistant: &AccountInfo) where A: OwnerAssistant + Clone + AccountSerialize + AccountDeserialize, { - *acct.owner_assistant_mut() = *new_assistant; + *acct.owner_assistant_mut() = new_assistant.key(); } diff --git a/solana/modules/common/src/admin/utils/ownable.rs b/solana/modules/common/src/admin/utils/ownable.rs index d88ed64c9..73e2b6f15 100644 --- a/solana/modules/common/src/admin/utils/ownable.rs +++ b/solana/modules/common/src/admin/utils/ownable.rs @@ -1,16 +1,20 @@ use crate::admin::Ownable; use anchor_lang::prelude::*; -pub fn only_owner(acct: &Account, owner: &Pubkey) -> bool +pub fn only_owner(acct: &Account, owner: &Signer, custom_error: Error) -> Result where A: Ownable + Clone + AccountSerialize + AccountDeserialize, { - *acct.owner() == *owner + if acct.owner() == &owner.key() { + Ok(true) + } else { + Err(custom_error.with_pubkeys((*acct.owner(), owner.key()))) + } } -pub fn transfer_ownership(acct: &mut Account, new_owner: &Pubkey) +pub fn transfer_ownership(acct: &mut Account, new_owner: &AccountInfo) where A: Ownable + Clone + AccountSerialize + AccountDeserialize, { - *acct.owner_mut() = *new_owner; + *acct.owner_mut() = new_owner.key(); } diff --git a/solana/programs/matching-engine/Cargo.toml b/solana/programs/matching-engine/Cargo.toml index 03e71dac4..0bafbbdc8 100644 --- a/solana/programs/matching-engine/Cargo.toml +++ b/solana/programs/matching-engine/Cargo.toml @@ -34,4 +34,7 @@ ruint.workspace = true cfg-if.workspace = true [dev-dependencies] -hex-literal.workspace = true \ No newline at end of file +hex-literal.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/solana/programs/matching-engine/src/composite/mod.rs b/solana/programs/matching-engine/src/composite/mod.rs new file mode 100644 index 000000000..6e322607f --- /dev/null +++ b/solana/programs/matching-engine/src/composite/mod.rs @@ -0,0 +1,550 @@ +use std::ops::{Deref, DerefMut}; + +use crate::{ + error::MatchingEngineError, + state::{ + Auction, AuctionStatus, Custodian, MessageProtocol, PreparedOrderResponse, RouterEndpoint, + }, + utils::{self, VaaDigest}, +}; +use anchor_lang::prelude::*; +use anchor_spl::token; +use common::{ + admin::utils::{assistant::only_authorized, ownable::only_owner}, + messages::raw::{LiquidityLayerMessage, LiquidityLayerPayload}, + wormhole_cctp_solana::{ + cctp::{message_transmitter_program, token_messenger_minter_program}, + wormhole::{core_bridge_program, VaaAccount}, + }, +}; + +#[derive(Accounts)] +pub struct Usdc<'info> { + /// CHECK: This address must equal [USDC_MINT](common::constants::USDC_MINT). + #[account(address = common::constants::USDC_MINT)] + pub mint: AccountInfo<'info>, +} + +impl<'info> Deref for Usdc<'info> { + type Target = AccountInfo<'info>; + + fn deref(&self) -> &Self::Target { + &self.mint + } +} + +/// Mint recipient token account, which is encoded as the mint recipient in the CCTP message. +/// The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message +/// from its custody account to this account. +/// +/// CHECK: Mutable. Seeds must be \["custody"\]. +/// +/// NOTE: This account must be encoded as the mint recipient in the CCTP message. +#[derive(Accounts)] +pub struct CctpMintRecipientMut<'info> { + #[account( + mut, + address = crate::cctp_mint_recipient::id() + )] + pub mint_recipient: Box>, +} + +impl<'info> Deref for CctpMintRecipientMut<'info> { + type Target = Account<'info, token::TokenAccount>; + + fn deref(&self) -> &Self::Target { + &self.mint_recipient + } +} + +#[derive(Accounts)] +pub struct LiquidityLayerVaa<'info> { + /// CHECK: This VAA account must be a posted VAA from the Wormhole Core Bridge program. + #[account( + constraint = { + // NOTE: This load performs an owner check. + let vaa = VaaAccount::load(&vaa)?; + + // Is it a legitimate LL message? + LiquidityLayerPayload::try_from(vaa.payload()).map_err(|_| MatchingEngineError::InvalidVaa)?; + + // Done. + true + } + )] + pub vaa: AccountInfo<'info>, +} + +impl<'info> LiquidityLayerVaa<'info> { + pub fn load_unchecked(&self) -> VaaAccount<'_> { + VaaAccount::load_unchecked(self) + } +} + +impl<'info> Deref for LiquidityLayerVaa<'info> { + type Target = AccountInfo<'info>; + + fn deref(&self) -> &Self::Target { + &self.vaa + } +} + +#[derive(Accounts)] +pub struct CheckedCustodian<'info> { + #[account( + seeds = [Custodian::SEED_PREFIX], + bump = Custodian::BUMP, + )] + pub custodian: Account<'info, Custodian>, +} + +impl<'info> Deref for CheckedCustodian<'info> { + type Target = Account<'info, Custodian>; + + fn deref(&self) -> &Self::Target { + &self.custodian + } +} + +#[derive(Accounts)] +pub struct CheckedMutCustodian<'info> { + #[account( + mut, + seeds = [Custodian::SEED_PREFIX], + bump = Custodian::BUMP, + )] + pub custodian: Account<'info, Custodian>, +} + +impl<'info> Deref for CheckedMutCustodian<'info> { + type Target = Account<'info, Custodian>; + + fn deref(&self) -> &Self::Target { + &self.custodian + } +} + +impl<'info> DerefMut for CheckedMutCustodian<'info> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.custodian + } +} + +#[derive(Accounts)] +pub struct OwnerOnly<'info> { + #[account( + constraint = only_owner( + &custodian, + &owner, + error!(MatchingEngineError::OwnerOnly) + )? + )] + pub owner: Signer<'info>, + + pub custodian: CheckedCustodian<'info>, +} + +#[derive(Accounts)] +pub struct OwnerOnlyMut<'info> { + #[account( + constraint = only_owner( + &custodian, + &owner, + error!(MatchingEngineError::OwnerOnly) + )? + )] + pub owner: Signer<'info>, + + pub custodian: CheckedMutCustodian<'info>, +} + +#[derive(Accounts)] +pub struct Admin<'info> { + #[account( + constraint = only_authorized( + &custodian, + &owner_or_assistant, + error!(MatchingEngineError::OwnerOrAssistantOnly) + )? + )] + pub owner_or_assistant: Signer<'info>, + + pub custodian: CheckedCustodian<'info>, +} + +#[derive(Accounts)] +pub struct AdminMut<'info> { + #[account( + constraint = only_authorized( + &custodian, + &owner_or_assistant, + error!(MatchingEngineError::OwnerOrAssistantOnly) + )? + )] + pub owner_or_assistant: Signer<'info>, + + pub custodian: CheckedMutCustodian<'info>, +} + +#[derive(Accounts)] +pub struct LocalTokenRouter<'info> { + /// CHECK: Must be an executable (the Token Router program), whose ID will be used to derive the + /// emitter (router endpoint) address. + #[account(executable)] + pub token_router_program: AccountInfo<'info>, + + /// CHECK: The Token Router program's emitter PDA (a.k.a. its custodian) will have account data. + #[account( + seeds = [b"emitter"], + bump, + seeds::program = token_router_program, + owner = token_router_program.key() @ MatchingEngineError::InvalidEndpoint, + constraint = !token_router_emitter.data_is_empty() @ MatchingEngineError::InvalidEndpoint, + )] + pub token_router_emitter: AccountInfo<'info>, + + #[account( + associated_token::mint = common::constants::USDC_MINT, + associated_token::authority = token_router_emitter, + )] + pub token_router_mint_recipient: Account<'info, token::TokenAccount>, +} + +#[derive(Accounts)] +pub struct ExistingMutRouterEndpoint<'info> { + #[account( + mut, + seeds = [ + RouterEndpoint::SEED_PREFIX, + &endpoint.chain.to_be_bytes() + ], + bump = endpoint.bump, + )] + pub endpoint: Account<'info, RouterEndpoint>, +} + +impl<'info> Deref for ExistingMutRouterEndpoint<'info> { + type Target = Account<'info, RouterEndpoint>; + + fn deref(&self) -> &Self::Target { + &self.endpoint + } +} + +impl<'info> DerefMut for ExistingMutRouterEndpoint<'info> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.endpoint + } +} + +#[derive(Accounts)] +pub struct LiveRouterEndpoint<'info> { + #[account( + seeds = [ + RouterEndpoint::SEED_PREFIX, + &endpoint.chain.to_be_bytes() + ], + bump = endpoint.bump, + constraint = { + endpoint.protocol != MessageProtocol::None + } @ MatchingEngineError::EndpointDisabled, + )] + pub endpoint: Box>, +} + +impl<'info> Deref for LiveRouterEndpoint<'info> { + type Target = Account<'info, RouterEndpoint>; + + fn deref(&self) -> &Self::Target { + &self.endpoint + } +} + +#[derive(Accounts)] +pub struct FastOrderPath<'info> { + #[account( + constraint = { + let vaa = fast_vaa.load_unchecked(); + require_eq!( + from.chain, + vaa.emitter_chain(), + MatchingEngineError::ErrInvalidSourceRouter + ); + require!( + from.address == vaa.emitter_address(), + MatchingEngineError::ErrInvalidSourceRouter + ); + + let message = LiquidityLayerMessage::try_from(vaa.payload()).unwrap(); + let order = message + .fast_market_order() + .ok_or(MatchingEngineError::NotFastMarketOrder)?; + require_eq!( + to.chain, + order.target_chain(), + MatchingEngineError::ErrInvalidTargetRouter + ); + + true + } + )] + pub fast_vaa: LiquidityLayerVaa<'info>, + + pub from: LiveRouterEndpoint<'info>, + + #[account(constraint = from.chain != to.chain @ MatchingEngineError::SameEndpoint)] + pub to: LiveRouterEndpoint<'info>, +} + +#[derive(Accounts)] +pub struct ActiveAuction<'info> { + #[account( + mut, + seeds = [ + Auction::SEED_PREFIX, + auction.vaa_hash.as_ref(), + ], + bump = auction.bump, + constraint = matches!(auction.status, AuctionStatus::Active) @ MatchingEngineError::AuctionNotActive, + )] + pub auction: Box>, + + #[account( + mut, + seeds = [ + crate::AUCTION_CUSTODY_TOKEN_SEED_PREFIX, + auction.key().as_ref(), + ], + bump = auction.info.as_ref().unwrap().custody_token_bump, + )] + pub custody_token: Account<'info, anchor_spl::token::TokenAccount>, + + #[account( + constraint = { + require_eq!( + auction.info.as_ref().unwrap().config_id, + config.id, + MatchingEngineError::AuctionConfigMismatch + ); + true + }, + )] + pub config: Account<'info, crate::state::AuctionConfig>, + + /// CHECK: Mutable. Must have the same key in auction data. + #[account( + mut, + address = auction.info.as_ref().unwrap().best_offer_token, + )] + pub best_offer_token: AccountInfo<'info>, +} + +impl<'info> VaaDigest for ActiveAuction<'info> { + fn digest(&self) -> [u8; 32] { + self.auction.vaa_hash + } +} + +impl<'info> Deref for ActiveAuction<'info> { + type Target = Account<'info, Auction>; + + fn deref(&self) -> &Self::Target { + &self.auction + } +} + +impl<'info> DerefMut for ActiveAuction<'info> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.auction + } +} + +#[derive(Accounts)] +pub struct ExecuteOrder<'info> { + /// CHECK: Must be owned by the Wormhole Core Bridge program. + #[account( + constraint = utils::require_vaa_hash_equals(&active_auction, &fast_vaa.load_unchecked())? + )] + pub fast_vaa: LiquidityLayerVaa<'info>, + + pub active_auction: ActiveAuction<'info>, + + pub to_router_endpoint: LiveRouterEndpoint<'info>, + + /// CHECK: Must be a token account, whose mint is [common::constants::USDC_MINT]. + #[account(mut)] + pub executor_token: AccountInfo<'info>, + + /// CHECK: Mutable. Must equal [initial_offer](Auction::initial_offer). + #[account( + mut, + address = active_auction.info.as_ref().unwrap().initial_offer_token, + )] + pub initial_offer_token: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct WormholePublishMessage<'info> { + /// CHECK: Seeds must be \["Bridge"\] (Wormhole Core Bridge program). + #[account(mut)] + pub config: AccountInfo<'info>, + + /// CHECK: Seeds must be \["Sequence"\, custodian] (Wormhole Core Bridge program). + #[account(mut)] + pub emitter_sequence: AccountInfo<'info>, + + /// CHECK: Seeds must be \["fee_collector"\] (Wormhole Core Bridge program). + #[account(mut)] + pub fee_collector: AccountInfo<'info>, + + pub core_bridge_program: Program<'info, core_bridge_program::CoreBridge>, +} + +#[derive(Accounts)] +pub struct CctpDepositForBurn<'info> { + pub burn_source: CctpMintRecipientMut<'info>, + + /// Circle-supported mint. + /// + /// CHECK: Mutable. This token account's mint must be the same as the one found in the CCTP + /// Token Messenger Minter program's local token account. + #[account(mut)] + pub mint: AccountInfo<'info>, + + /// CHECK: Seeds must be \["sender_authority"\] (CCTP Token Messenger Minter program). + pub token_messenger_minter_sender_authority: AccountInfo<'info>, + + /// CHECK: Mutable. Seeds must be \["message_transmitter"\] (CCTP Message Transmitter program). + #[account(mut)] + pub message_transmitter_config: AccountInfo<'info>, + + /// CHECK: Seeds must be \["token_messenger"\] (CCTP Token Messenger Minter program). + pub token_messenger: AccountInfo<'info>, + + /// CHECK: Seeds must be \["remote_token_messenger"\, remote_domain.to_string()] (CCTP Token + /// Messenger Minter program). + pub remote_token_messenger: AccountInfo<'info>, + + /// CHECK Seeds must be \["token_minter"\] (CCTP Token Messenger Minter program). + pub token_minter: AccountInfo<'info>, + + /// Local token account, which this program uses to validate the `mint` used to burn. + /// + /// CHECK: Mutable. Seeds must be \["local_token", mint\] (CCTP Token Messenger Minter program). + #[account(mut)] + pub local_token: AccountInfo<'info>, + + /// CHECK: Seeds must be \["__event_authority"\] (CCTP Token Messenger Minter program). + pub token_messenger_minter_event_authority: AccountInfo<'info>, + + pub token_messenger_minter_program: + Program<'info, token_messenger_minter_program::TokenMessengerMinter>, + pub message_transmitter_program: + Program<'info, message_transmitter_program::MessageTransmitter>, +} + +#[derive(Accounts)] +pub struct CctpReceiveMessage<'info> { + pub mint_recipient: CctpMintRecipientMut<'info>, + + /// CHECK: Seeds must be \["message_transmitter_authority"\] (CCTP Message Transmitter program). + pub message_transmitter_authority: AccountInfo<'info>, + + /// CHECK: Seeds must be \["message_transmitter"\] (CCTP Message Transmitter program). + pub message_transmitter_config: AccountInfo<'info>, + + /// CHECK: 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>, + + /// CHECK: Seeds must be \["__event_authority"\] (CCTP Message Transmitter program)). + pub message_transmitter_event_authority: AccountInfo<'info>, + + /// CHECK: Seeds must be \["token_messenger"\] (CCTP Token Messenger Minter program). + pub token_messenger: AccountInfo<'info>, + + /// CHECK: Seeds must be \["remote_token_messenger"\, remote_domain.to_string()] (CCTP Token + /// Messenger Minter program). + pub remote_token_messenger: AccountInfo<'info>, + + /// CHECK: Seeds must be \["token_minter"\] (CCTP Token Messenger Minter program). + pub token_minter: AccountInfo<'info>, + + /// Token Messenger Minter's Local Token account. This program uses the mint of this account to + /// validate the `mint_recipient` token account's mint. + /// + /// CHECK: Mutable. Seeds must be \["local_token", mint\] (CCTP Token Messenger Minter program). + #[account(mut)] + pub local_token: AccountInfo<'info>, + + /// CHECK: Seeds must be \["token_pair", remote_domain.to_string(), remote_token_address\] (CCTP + /// Token Messenger Minter program). + pub token_pair: AccountInfo<'info>, + + /// CHECK: Mutable. Seeds must be \["custody", mint\] (CCTP Token Messenger Minter program). + #[account(mut)] + pub token_messenger_minter_custody_token: AccountInfo<'info>, + + /// CHECK: Seeds must be \["__event_authority"\] (CCTP Token Messenger Minter program). + pub token_messenger_minter_event_authority: AccountInfo<'info>, + + pub token_messenger_minter_program: + Program<'info, token_messenger_minter_program::TokenMessengerMinter>, + pub message_transmitter_program: + Program<'info, message_transmitter_program::MessageTransmitter>, +} + +#[derive(Accounts)] +pub struct ClosePreparedOrderResponse<'info> { + /// CHECK: Must equal the prepared_by field in the prepared order response. + #[account( + mut, + address = order_response.prepared_by, + )] + pub by: AccountInfo<'info>, + + #[account( + mut, + close = by, + seeds = [ + PreparedOrderResponse::SEED_PREFIX, + order_response.fast_vaa_hash.as_ref() + ], + bump = order_response.bump, + )] + pub order_response: Box>, + + /// CHECK: Seeds must be \["prepared-custody"\, prepared_order_response.key()]. + #[account( + mut, + seeds = [ + crate::PREPARED_CUSTODY_TOKEN_SEED_PREFIX, + order_response.key().as_ref(), + ], + bump, + )] + pub custody_token: AccountInfo<'info>, +} + +impl<'info> VaaDigest for ClosePreparedOrderResponse<'info> { + fn digest(&self) -> [u8; 32] { + self.order_response.fast_vaa_hash + } +} + +/// NOTE: Keep this at the end in case Wormhole removes the need for these accounts. +#[derive(Accounts)] +pub struct RequiredSysvars<'info> { + /// Wormhole Core Bridge needs the clock sysvar based on its legacy implementation. + /// + /// CHECK: Must equal clock ID. + #[account(address = solana_program::sysvar::clock::id())] + pub clock: AccountInfo<'info>, + + /// Wormhole Core Bridge needs the rent sysvar based on its legacy implementation. + /// + /// CHECK: Must equal rent ID. + #[account(address = solana_program::sysvar::rent::id())] + pub rent: AccountInfo<'info>, +} diff --git a/solana/programs/matching-engine/src/error.rs b/solana/programs/matching-engine/src/error.rs index 7897b55fa..e11419779 100644 --- a/solana/programs/matching-engine/src/error.rs +++ b/solana/programs/matching-engine/src/error.rs @@ -11,9 +11,6 @@ pub enum MatchingEngineError { #[msg("OwnerOrAssistantOnly")] OwnerOrAssistantOnly = 0x4, - #[msg("InvalidCustodyToken")] - InvalidCustodyToken = 0x6, - #[msg("CpiDisallowed")] CpiDisallowed = 0x8, @@ -35,9 +32,6 @@ pub enum MatchingEngineError { #[msg("ImmutableProgram")] ImmutableProgram = 0x102, - #[msg("NotUsdc")] - NotUsdc = 0x103, - #[msg("InvalidNewOwner")] InvalidNewOwner = 0x202, @@ -146,8 +140,8 @@ pub enum MatchingEngineError { #[msg("MismatchedVaaHash")] MismatchedVaaHash, - #[msg("BestOfferTokenMismatch")] - BestOfferTokenMismatch, + #[msg("ExecutorTokenMismatch")] + ExecutorTokenMismatch, #[msg("InitialOfferTokenMismatch")] InitialOfferTokenMismatch, diff --git a/solana/programs/matching-engine/src/lib.rs b/solana/programs/matching-engine/src/lib.rs index 1b145c988..5f454a09e 100644 --- a/solana/programs/matching-engine/src/lib.rs +++ b/solana/programs/matching-engine/src/lib.rs @@ -3,10 +3,12 @@ pub mod cctp_mint_recipient; -pub mod error; +mod composite; + +mod error; mod processor; -pub(crate) use processor::*; +use processor::*; pub mod state; @@ -25,6 +27,10 @@ cfg_if::cfg_if! { } } +const AUCTION_CUSTODY_TOKEN_SEED_PREFIX: &[u8] = b"auction-custody"; +const LOCAL_CUSTODY_TOKEN_SEED_PREFIX: &[u8] = b"local-custody"; +const PREPARED_CUSTODY_TOKEN_SEED_PREFIX: &[u8] = b"prepared-custody"; + #[program] pub mod matching_engine { use super::*; @@ -52,14 +58,6 @@ pub mod matching_engine { processor::settle_auction_none_local(ctx) } - pub fn settle_auction_active_cctp(ctx: Context) -> Result<()> { - processor::settle_auction_active_cctp(ctx) - } - - pub fn settle_auction_active_local(ctx: Context) -> Result<()> { - processor::settle_auction_active_local(ctx) - } - /// This instruction is be used to generate your program's config. /// And for convenience, we will store Wormhole-related PDAs in the /// config so we can verify these accounts with a simple == constraint. diff --git a/solana/programs/matching-engine/src/processor/admin/close_proposal.rs b/solana/programs/matching-engine/src/processor/admin/close_proposal.rs index 6c5146615..a61148b1a 100644 --- a/solana/programs/matching-engine/src/processor/admin/close_proposal.rs +++ b/solana/programs/matching-engine/src/processor/admin/close_proposal.rs @@ -1,9 +1,9 @@ -use crate::state::{custodian::*, Proposal}; +use crate::{composite::*, state::Proposal}; use anchor_lang::prelude::*; #[derive(Accounts)] pub struct CloseProposal<'info> { - admin: OwnerCustodian<'info>, + admin: OwnerOnly<'info>, /// CHECK: This account must equal proposal.by pubkey. #[account( @@ -17,7 +17,7 @@ pub struct CloseProposal<'info> { close = proposed_by, seeds = [ Proposal::SEED_PREFIX, - proposal.id.to_be_bytes().as_ref(), + &proposal.id.to_be_bytes(), ], bump = proposal.bump, constraint = proposal.slot_enacted_at.is_none() @ ErrorCode::InstructionMissing, // TODO: add err ProposalAlreadyEnacted diff --git a/solana/programs/matching-engine/src/processor/admin/initialize.rs b/solana/programs/matching-engine/src/processor/admin/initialize.rs index fca48a9c2..19930bc22 100644 --- a/solana/programs/matching-engine/src/processor/admin/initialize.rs +++ b/solana/programs/matching-engine/src/processor/admin/initialize.rs @@ -1,4 +1,5 @@ use crate::{ + composite::*, error::MatchingEngineError, state::{AuctionConfig, Custodian}, }; @@ -32,7 +33,7 @@ pub struct Initialize<'info> { space = 8 + AuctionConfig::INIT_SPACE, seeds = [ AuctionConfig::SEED_PREFIX, - u32::default().to_be_bytes().as_ref() + &u32::default().to_be_bytes() ], bump, )] @@ -58,22 +59,21 @@ pub struct Initialize<'info> { fee_recipient: AccountInfo<'info>, #[account( - associated_token::mint = mint, + associated_token::mint = usdc, associated_token::authority = fee_recipient, )] fee_recipient_token: Account<'info, token::TokenAccount>, #[account( - init, + init_if_needed, payer = owner, - associated_token::mint = mint, + associated_token::mint = usdc, associated_token::authority = custodian, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, + address = crate::cctp_mint_recipient::id(), )] cctp_mint_recipient: Account<'info, token::TokenAccount>, - #[account(address = common::constants::USDC_MINT @ MatchingEngineError::NotUsdc)] - mint: Account<'info, token::Mint>, + usdc: Usdc<'info>, /// We use the program data to make sure this owner is the upgrade authority (the true owner, /// who deployed this program). diff --git a/solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/cancel.rs b/solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/cancel.rs index d4d2fc0a7..372605464 100644 --- a/solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/cancel.rs +++ b/solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/cancel.rs @@ -1,9 +1,9 @@ -use crate::state::custodian::*; +use crate::composite::*; use anchor_lang::prelude::*; #[derive(Accounts)] pub struct CancelOwnershipTransferRequest<'info> { - admin: OwnerMutCustodian<'info>, + admin: OwnerOnlyMut<'info>, } pub fn cancel_ownership_transfer_request( diff --git a/solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/submit.rs b/solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/submit.rs index cb33003d4..4eec99b80 100644 --- a/solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/submit.rs +++ b/solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/submit.rs @@ -1,9 +1,9 @@ -use crate::{error::MatchingEngineError, state::custodian::*}; +use crate::{composite::*, error::MatchingEngineError}; use anchor_lang::prelude::*; #[derive(Accounts)] pub struct SubmitOwnershipTransferRequest<'info> { - admin: OwnerMutCustodian<'info>, + admin: OwnerOnlyMut<'info>, /// New Owner. /// diff --git a/solana/programs/matching-engine/src/processor/admin/propose/auction_parameters.rs b/solana/programs/matching-engine/src/processor/admin/propose/auction_parameters.rs index 6869e733a..4d69904ee 100644 --- a/solana/programs/matching-engine/src/processor/admin/propose/auction_parameters.rs +++ b/solana/programs/matching-engine/src/processor/admin/propose/auction_parameters.rs @@ -1,4 +1,7 @@ -use crate::state::{custodian::*, AuctionParameters, Proposal, ProposalAction}; +use crate::{ + composite::*, + state::{AuctionParameters, Proposal, ProposalAction}, +}; use anchor_lang::prelude::*; #[derive(Accounts)] @@ -6,7 +9,7 @@ pub struct ProposeAuctionParameters<'info> { #[account(mut)] payer: Signer<'info>, - admin: AdminMutCustodian<'info>, + admin: AdminMut<'info>, #[account( init, @@ -14,7 +17,7 @@ pub struct ProposeAuctionParameters<'info> { space = 8 + Proposal::INIT_SPACE, seeds = [ Proposal::SEED_PREFIX, - admin.custodian.next_proposal_id.to_be_bytes().as_ref() + &admin.custodian.next_proposal_id.to_be_bytes() ], bump, )] diff --git a/solana/programs/matching-engine/src/processor/admin/router_endpoint/add/cctp.rs b/solana/programs/matching-engine/src/processor/admin/router_endpoint/add/cctp.rs index 0def9b781..f9ea47eb0 100644 --- a/solana/programs/matching-engine/src/processor/admin/router_endpoint/add/cctp.rs +++ b/solana/programs/matching-engine/src/processor/admin/router_endpoint/add/cctp.rs @@ -1,11 +1,14 @@ use crate::{ - state::{custodian::*, router_endpoint::*}, + composite::*, + state::RouterEndpoint, utils::{self, admin::AddCctpRouterEndpointArgs}, }; use anchor_lang::prelude::*; +use anchor_spl::token; use common::wormhole_cctp_solana::{ cctp::token_messenger_minter_program::{self, RemoteTokenMessenger}, utils::ExternalAccount, + wormhole::SOLANA_CHAIN, }; #[derive(Accounts)] @@ -14,7 +17,7 @@ pub struct AddCctpRouterEndpoint<'info> { #[account(mut)] payer: Signer<'info>, - admin: AdminCustodian<'info>, + admin: Admin<'info>, #[account( init, @@ -28,6 +31,36 @@ pub struct AddCctpRouterEndpoint<'info> { )] router_endpoint: Account<'info, RouterEndpoint>, + /// Local router endpoint PDA. + /// + /// NOTE: This account may not exist yet. But we need to pass it since it will be the owner of + /// the local custody token account. + /// + /// CHECK: Seeds must be \["endpoint", SOLANA_CHAIN.to_be_bytes()]. + #[account( + seeds = [ + RouterEndpoint::SEED_PREFIX, + &SOLANA_CHAIN.to_be_bytes() + ], + bump, + )] + local_router_endpoint: AccountInfo<'info>, + + #[account( + init, + payer = payer, + token::mint = usdc, + token::authority = local_router_endpoint, + seeds = [ + crate::LOCAL_CUSTODY_TOKEN_SEED_PREFIX, + &args.chain.to_be_bytes(), + ], + bump, + )] + local_custody_token: Box>, + + usdc: Usdc<'info>, + /// CHECK: Seeds must be \["remote_token_messenger"\, remote_domain.to_string()] (CCTP Token /// Messenger Minter program). #[account( @@ -40,6 +73,7 @@ pub struct AddCctpRouterEndpoint<'info> { )] remote_token_messenger: Account<'info, ExternalAccount>, + token_program: Program<'info, token::Token>, system_program: Program<'info, System>, } diff --git a/solana/programs/matching-engine/src/processor/admin/router_endpoint/add/local.rs b/solana/programs/matching-engine/src/processor/admin/router_endpoint/add/local.rs index 5e484cf94..cb714d2e8 100644 --- a/solana/programs/matching-engine/src/processor/admin/router_endpoint/add/local.rs +++ b/solana/programs/matching-engine/src/processor/admin/router_endpoint/add/local.rs @@ -1,8 +1,4 @@ -use crate::{ - processor::admin::router_endpoint::local_token_router::*, - state::{custodian::*, RouterEndpoint}, - utils, -}; +use crate::{composite::*, state::RouterEndpoint, utils}; use anchor_lang::prelude::*; use common::wormhole_cctp_solana::wormhole::SOLANA_CHAIN; @@ -11,7 +7,7 @@ pub struct AddLocalRouterEndpoint<'info> { #[account(mut)] payer: Signer<'info>, - admin: AdminCustodian<'info>, + admin: Admin<'info>, #[account( init, diff --git a/solana/programs/matching-engine/src/processor/admin/router_endpoint/disable.rs b/solana/programs/matching-engine/src/processor/admin/router_endpoint/disable.rs index 249c28747..ef5c1c5cf 100644 --- a/solana/programs/matching-engine/src/processor/admin/router_endpoint/disable.rs +++ b/solana/programs/matching-engine/src/processor/admin/router_endpoint/disable.rs @@ -1,15 +1,15 @@ -use crate::state::{custodian::*, router_endpoint::*, MessageProtocol}; +use crate::{composite::*, state::MessageProtocol}; use anchor_lang::prelude::*; #[derive(Accounts)] pub struct DisableRouterEndpoint<'info> { - admin: OwnerCustodian<'info>, + admin: OwnerOnly<'info>, router_endpoint: ExistingMutRouterEndpoint<'info>, } pub fn disable_router_endpoint(ctx: Context) -> Result<()> { - let endpoint = &mut ctx.accounts.router_endpoint.inner; + let endpoint = &mut ctx.accounts.router_endpoint; endpoint.protocol = MessageProtocol::None; endpoint.address = Default::default(); endpoint.mint_recipient = Default::default(); diff --git a/solana/programs/matching-engine/src/processor/admin/router_endpoint/mod.rs b/solana/programs/matching-engine/src/processor/admin/router_endpoint/mod.rs index 7eb6dba78..5cb1fe565 100644 --- a/solana/programs/matching-engine/src/processor/admin/router_endpoint/mod.rs +++ b/solana/programs/matching-engine/src/processor/admin/router_endpoint/mod.rs @@ -6,32 +6,3 @@ pub use disable::*; mod update; pub use update::*; - -pub mod local_token_router { - use crate::error::MatchingEngineError; - use anchor_lang::prelude::*; - - #[derive(Accounts)] - pub struct LocalTokenRouter<'info> { - /// CHECK: Must be an executable (the Token Router program), whose ID will be used to derive the - /// emitter (router endpoint) address. - #[account(executable)] - pub token_router_program: AccountInfo<'info>, - - /// CHECK: The Token Router program's emitter PDA (a.k.a. its custodian) will have account data. - #[account( - seeds = [b"emitter"], - bump, - seeds::program = token_router_program, - owner = token_router_program.key() @ MatchingEngineError::InvalidEndpoint, - constraint = !token_router_emitter.data_is_empty() @ MatchingEngineError::InvalidEndpoint, - )] - pub token_router_emitter: AccountInfo<'info>, - - #[account( - associated_token::mint = common::constants::USDC_MINT, - associated_token::authority = token_router_emitter, - )] - pub token_router_mint_recipient: Account<'info, anchor_spl::token::TokenAccount>, - } -} diff --git a/solana/programs/matching-engine/src/processor/admin/router_endpoint/update/cctp.rs b/solana/programs/matching-engine/src/processor/admin/router_endpoint/update/cctp.rs index 4b9450b51..2970ca6c3 100644 --- a/solana/programs/matching-engine/src/processor/admin/router_endpoint/update/cctp.rs +++ b/solana/programs/matching-engine/src/processor/admin/router_endpoint/update/cctp.rs @@ -1,5 +1,5 @@ use crate::{ - state::{custodian::*, router_endpoint::*}, + composite::*, utils::{self, admin::AddCctpRouterEndpointArgs}, }; use anchor_lang::prelude::*; @@ -11,7 +11,7 @@ use common::wormhole_cctp_solana::{ #[derive(Accounts)] #[instruction(args: AddCctpRouterEndpointArgs)] pub struct UpdateCctpRouterEndpoint<'info> { - admin: OwnerCustodian<'info>, + admin: OwnerOnly<'info>, router_endpoint: ExistingMutRouterEndpoint<'info>, @@ -32,9 +32,5 @@ pub fn update_cctp_router_endpoint( ctx: Context, args: AddCctpRouterEndpointArgs, ) -> Result<()> { - utils::admin::handle_add_cctp_router_endpoint( - &mut ctx.accounts.router_endpoint.inner, - args, - None, - ) + utils::admin::handle_add_cctp_router_endpoint(&mut ctx.accounts.router_endpoint, args, None) } diff --git a/solana/programs/matching-engine/src/processor/admin/router_endpoint/update/local.rs b/solana/programs/matching-engine/src/processor/admin/router_endpoint/update/local.rs index e7fda5c46..edfa11511 100644 --- a/solana/programs/matching-engine/src/processor/admin/router_endpoint/update/local.rs +++ b/solana/programs/matching-engine/src/processor/admin/router_endpoint/update/local.rs @@ -1,20 +1,15 @@ -use crate::{ - error::MatchingEngineError, - processor::admin::router_endpoint::local_token_router::*, - state::{custodian::*, router_endpoint::*}, - utils, -}; +use crate::{composite::*, error::MatchingEngineError, utils}; use anchor_lang::prelude::*; use common::wormhole_cctp_solana::wormhole::SOLANA_CHAIN; #[derive(Accounts)] pub struct UpdateLocalRouterEndpoint<'info> { - admin: OwnerCustodian<'info>, + admin: OwnerOnly<'info>, #[account( constraint = { require_eq!( - router_endpoint.inner.chain, + router_endpoint.chain, SOLANA_CHAIN, MatchingEngineError::InvalidChain ); @@ -28,7 +23,7 @@ pub struct UpdateLocalRouterEndpoint<'info> { pub fn update_local_router_endpoint(ctx: Context) -> Result<()> { utils::admin::handle_add_local_router_endpoint( - &mut ctx.accounts.router_endpoint.inner, + &mut ctx.accounts.router_endpoint, &ctx.accounts.local.token_router_program, &ctx.accounts.local.token_router_emitter, &ctx.accounts.local.token_router_mint_recipient, diff --git a/solana/programs/matching-engine/src/processor/admin/update/auction_parameters.rs b/solana/programs/matching-engine/src/processor/admin/update/auction_parameters.rs index fef29551f..259792f00 100644 --- a/solana/programs/matching-engine/src/processor/admin/update/auction_parameters.rs +++ b/solana/programs/matching-engine/src/processor/admin/update/auction_parameters.rs @@ -1,6 +1,6 @@ use crate::{ + composite::*, error::MatchingEngineError, - state::custodian::*, state::{AuctionConfig, Proposal, ProposalAction}, }; use anchor_lang::prelude::*; @@ -10,13 +10,13 @@ pub struct UpdateAuctionParameters<'info> { #[account(mut)] payer: Signer<'info>, - admin: OwnerMutCustodian<'info>, + admin: OwnerOnlyMut<'info>, #[account( mut, seeds = [ Proposal::SEED_PREFIX, - proposal.id.to_be_bytes().as_ref(), + &proposal.id.to_be_bytes(), ], bump = proposal.bump, constraint = { diff --git a/solana/programs/matching-engine/src/processor/admin/update/fee_recipient_token.rs b/solana/programs/matching-engine/src/processor/admin/update/fee_recipient_token.rs index a3f66124c..68a87a340 100644 --- a/solana/programs/matching-engine/src/processor/admin/update/fee_recipient_token.rs +++ b/solana/programs/matching-engine/src/processor/admin/update/fee_recipient_token.rs @@ -1,24 +1,10 @@ -use crate::{error::MatchingEngineError, state::Custodian}; +use crate::{composite::*, error::MatchingEngineError}; use anchor_lang::prelude::*; use anchor_spl::token; -use common::admin::utils::assistant::only_authorized; #[derive(Accounts)] pub struct UpdateFeeRecipient<'info> { - #[account( - mut, - constraint = { - only_authorized(&custodian, &owner_or_assistant.key()) - } @ MatchingEngineError::OwnerOrAssistantOnly, - )] - owner_or_assistant: Signer<'info>, - - #[account( - mut, - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - )] - custodian: Account<'info, Custodian>, + admin: AdminMut<'info>, #[account( associated_token::mint = common::constants::USDC_MINT, @@ -39,7 +25,7 @@ pub struct UpdateFeeRecipient<'info> { pub fn update_fee_recipient(ctx: Context) -> Result<()> { // Update the fee_recipient key. - ctx.accounts.custodian.fee_recipient_token = ctx.accounts.new_fee_recipient_token.key(); + ctx.accounts.admin.custodian.fee_recipient_token = ctx.accounts.new_fee_recipient_token.key(); Ok(()) } diff --git a/solana/programs/matching-engine/src/processor/admin/update/owner_assistant.rs b/solana/programs/matching-engine/src/processor/admin/update/owner_assistant.rs index 1aa7a27d6..2d80271a1 100644 --- a/solana/programs/matching-engine/src/processor/admin/update/owner_assistant.rs +++ b/solana/programs/matching-engine/src/processor/admin/update/owner_assistant.rs @@ -1,19 +1,10 @@ -use crate::{error::MatchingEngineError, state::Custodian}; +use crate::{composite::*, error::MatchingEngineError}; use anchor_lang::prelude::*; use common::admin::utils::assistant; #[derive(Accounts)] pub struct UpdateOwnerAssistant<'info> { - /// Owner of the program set in the [`OwnerConfig`] account. - owner: Signer<'info>, - - #[account( - mut, - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - has_one = owner @ MatchingEngineError::OwnerOnly, - )] - custodian: Account<'info, Custodian>, + admin: OwnerOnlyMut<'info>, /// New Assistant. /// @@ -28,8 +19,8 @@ pub struct UpdateOwnerAssistant<'info> { pub fn update_owner_assistant(ctx: Context) -> Result<()> { assistant::transfer_owner_assistant( - &mut ctx.accounts.custodian, - &ctx.accounts.new_owner_assistant.key(), + &mut ctx.accounts.admin.custodian, + &ctx.accounts.new_owner_assistant, ); // Done. diff --git a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/cctp.rs b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/cctp.rs index 5a2fa57f6..00dbd4f78 100644 --- a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/cctp.rs +++ b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/cctp.rs @@ -1,18 +1,11 @@ use crate::{ + composite::*, error::MatchingEngineError, - state::{Auction, AuctionConfig, Custodian, MessageProtocol, PayerSequence, RouterEndpoint}, - utils, + state::{Auction, Custodian, MessageProtocol, PayerSequence}, }; use anchor_lang::prelude::*; use anchor_spl::token; -use common::{ - wormhole_cctp_solana::{ - self, - cctp::{message_transmitter_program, token_messenger_minter_program}, - wormhole::{core_bridge_program, VaaAccount}, - }, - wormhole_io::TypePrefixedPayload, -}; +use common::{wormhole_cctp_solana, wormhole_io::TypePrefixedPayload}; /// Accounts required for [execute_fast_order_cctp]. #[derive(Accounts)] @@ -32,88 +25,13 @@ pub struct ExecuteFastOrderCctp<'info> { )] payer_sequence: Account<'info, PayerSequence>, - /// This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source - /// authority for CCTP transfers. - /// - /// CHECK: Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - )] - custodian: AccountInfo<'info>, - - auction_config: Box>, - - /// CHECK: Must be owned by the Wormhole Core Bridge program. - #[account(owner = core_bridge_program::id())] - fast_vaa: AccountInfo<'info>, - - #[account( - mut, - seeds = [ - Auction::SEED_PREFIX, - VaaAccount::load(&fast_vaa)?.digest().as_ref() - ], - bump = auction.bump, - constraint = utils::is_valid_active_auction( - &auction_config, - &auction, - Some(best_offer_token.key()), - Some(initial_offer_token.key()), - )? - )] - auction: Box>, - - #[account( - seeds = [ - RouterEndpoint::SEED_PREFIX, - to_router_endpoint.chain.to_be_bytes().as_ref(), - ], - bump = to_router_endpoint.bump, - )] - to_router_endpoint: Account<'info, RouterEndpoint>, - - #[account( - mut, - token::mint = mint, - )] - executor_token: Box>, - - /// CHECK: Mutable. Must equal [best_offer](Auction::best_offer). - #[account(mut)] - best_offer_token: AccountInfo<'info>, - - /// CHECK: Mutable. Must equal [initial_offer](Auction::initial_offer). - #[account(mut)] - initial_offer_token: AccountInfo<'info>, - - /// Also the burn_source token account. - /// - /// CHECK: Mutable. Seeds must be \["custody"\]. - #[account( - mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, - )] - cctp_mint_recipient: AccountInfo<'info>, - - /// Circle-supported mint. - /// - /// CHECK: Mutable. This token account's mint must be the same as the one found in the CCTP - /// Token Messenger Minter program's local token account. - #[account(mut)] - mint: AccountInfo<'info>, - - /// CHECK: Seeds must be \["Bridge"\] (Wormhole Core Bridge program). - #[account(mut)] - core_bridge_config: UncheckedAccount<'info>, - /// CHECK: Mutable. Seeds must be \["core-msg", payer, payer_sequence.value\]. #[account( mut, seeds = [ common::constants::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), - payer_sequence.value.to_be_bytes().as_ref(), + &payer_sequence.value.to_be_bytes(), ], bump, )] @@ -125,65 +43,29 @@ pub struct ExecuteFastOrderCctp<'info> { seeds = [ common::constants::CCTP_MESSAGE_SEED_PREFIX, payer.key().as_ref(), - payer_sequence.value.to_be_bytes().as_ref(), + &payer_sequence.value.to_be_bytes(), ], bump, )] cctp_message: AccountInfo<'info>, - /// CHECK: Seeds must be \["Sequence"\, custodian] (Wormhole Core Bridge program). - #[account(mut)] - core_emitter_sequence: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["fee_collector"\] (Wormhole Core Bridge program). - #[account(mut)] - core_fee_collector: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["sender_authority"\] (CCTP Token Messenger Minter program). - token_messenger_minter_sender_authority: UncheckedAccount<'info>, - - /// CHECK: Mutable. Seeds must be \["message_transmitter"\] (CCTP Message Transmitter program). - #[account(mut)] - message_transmitter_config: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["token_messenger"\] (CCTP Token Messenger Minter program). - token_messenger: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["remote_token_messenger"\, remote_domain.to_string()] (CCTP Token - /// Messenger Minter program). - remote_token_messenger: UncheckedAccount<'info>, + custodian: CheckedCustodian<'info>, - /// CHECK Seeds must be \["token_minter"\] (CCTP Token Messenger Minter program). - token_minter: UncheckedAccount<'info>, + execute_order: ExecuteOrder<'info>, - /// Local token account, which this program uses to validate the `mint` used to burn. - /// - /// CHECK: Mutable. Seeds must be \["local_token", mint\] (CCTP Token Messenger Minter program). - #[account(mut)] - local_token: UncheckedAccount<'info>, + wormhole: WormholePublishMessage<'info>, - /// CHECK: Seeds must be \["__event_authority"\] (CCTP Token Messenger Minter program). - token_messenger_minter_event_authority: UncheckedAccount<'info>, + cctp: CctpDepositForBurn<'info>, - core_bridge_program: Program<'info, core_bridge_program::CoreBridge>, - token_messenger_minter_program: - Program<'info, token_messenger_minter_program::TokenMessengerMinter>, - message_transmitter_program: Program<'info, message_transmitter_program::MessageTransmitter>, system_program: Program<'info, System>, token_program: Program<'info, token::Token>, - /// CHECK: Wormhole Core Bridge needs the clock sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::clock::id())] - clock: AccountInfo<'info>, - - /// CHECK: Wormhole Core Bridge needs the rent sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::rent::id())] - rent: AccountInfo<'info>, + sysvars: RequiredSysvars<'info>, } /// TODO: add docstring pub fn execute_fast_order_cctp(ctx: Context) -> Result<()> { - match ctx.accounts.to_router_endpoint.protocol { + match ctx.accounts.execute_order.to_router_endpoint.protocol { MessageProtocol::Cctp { domain } => handle_execute_fast_order_cctp(ctx, domain), _ => err!(MatchingEngineError::InvalidCctpEndpoint), } @@ -193,20 +75,14 @@ pub fn handle_execute_fast_order_cctp( ctx: Context, destination_cctp_domain: u32, ) -> Result<()> { - let super::PreparedFastExecution { + let super::PreparedOrderExecution { user_amount: amount, fill, sequence_seed, - } = super::prepare_fast_execution(super::PrepareFastExecution { - custodian: &ctx.accounts.custodian, - auction_config: &ctx.accounts.auction_config, - fast_vaa: &ctx.accounts.fast_vaa, - auction: &mut ctx.accounts.auction, - cctp_mint_recipient: &ctx.accounts.cctp_mint_recipient, - executor_token: &ctx.accounts.executor_token, - best_offer_token: &ctx.accounts.best_offer_token, - initial_offer_token: &ctx.accounts.initial_offer_token, + } = super::prepare_order_execution(super::PrepareFastExecution { + execute_order: &mut ctx.accounts.execute_order, payer_sequence: &mut ctx.accounts.payer_sequence, + dst_token: &ctx.accounts.cctp.burn_source, token_program: &ctx.accounts.token_program, })?; @@ -214,6 +90,7 @@ pub fn handle_execute_fast_order_cctp( wormhole_cctp_solana::cpi::burn_and_publish( CpiContext::new_with_signer( ctx.accounts + .cctp .token_messenger_minter_program .to_account_info(), wormhole_cctp_solana::cpi::DepositForBurnWithCaller { @@ -221,31 +98,36 @@ pub fn handle_execute_fast_order_cctp( payer: ctx.accounts.payer.to_account_info(), token_messenger_minter_sender_authority: ctx .accounts + .cctp .token_messenger_minter_sender_authority .to_account_info(), - burn_token: ctx.accounts.cctp_mint_recipient.to_account_info(), + burn_token: ctx.accounts.cctp.burn_source.to_account_info(), message_transmitter_config: ctx .accounts + .cctp .message_transmitter_config .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(), - local_token: ctx.accounts.local_token.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), + token_messenger: ctx.accounts.cctp.token_messenger.to_account_info(), + remote_token_messenger: ctx.accounts.cctp.remote_token_messenger.to_account_info(), + token_minter: ctx.accounts.cctp.token_minter.to_account_info(), + local_token: ctx.accounts.cctp.local_token.to_account_info(), + mint: ctx.accounts.cctp.mint.to_account_info(), cctp_message: ctx.accounts.cctp_message.to_account_info(), message_transmitter_program: ctx .accounts + .cctp .message_transmitter_program .to_account_info(), token_messenger_minter_program: ctx .accounts + .cctp .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 + .cctp .token_messenger_minter_event_authority .to_account_info(), }, @@ -260,17 +142,17 @@ pub fn handle_execute_fast_order_cctp( ], ), CpiContext::new_with_signer( - ctx.accounts.core_bridge_program.to_account_info(), + ctx.accounts.wormhole.core_bridge_program.to_account_info(), wormhole_cctp_solana::cpi::PostMessage { payer: ctx.accounts.payer.to_account_info(), message: ctx.accounts.core_message.to_account_info(), emitter: ctx.accounts.custodian.to_account_info(), - config: ctx.accounts.core_bridge_config.to_account_info(), - emitter_sequence: ctx.accounts.core_emitter_sequence.to_account_info(), - fee_collector: ctx.accounts.core_fee_collector.to_account_info(), + config: ctx.accounts.wormhole.config.to_account_info(), + emitter_sequence: ctx.accounts.wormhole.emitter_sequence.to_account_info(), + fee_collector: ctx.accounts.wormhole.fee_collector.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), - clock: ctx.accounts.clock.to_account_info(), - rent: ctx.accounts.rent.to_account_info(), + clock: ctx.accounts.sysvars.clock.to_account_info(), + rent: ctx.accounts.sysvars.rent.to_account_info(), }, &[ Custodian::SIGNER_SEEDS, @@ -284,14 +166,37 @@ pub fn handle_execute_fast_order_cctp( ), wormhole_cctp_solana::cpi::BurnAndPublishArgs { burn_source: None, - destination_caller: ctx.accounts.to_router_endpoint.address, + destination_caller: ctx.accounts.execute_order.to_router_endpoint.address, destination_cctp_domain, amount, - mint_recipient: ctx.accounts.to_router_endpoint.mint_recipient, + mint_recipient: ctx.accounts.execute_order.to_router_endpoint.mint_recipient, wormhole_message_nonce: common::constants::WORMHOLE_MESSAGE_NONCE, payload: fill.to_vec_payload(), }, )?; - Ok(()) + // Finally close the account since it is no longer needed. + token::close_account(CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + token::CloseAccount { + account: ctx + .accounts + .execute_order + .active_auction + .custody_token + .to_account_info(), + destination: ctx.accounts.payer.to_account_info(), + authority: ctx + .accounts + .execute_order + .active_auction + .auction + .to_account_info(), + }, + &[&[ + Auction::SEED_PREFIX, + ctx.accounts.execute_order.active_auction.vaa_hash.as_ref(), + &[ctx.accounts.execute_order.active_auction.bump], + ]], + )) } diff --git a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/local.rs b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/local.rs index 0deeafa50..610ce0049 100644 --- a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/local.rs +++ b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/local.rs @@ -1,14 +1,10 @@ use crate::{ - error::MatchingEngineError, - state::{Auction, AuctionConfig, Custodian, MessageProtocol, PayerSequence, RouterEndpoint}, + composite::*, + state::{Auction, PayerSequence}, utils, }; use anchor_lang::prelude::*; use anchor_spl::token; -use common::{ - wormhole_cctp_solana::wormhole::{core_bridge_program, VaaAccount, SOLANA_CHAIN}, - wormhole_io::TypePrefixedPayload, -}; #[derive(Accounts)] pub struct ExecuteFastOrderLocal<'info> { @@ -27,155 +23,93 @@ pub struct ExecuteFastOrderLocal<'info> { )] payer_sequence: Account<'info, PayerSequence>, - /// This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source - /// authority for CCTP transfers. - /// - /// CHECK: Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - )] - custodian: AccountInfo<'info>, - - auction_config: Box>, - - /// CHECK: Must be owned by the Wormhole Core Bridge program. - #[account(owner = core_bridge_program::id())] - fast_vaa: AccountInfo<'info>, - + /// CHECK: Mutable. Seeds must be \["msg", payer, payer_sequence.value\]. #[account( mut, seeds = [ - Auction::SEED_PREFIX, - VaaAccount::load(&fast_vaa)?.digest().as_ref() - ], - bump = auction.bump, - constraint = utils::is_valid_active_auction( - &auction_config, - &auction, - Some(best_offer_token.key()), - Some(initial_offer_token.key()), - )? - )] - auction: Box>, - - #[account( - seeds = [ - RouterEndpoint::SEED_PREFIX, - SOLANA_CHAIN.to_be_bytes().as_ref(), + common::constants::CORE_MESSAGE_SEED_PREFIX, + payer.key().as_ref(), + &payer_sequence.value.to_be_bytes(), ], - bump = to_router_endpoint.bump, - constraint = to_router_endpoint.protocol != MessageProtocol::None @ MatchingEngineError::EndpointDisabled, - )] - to_router_endpoint: Account<'info, RouterEndpoint>, - - #[account( - mut, - token::mint = common::constants::USDC_MINT, + bump, )] - executor_token: Box>, + core_message: AccountInfo<'info>, - /// CHECK: Mutable. Must equal [best_offer](Auction::best_offer). - #[account(mut)] - best_offer_token: AccountInfo<'info>, + custodian: CheckedCustodian<'info>, - /// CHECK: Mutable. Must equal [initial_offer](Auction::initial_offer). - #[account(mut)] - initial_offer_token: AccountInfo<'info>, - - /// Also the burn_source token account. - /// - /// CHECK: Mutable. Seeds must be \["custody"\]. - #[account( - mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, - )] - cctp_mint_recipient: AccountInfo<'info>, + #[account(constraint = utils::require_local_endpoint(&execute_order.to_router_endpoint)?)] + execute_order: ExecuteOrder<'info>, - /// CHECK: Seeds must be \["Bridge"\] (Wormhole Core Bridge program). - #[account(mut)] - core_bridge_config: UncheckedAccount<'info>, + wormhole: WormholePublishMessage<'info>, - /// CHECK: Mutable. Seeds must be \["msg", payer, payer_sequence.value\]. #[account( mut, seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, - payer.key().as_ref(), - payer_sequence.value.to_be_bytes().as_ref(), + crate::LOCAL_CUSTODY_TOKEN_SEED_PREFIX, + &execute_order.fast_vaa.load_unchecked().emitter_chain().to_be_bytes(), ], bump, )] - core_message: AccountInfo<'info>, + local_custody_token: Box>, - /// CHECK: Seeds must be \["Sequence"\, custodian] (Wormhole Core Bridge program). - #[account(mut)] - core_emitter_sequence: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["fee_collector"\] (Wormhole Core Bridge program). - #[account(mut)] - core_fee_collector: UncheckedAccount<'info>, - - core_bridge_program: Program<'info, core_bridge_program::CoreBridge>, system_program: Program<'info, System>, token_program: Program<'info, token::Token>, - /// CHECK: Wormhole Core Bridge needs the clock sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::clock::id())] - clock: AccountInfo<'info>, - - /// CHECK: Wormhole Core Bridge needs the rent sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::rent::id())] - rent: AccountInfo<'info>, + sysvars: RequiredSysvars<'info>, } pub fn execute_fast_order_local(ctx: Context) -> Result<()> { - let super::PreparedFastExecution { + let super::PreparedOrderExecution { user_amount: amount, fill, sequence_seed, - } = super::prepare_fast_execution(super::PrepareFastExecution { - custodian: &ctx.accounts.custodian, - auction_config: &ctx.accounts.auction_config, - fast_vaa: &ctx.accounts.fast_vaa, - auction: &mut ctx.accounts.auction, - cctp_mint_recipient: &ctx.accounts.cctp_mint_recipient, - executor_token: &ctx.accounts.executor_token, - best_offer_token: &ctx.accounts.best_offer_token, - initial_offer_token: &ctx.accounts.initial_offer_token, + } = super::prepare_order_execution(super::PrepareFastExecution { + execute_order: &mut ctx.accounts.execute_order, payer_sequence: &mut ctx.accounts.payer_sequence, + dst_token: &ctx.accounts.local_custody_token, token_program: &ctx.accounts.token_program, })?; // Publish message via Core Bridge. - core_bridge_program::cpi::post_message( - CpiContext::new_with_signer( - ctx.accounts.core_bridge_program.to_account_info(), - core_bridge_program::cpi::PostMessage { - payer: ctx.accounts.payer.to_account_info(), - message: ctx.accounts.core_message.to_account_info(), - emitter: ctx.accounts.custodian.to_account_info(), - config: ctx.accounts.core_bridge_config.to_account_info(), - emitter_sequence: ctx.accounts.core_emitter_sequence.to_account_info(), - fee_collector: ctx.accounts.core_fee_collector.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - clock: ctx.accounts.clock.to_account_info(), - rent: ctx.accounts.rent.to_account_info(), - }, - &[ - Custodian::SIGNER_SEEDS, - &[ - common::constants::CORE_MESSAGE_SEED_PREFIX, - ctx.accounts.payer.key().as_ref(), - sequence_seed.as_ref(), - &[ctx.bumps.core_message], - ], - ], - ), - core_bridge_program::cpi::PostMessageArgs { - nonce: common::constants::WORMHOLE_MESSAGE_NONCE, - payload: common::messages::FastFill { amount, fill }.to_vec_payload(), - commitment: core_bridge_program::Commitment::Finalized, + // + // NOTE: We cannot close the custody account yet because the user needs to be able to retrieve + // the funds when they complete the fast fill. + utils::wormhole::post_matching_engine_message( + utils::wormhole::PostMatchingEngineMessage { + wormhole: &ctx.accounts.wormhole, + core_message: &ctx.accounts.core_message, + custodian: &ctx.accounts.custodian, + payer: &ctx.accounts.payer, + system_program: &ctx.accounts.system_program, + sysvars: &ctx.accounts.sysvars, }, - ) + common::messages::FastFill { amount, fill }, + &sequence_seed, + ctx.bumps.core_message, + )?; + + // Finally close the account since it is no longer needed. + token::close_account(CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + token::CloseAccount { + account: ctx + .accounts + .execute_order + .active_auction + .custody_token + .to_account_info(), + destination: ctx.accounts.payer.to_account_info(), + authority: ctx + .accounts + .execute_order + .active_auction + .auction + .to_account_info(), + }, + &[&[ + Auction::SEED_PREFIX, + ctx.accounts.execute_order.active_auction.vaa_hash.as_ref(), + &[ctx.accounts.execute_order.active_auction.bump], + ]], + )) } diff --git a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs index 2959cb904..d33e995db 100644 --- a/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs +++ b/solana/programs/matching-engine/src/processor/auction/execute_fast_order/mod.rs @@ -5,85 +5,93 @@ mod local; pub use local::*; use crate::{ + composite::*, error::MatchingEngineError, - state::{Auction, AuctionConfig, AuctionStatus, Custodian, PayerSequence}, + state::{Auction, AuctionStatus, PayerSequence}, utils::{self, auction::DepositPenalty}, }; use anchor_lang::prelude::*; use anchor_spl::token; -use common::{ - messages::{raw::LiquidityLayerPayload, Fill}, - wormhole_cctp_solana::wormhole::VaaAccount, -}; +use common::messages::{raw::LiquidityLayerPayload, Fill}; struct PrepareFastExecution<'ctx, 'info> { - custodian: &'ctx AccountInfo<'info>, - auction_config: &'ctx Account<'info, AuctionConfig>, - fast_vaa: &'ctx AccountInfo<'info>, - auction: &'ctx mut Box>, - cctp_mint_recipient: &'ctx AccountInfo<'info>, - executor_token: &'ctx Account<'info, token::TokenAccount>, - best_offer_token: &'ctx AccountInfo<'info>, - initial_offer_token: &'ctx AccountInfo<'info>, + execute_order: &'ctx mut ExecuteOrder<'info>, payer_sequence: &'ctx mut Account<'info, PayerSequence>, + dst_token: &'ctx Account<'info, token::TokenAccount>, token_program: &'ctx Program<'info, token::Token>, } -struct PreparedFastExecution { +struct PreparedOrderExecution { pub user_amount: u64, pub fill: Fill, pub sequence_seed: [u8; 8], } -fn prepare_fast_execution(accounts: PrepareFastExecution) -> Result { +fn prepare_order_execution(accounts: PrepareFastExecution) -> Result { let PrepareFastExecution { - custodian, - auction_config, - fast_vaa, - auction, - cctp_mint_recipient, - executor_token, - best_offer_token, - initial_offer_token, + execute_order, payer_sequence, + dst_token, token_program, } = accounts; - // Create zero copy reference to `FastMarketOrder` payload. - let fast_vaa = VaaAccount::load_unchecked(fast_vaa); + let ExecuteOrder { + fast_vaa, + active_auction, + to_router_endpoint: _, + executor_token, + initial_offer_token, + } = execute_order; + + let fast_vaa = fast_vaa.load_unchecked(); let order = LiquidityLayerPayload::try_from(fast_vaa.payload()) .map_err(|_| MatchingEngineError::InvalidVaa)? .message() .to_fast_market_order_unchecked(); + let ActiveAuction { + auction, + custody_token, + config, + best_offer_token, + } = active_auction; + + // Create zero copy reference to `FastMarketOrder` payload. + let (user_amount, new_status) = { let auction_info = auction.info.as_ref().unwrap(); - let current_slot = Clock::get().map(|clock| clock.slot)?; + let current_slot = Clock::get().unwrap().slot; require!( - current_slot > auction_info.auction_end_slot(auction_config), + auction_info.end_early || current_slot > auction_info.auction_end_slot(config), MatchingEngineError::AuctionPeriodNotExpired ); let DepositPenalty { penalty, user_reward, - } = utils::auction::compute_deposit_penalty(auction_config, auction_info, current_slot); + } = utils::auction::compute_deposit_penalty(config, auction_info, current_slot); let mut deposit_and_fee = auction_info.offer_price + auction_info.security_deposit - user_reward; + let auction_signer_seeds = &[ + Auction::SEED_PREFIX, + auction.vaa_hash.as_ref(), + &[auction.bump], + ]; + if penalty > 0 && best_offer_token.key() != executor_token.key() { // Pay the liquidator the penalty. token::transfer( CpiContext::new_with_signer( token_program.to_account_info(), anchor_spl::token::Transfer { - from: cctp_mint_recipient.to_account_info(), + from: custody_token.to_account_info(), to: executor_token.to_account_info(), - authority: custodian.to_account_info(), + authority: auction.to_account_info(), }, - &[Custodian::SIGNER_SEEDS], + &[auction_signer_seeds], ), penalty, )?; @@ -98,11 +106,11 @@ fn prepare_fast_execution(accounts: PrepareFastExecution) -> Result Result 0 { Some(penalty) } else { None }, + }, ) }; // Set the auction status to completed. auction.status = new_status; - Ok(PreparedFastExecution { + Ok(PreparedOrderExecution { user_amount, fill: Fill { source_chain: fast_vaa.emitter_chain(), diff --git a/solana/programs/matching-engine/src/processor/auction/offer/improve.rs b/solana/programs/matching-engine/src/processor/auction/offer/improve.rs index 1ecb8afff..9ba0002e8 100644 --- a/solana/programs/matching-engine/src/processor/auction/offer/improve.rs +++ b/solana/programs/matching-engine/src/processor/auction/offer/improve.rs @@ -1,106 +1,93 @@ -use crate::{ - error::MatchingEngineError, - state::{Auction, AuctionConfig, Custodian}, - utils, -}; +use crate::{composite::*, error::MatchingEngineError, state::Auction, utils}; use anchor_lang::prelude::*; use anchor_spl::token; #[derive(Accounts)] +#[instruction(offer_price: u64)] pub struct ImproveOffer<'info> { - /// This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source - /// authority for CCTP transfers. - /// - /// CHECK: Seeds must be \["emitter"\]. #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - )] - custodian: AccountInfo<'info>, + constraint = { + // This is safe because we know that this is an active auction. + let info = active_auction.info.as_ref().unwrap(); - auction_config: Account<'info, AuctionConfig>, + require!( + !info.end_early + && Clock::get().unwrap().slot <= info.auction_end_slot(&active_auction.config), + MatchingEngineError::AuctionPeriodExpired + ); - offer_authority: Signer<'info>, + require!( + offer_price + <= utils::auction::compute_min_allowed_offer(&active_auction.config, info), + MatchingEngineError::CarpingNotAllowed + ); - #[account( - mut, - seeds = [ - Auction::SEED_PREFIX, - auction.vaa_hash.as_ref(), - ], - bump = auction.bump, - constraint = utils::is_valid_active_auction( - &auction_config, - &auction, - Some(best_offer_token.key()), - None, - )? + true + } )] - auction: Account<'info, Auction>, + active_auction: ActiveAuction<'info>, - #[account( - mut, - associated_token::mint = common::constants::USDC_MINT, - associated_token::authority = offer_authority - )] - offer_token: Account<'info, token::TokenAccount>, - - /// CHECK: Mutable. Must have the same key in auction data. - #[account(mut)] - best_offer_token: AccountInfo<'info>, + /// CHECK: Must be a token account, whose mint is `USDC_MINT` and have delegated authority to + /// the auction PDA. + offer_token: AccountInfo<'info>, token_program: Program<'info, token::Token>, } -pub fn improve_offer(ctx: Context, fee_offer: u64) -> Result<()> { - let auction_info = ctx.accounts.auction.info.as_mut().unwrap(); - - { - let current_slot = Clock::get().map(|clock| clock.slot)?; - require!( - current_slot <= auction_info.auction_end_slot(&ctx.accounts.auction_config), - MatchingEngineError::AuctionPeriodExpired - ); - } +pub fn improve_offer(ctx: Context, offer_price: u64) -> Result<()> { + let ActiveAuction { + auction, + custody_token, + best_offer_token, + config: _, + } = &ctx.accounts.active_auction; - // Make sure the new offer is less than the previous offer. - require!( - fee_offer < auction_info.offer_price, - MatchingEngineError::OfferPriceNotImproved - ); - - // This check is safe because we already checked that `fee_offer` is less than `offer_price`. - { - let min_offer_delta = - utils::auction::compute_min_offer_delta(&ctx.accounts.auction_config, auction_info); - require!( - auction_info.offer_price - fee_offer >= min_offer_delta, - MatchingEngineError::CarpingNotAllowed - ); - } + let offer_token = &ctx.accounts.offer_token; + let token_program = &ctx.accounts.token_program; // Transfer funds from the `offer_token` token account to the `best_offer_token` token account, // but only if the pubkeys are different. - let offer_token = ctx.accounts.offer_token.key(); - if auction_info.best_offer_token != offer_token { + if offer_token.key() != best_offer_token.key() { + // These operations will seem silly, but we do this as a safety measure to ensure that + // nothing terrible happened with the auction's custody account. + let total_deposit = ctx.accounts.active_auction.custody_token.amount; + + let auction_signer_seeds = &[ + Auction::SEED_PREFIX, + auction.vaa_hash.as_ref(), + &[auction.bump], + ]; + token::transfer( CpiContext::new_with_signer( - ctx.accounts.token_program.to_account_info(), + token_program.to_account_info(), anchor_spl::token::Transfer { - from: ctx.accounts.offer_token.to_account_info(), - to: ctx.accounts.best_offer_token.to_account_info(), - authority: ctx.accounts.custodian.to_account_info(), + from: custody_token.to_account_info(), + to: best_offer_token.to_account_info(), + authority: auction.to_account_info(), }, - &[Custodian::SIGNER_SEEDS], + &[auction_signer_seeds], ), - auction_info.total_deposit(), + total_deposit, )?; - // Update the `best_offer` token account and `amount` fields. - auction_info.best_offer_token = offer_token; + token::transfer( + CpiContext::new_with_signer( + token_program.to_account_info(), + anchor_spl::token::Transfer { + from: offer_token.to_account_info(), + to: custody_token.to_account_info(), + authority: auction.to_account_info(), + }, + &[auction_signer_seeds], + ), + total_deposit, + )?; } - auction_info.offer_price = fee_offer; + let info = ctx.accounts.active_auction.info.as_mut().unwrap(); + info.best_offer_token = offer_token.key(); + info.offer_price = offer_price; Ok(()) } diff --git a/solana/programs/matching-engine/src/processor/auction/offer/place_initial.rs b/solana/programs/matching-engine/src/processor/auction/offer/place_initial.rs index 5d02b2b38..bb0d136d9 100644 --- a/solana/programs/matching-engine/src/processor/auction/offer/place_initial.rs +++ b/solana/programs/matching-engine/src/processor/auction/offer/place_initial.rs @@ -1,34 +1,20 @@ -use anchor_lang::prelude::*; -use anchor_spl::token; -use common::{ - messages::raw::LiquidityLayerPayload, - wormhole_cctp_solana::wormhole::{core_bridge_program, VaaAccount}, -}; - use crate::{ + composite::*, error::MatchingEngineError, - state::{ - Auction, AuctionConfig, AuctionInfo, AuctionStatus, Custodian, MessageProtocol, - RouterEndpoint, - }, + state::{Auction, AuctionConfig, AuctionInfo, AuctionStatus}, }; +use anchor_lang::prelude::*; +use anchor_spl::token; +use common::messages::raw::LiquidityLayerMessage; #[derive(Accounts)] pub struct PlaceInitialOffer<'info> { #[account(mut)] payer: Signer<'info>, - // TODO: add initial_offer authority. Do not require to be owner? - // initial_offer_authority: Signer<'info>, - // - /// This program's Wormhole (Core Bridge) emitter authority. - /// - /// CHECK: Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - )] - custodian: Account<'info, Custodian>, + /// NOTE: Currently not used for anything. But this account can be used in + /// case we need to pause starting auctions. + custodian: CheckedCustodian<'info>, #[account( constraint = { @@ -42,9 +28,7 @@ pub struct PlaceInitialOffer<'info> { )] auction_config: Account<'info, AuctionConfig>, - /// CHECK: Must be owned by the Wormhole Core Bridge program. - #[account(owner = core_bridge_program::id())] - fast_vaa: AccountInfo<'info>, + fast_order_path: FastOrderPath<'info>, /// This account should only be created once, and should never be changed to /// init_if_needed. Otherwise someone can game an existing auction. @@ -54,122 +38,110 @@ pub struct PlaceInitialOffer<'info> { space = 8 + Auction::INIT_SPACE, seeds = [ Auction::SEED_PREFIX, - VaaAccount::load(&fast_vaa)?.digest().as_ref(), + fast_order_path.fast_vaa.load_unchecked().digest().as_ref(), ], bump )] auction: Box>, - #[account( - seeds = [ - RouterEndpoint::SEED_PREFIX, - from_router_endpoint.chain.to_be_bytes().as_ref(), - ], - bump = from_router_endpoint.bump, - constraint = from_router_endpoint.protocol != MessageProtocol::None @ MatchingEngineError::EndpointDisabled, - )] - from_router_endpoint: Account<'info, RouterEndpoint>, + /// CHECK: Must be a token account, whose mint is `USDC_MINT` and have delegated authority to + /// the auction PDA. + offer_token: AccountInfo<'info>, #[account( + init, + payer = payer, + token::mint = usdc, + token::authority = auction, seeds = [ - RouterEndpoint::SEED_PREFIX, - to_router_endpoint.chain.to_be_bytes().as_ref(), + crate::AUCTION_CUSTODY_TOKEN_SEED_PREFIX, + auction.key().as_ref(), ], - bump = to_router_endpoint.bump, - constraint = to_router_endpoint.protocol != MessageProtocol::None @ MatchingEngineError::EndpointDisabled, + bump, )] - to_router_endpoint: Account<'info, RouterEndpoint>, + auction_custody_token: Account<'info, token::TokenAccount>, - #[account( - mut, - associated_token::mint = cctp_mint_recipient.mint, - associated_token::authority = payer - )] - offer_token: Account<'info, token::TokenAccount>, - - #[account( - mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, - )] - cctp_mint_recipient: Account<'info, token::TokenAccount>, + usdc: Usdc<'info>, system_program: Program<'info, System>, token_program: Program<'info, token::Token>, } -pub fn place_initial_offer(ctx: Context, fee_offer: u64) -> Result<()> { +pub fn place_initial_offer(ctx: Context, offer_price: u64) -> Result<()> { // Create zero copy reference to `FastMarketOrder` payload. - let fast_vaa = VaaAccount::load(&ctx.accounts.fast_vaa)?; - let msg = LiquidityLayerPayload::try_from(fast_vaa.payload()) - .map_err(|_| MatchingEngineError::InvalidVaa)? - .message(); - let fast_order = msg - .fast_market_order() - .ok_or(MatchingEngineError::NotFastMarketOrder)?; + let fast_vaa = ctx.accounts.fast_order_path.fast_vaa.load_unchecked(); + let order = LiquidityLayerMessage::try_from(fast_vaa.payload()) + .unwrap() + .to_fast_market_order_unchecked(); let source_chain = fast_vaa.emitter_chain(); // We need to fetch clock values for a couple of operations in this instruction. - let Clock { - slot, - unix_timestamp, - .. - } = Clock::get()?; - - // Check to see if the deadline has expired. - let deadline = i64::from(fast_order.deadline()); + let start_slot = { + let Clock { + slot, + unix_timestamp, + .. + } = Clock::get().unwrap(); + + // Check to see if the deadline has expired. + let deadline = i64::from(order.deadline()); + require!( + deadline == 0 || unix_timestamp < deadline, + MatchingEngineError::FastMarketOrderExpired, + ); + + slot + }; + + let max_fee = order.max_fee(); require!( - deadline == 0 || unix_timestamp < deadline, - MatchingEngineError::FastMarketOrderExpired, + offer_price <= max_fee, + MatchingEngineError::OfferPriceTooHigh ); - let max_fee = fast_order.max_fee(); - require!(fee_offer <= max_fee, MatchingEngineError::OfferPriceTooHigh); - - // Verify that the to and from router endpoints are valid. - crate::utils::require_valid_router_path( - &fast_vaa, - &ctx.accounts.from_router_endpoint, - &ctx.accounts.to_router_endpoint, - fast_order.target_chain(), - )?; - // Parse the transfer amount from the VAA. - let amount_in = fast_order.amount_in(); - - // Transfer tokens from the offer authority's token account to the custodian. - token::transfer( - CpiContext::new_with_signer( - ctx.accounts.token_program.to_account_info(), - anchor_spl::token::Transfer { - from: ctx.accounts.offer_token.to_account_info(), - to: ctx.accounts.cctp_mint_recipient.to_account_info(), - authority: ctx.accounts.custodian.to_account_info(), - }, - &[Custodian::SIGNER_SEEDS], - ), - amount_in + max_fee, - )?; + let amount_in = order.amount_in(); // Set up the Auction account for this auction. let initial_offer_token = ctx.accounts.offer_token.key(); + let vaa_hash = fast_vaa.digest().0; ctx.accounts.auction.set_inner(Auction { bump: ctx.bumps.auction, - vaa_hash: fast_vaa.digest().0, + vaa_hash, status: AuctionStatus::Active, info: Some(AuctionInfo { config_id: ctx.accounts.auction_config.id, + custody_token_bump: ctx.bumps.auction_custody_token, vaa_sequence: fast_vaa.sequence(), source_chain, best_offer_token: initial_offer_token, initial_offer_token, - start_slot: slot, + start_slot, amount_in, security_deposit: max_fee, - offer_price: fee_offer, + offer_price, amount_out: amount_in, + end_early: false, }), }); - Ok(()) + // Finally transfer tokens from the offer authority's token account to the + // auction's custody account. + token::transfer( + CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + anchor_spl::token::Transfer { + from: ctx.accounts.offer_token.to_account_info(), + to: ctx.accounts.auction_custody_token.to_account_info(), + authority: ctx.accounts.auction.to_account_info(), + }, + &[&[ + Auction::SEED_PREFIX, + vaa_hash.as_ref(), + &[ctx.bumps.auction], + ]], + ), + amount_in + max_fee, + ) } diff --git a/solana/programs/matching-engine/src/processor/auction/prepare_settlement/cctp.rs b/solana/programs/matching-engine/src/processor/auction/prepare_settlement/cctp.rs index 78f85e808..926e507dc 100644 --- a/solana/programs/matching-engine/src/processor/auction/prepare_settlement/cctp.rs +++ b/solana/programs/matching-engine/src/processor/auction/prepare_settlement/cctp.rs @@ -1,16 +1,13 @@ use crate::{ + composite::*, error::MatchingEngineError, - state::{Custodian, PreparedOrderResponse}, + state::{Auction, AuctionStatus, Custodian, PreparedOrderResponse}, }; use anchor_lang::prelude::*; use anchor_spl::token; use common::{ messages::raw::{LiquidityLayerDepositMessage, LiquidityLayerMessage}, - wormhole_cctp_solana::{ - self, - cctp::{message_transmitter_program, token_messenger_minter_program}, - wormhole::{core_bridge_program, VaaAccount}, - }, + wormhole_cctp_solana::{self, cctp::message_transmitter_program, wormhole::VaaAccount}, }; #[derive(Accounts)] @@ -18,95 +15,94 @@ pub struct PrepareOrderResponseCctp<'info> { #[account(mut)] payer: Signer<'info>, - /// This program's Wormhole (Core Bridge) emitter authority. - /// - /// CHECK: Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - )] - custodian: Box>, + custodian: CheckedCustodian<'info>, - /// CHECK: Must be owned by the Wormhole Core Bridge program. This account will be read via - /// zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader. - #[account(owner = core_bridge_program::id())] - fast_vaa: AccountInfo<'info>, + fast_vaa: LiquidityLayerVaa<'info>, - /// CHECK: Must be owned by the Wormhole Core Bridge program. Ownership check happens in - /// [verify_vaa_and_mint](wormhole_cctp_solana::cpi::verify_vaa_and_mint). - finalized_vaa: AccountInfo<'info>, + #[account( + constraint = { + // Fast and finalized VAAs must reconcile with each other. + let fast_vaa = fast_vaa.load_unchecked(); + let finalized_vaa = finalized_vaa.load_unchecked(); + + require_eq!( + fast_vaa.emitter_chain(), + finalized_vaa.emitter_chain(), + MatchingEngineError::VaaMismatch + ); + require!( + fast_vaa.emitter_address() == finalized_vaa.emitter_address(), + MatchingEngineError::VaaMismatch + ); + require_eq!( + fast_vaa.sequence(), + finalized_vaa.sequence() + 1, + MatchingEngineError::VaaMismatch + ); + require!( + fast_vaa.timestamp() == finalized_vaa.timestamp(), + MatchingEngineError::VaaMismatch + ); + + // Make sure the finalized VAA is a slow order response encoded in a deposit. + let finalized_msg = LiquidityLayerMessage::try_from(finalized_vaa.payload()).unwrap(); + let deposit = finalized_msg + .deposit() + .ok_or(MatchingEngineError::InvalidPayloadId)?; + let deposit_msg = LiquidityLayerDepositMessage::try_from(deposit.payload()) + .map_err(|_| error!(MatchingEngineError::InvalidDepositMessage))?; + let slow_order_response = deposit_msg + .slow_order_response() + .ok_or(MatchingEngineError::InvalidDepositPayloadId)?; + + true + } + )] + finalized_vaa: LiquidityLayerVaa<'info>, #[account( - init, + init_if_needed, payer = payer, space = 8 + PreparedOrderResponse::INIT_SPACE, seeds = [ PreparedOrderResponse::SEED_PREFIX, - payer.key().as_ref(), VaaAccount::load(&fast_vaa)?.digest().as_ref() ], bump, )] - prepared_order_response: Account<'info, PreparedOrderResponse>, - - /// Mint recipient token account, which is encoded as the mint recipient in the CCTP message. - /// The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message - /// from its custody account to this account. - /// - /// CHECK: Mutable. Seeds must be \["custody"\]. - /// - /// NOTE: This account must be encoded as the mint recipient in the CCTP message. + prepared_order_response: Box>, + #[account( - mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, + init_if_needed, + payer = payer, + token::mint = usdc, + token::authority = prepared_order_response, + seeds = [ + crate::PREPARED_CUSTODY_TOKEN_SEED_PREFIX, + prepared_order_response.key().as_ref(), + ], + bump, )] - cctp_mint_recipient: AccountInfo<'info>, - - /// CHECK: Seeds must be \["message_transmitter_authority"\] (CCTP Message Transmitter program). - message_transmitter_authority: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["message_transmitter"\] (CCTP Message Transmitter program). - 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: AccountInfo<'info>, - - /// CHECK: Seeds must be \["token_messenger"\] (CCTP Token Messenger Minter program). - token_messenger: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["remote_token_messenger"\, remote_domain.to_string()] (CCTP Token - /// Messenger Minter program). - remote_token_messenger: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["token_minter"\] (CCTP Token Messenger Minter program). - token_minter: UncheckedAccount<'info>, + prepared_custody_token: Box>, - /// Token Messenger Minter's Local Token account. This program uses the mint of this account to - /// validate the `mint_recipient` token account's mint. - /// - /// CHECK: Mutable. Seeds must be \["local_token", mint\] (CCTP Token Messenger Minter program). - #[account(mut)] - local_token: UncheckedAccount<'info>, + #[account( + mut, + constraint = { + require_eq!( + &auction.status, + &AuctionStatus::Active, + MatchingEngineError::AuctionNotActive + ); + + true + } + )] + auction: Option>>, - /// CHECK: Seeds must be \["token_pair", remote_domain.to_string(), remote_token_address\] (CCTP - /// Token Messenger Minter program). - token_pair: UncheckedAccount<'info>, + usdc: Usdc<'info>, - /// CHECK: Mutable. Seeds must be \["custody", mint\] (CCTP Token Messenger Minter program). - #[account(mut)] - token_messenger_minter_custody_token: UncheckedAccount<'info>, + cctp: CctpReceiveMessage<'info>, - /// CHECK: Seeds must be \["__event_authority"\] (CCTP Token Messenger Minter program). - token_messenger_minter_event_authority: AccountInfo<'info>, - - token_messenger_minter_program: - Program<'info, token_messenger_minter_program::TokenMessengerMinter>, - message_transmitter_program: Program<'info, message_transmitter_program::MessageTransmitter>, token_program: Program<'info, token::Token>, system_program: Program<'info, System>, } @@ -121,50 +117,68 @@ pub fn prepare_order_response_cctp( ctx: Context, args: CctpMessageArgs, ) -> Result<()> { - let fast_vaa = VaaAccount::load_unchecked(&ctx.accounts.fast_vaa); + match ctx.accounts.prepared_order_response.source_chain { + 0 => handle_prepare_order_response_cctp(ctx, args), + _ => super::prepare_order_response_noop(), + } +} +fn handle_prepare_order_response_cctp( + ctx: Context, + args: CctpMessageArgs, +) -> Result<()> { let finalized_vaa = wormhole_cctp_solana::cpi::verify_vaa_and_mint( &ctx.accounts.finalized_vaa, CpiContext::new_with_signer( - ctx.accounts.message_transmitter_program.to_account_info(), + ctx.accounts + .cctp + .message_transmitter_program + .to_account_info(), message_transmitter_program::cpi::ReceiveTokenMessengerMinterMessage { payer: ctx.accounts.payer.to_account_info(), caller: ctx.accounts.custodian.to_account_info(), message_transmitter_authority: ctx .accounts + .cctp .message_transmitter_authority .to_account_info(), message_transmitter_config: ctx .accounts + .cctp .message_transmitter_config .to_account_info(), - used_nonces: ctx.accounts.used_nonces.to_account_info(), + used_nonces: ctx.accounts.cctp.used_nonces.to_account_info(), token_messenger_minter_program: ctx .accounts + .cctp .token_messenger_minter_program .to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), message_transmitter_event_authority: ctx .accounts + .cctp .message_transmitter_event_authority .to_account_info(), message_transmitter_program: ctx .accounts + .cctp .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(), - local_token: ctx.accounts.local_token.to_account_info(), - token_pair: ctx.accounts.token_pair.to_account_info(), - mint_recipient: ctx.accounts.cctp_mint_recipient.to_account_info(), + token_messenger: ctx.accounts.cctp.token_messenger.to_account_info(), + remote_token_messenger: ctx.accounts.cctp.remote_token_messenger.to_account_info(), + token_minter: ctx.accounts.cctp.token_minter.to_account_info(), + local_token: ctx.accounts.cctp.local_token.to_account_info(), + token_pair: ctx.accounts.cctp.token_pair.to_account_info(), + mint_recipient: ctx.accounts.cctp.mint_recipient.to_account_info(), custody_token: ctx .accounts + .cctp .token_messenger_minter_custody_token .to_account_info(), token_program: ctx.accounts.token_program.to_account_info(), token_messenger_minter_event_authority: ctx .accounts + .cctp .token_messenger_minter_event_authority .to_account_info(), }, @@ -176,32 +190,6 @@ pub fn prepare_order_response_cctp( }, )?; - // Reconcile fast VAA with finalized VAA. - let source_chain = { - let fast_emitter = fast_vaa.emitter_info(); - let finalized_emitter = finalized_vaa.emitter_info(); - require_eq!( - fast_emitter.chain, - finalized_emitter.chain, - MatchingEngineError::VaaMismatch - ); - require!( - fast_emitter.address == finalized_emitter.address, - MatchingEngineError::VaaMismatch - ); - require_eq!( - fast_emitter.sequence, - finalized_emitter.sequence + 1, - MatchingEngineError::VaaMismatch - ); - require!( - fast_vaa.timestamp() == finalized_vaa.timestamp(), - MatchingEngineError::VaaMismatch - ); - - finalized_emitter.chain - }; - // This should be infallible because: // 1. We know that the fast VAA was used to start this auction (using its hash for the // auction data PDA). @@ -209,16 +197,18 @@ pub fn prepare_order_response_cctp( // // However, we will still process results in case Token Router implementation renders any of // these assumptions invalid. - let finalized_msg = LiquidityLayerMessage::try_from(finalized_vaa.payload()) - .map_err(|_| error!(MatchingEngineError::InvalidVaa))?; - let deposit = finalized_msg - .deposit() - .ok_or(MatchingEngineError::InvalidPayloadId)?; - let deposit_msg = LiquidityLayerDepositMessage::try_from(deposit.payload()) - .map_err(|_| error!(MatchingEngineError::InvalidDepositMessage))?; - let slow_order_response = deposit_msg - .slow_order_response() - .ok_or(MatchingEngineError::InvalidDepositPayloadId)?; + let finalized_msg = LiquidityLayerMessage::try_from(finalized_vaa.payload()).unwrap(); + let deposit = finalized_msg.to_deposit_unchecked(); + let base_fee = LiquidityLayerDepositMessage::try_from(deposit.payload()) + .unwrap() + .to_slow_order_response_unchecked() + .base_fee(); + + let fast_vaa = ctx.accounts.fast_vaa.load_unchecked(); + let amount = LiquidityLayerMessage::try_from(fast_vaa.payload()) + .unwrap() + .to_fast_market_order_unchecked() + .amount_in(); // Write to the prepared slow order account, which will be closed by one of the following // instructions: @@ -231,10 +221,32 @@ pub fn prepare_order_response_cctp( bump: ctx.bumps.prepared_order_response, fast_vaa_hash: fast_vaa.digest().0, prepared_by: ctx.accounts.payer.key(), - source_chain, - base_fee: slow_order_response.base_fee(), + source_chain: finalized_vaa.emitter_chain(), + base_fee, }); - // Done. - Ok(()) + // This method overrides the offer price with the base fee if there is an active auction. We do + // this to ensure that the user does not overpay for a transfer that has already finalized + // during an auction. + if let Some(auction) = &mut ctx.accounts.auction { + msg!("AuctionStatus::Active"); + + let info = auction.info.as_mut().unwrap(); + info.offer_price = base_fee; + info.end_early = true; + } + + // Finally transfer minted via CCTP to prepared custody token. + token::transfer( + CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + token::Transfer { + from: ctx.accounts.cctp.mint_recipient.to_account_info(), + to: ctx.accounts.prepared_custody_token.to_account_info(), + authority: ctx.accounts.custodian.to_account_info(), + }, + &[Custodian::SIGNER_SEEDS], + ), + amount, + ) } diff --git a/solana/programs/matching-engine/src/processor/auction/prepare_settlement/mod.rs b/solana/programs/matching-engine/src/processor/auction/prepare_settlement/mod.rs index c143ed248..cd21ed9e1 100644 --- a/solana/programs/matching-engine/src/processor/auction/prepare_settlement/mod.rs +++ b/solana/programs/matching-engine/src/processor/auction/prepare_settlement/mod.rs @@ -1,2 +1,11 @@ mod cctp; pub use cctp::*; + +use anchor_lang::prelude::*; + +fn prepare_order_response_noop() -> Result<()> { + msg!("Already prepared"); + + // No-op. + Ok(()) +} diff --git a/solana/programs/matching-engine/src/processor/auction/settle/active/cctp.rs b/solana/programs/matching-engine/src/processor/auction/settle/active/cctp.rs deleted file mode 100644 index 87b4fa0b8..000000000 --- a/solana/programs/matching-engine/src/processor/auction/settle/active/cctp.rs +++ /dev/null @@ -1,315 +0,0 @@ -use crate::{ - error::MatchingEngineError, - state::{ - Auction, AuctionConfig, Custodian, MessageProtocol, PayerSequence, PreparedOrderResponse, - RouterEndpoint, - }, - utils, -}; -use anchor_lang::prelude::*; -use anchor_spl::token; -use common::{ - wormhole_cctp_solana::{ - self, - cctp::{message_transmitter_program, token_messenger_minter_program}, - wormhole::core_bridge_program, - }, - wormhole_io::TypePrefixedPayload, -}; - -/// Accounts required for [settle_auction_active_cctp]. -#[derive(Accounts)] -pub struct SettleAuctionActiveCctp<'info> { - #[account( - mut, - address = prepared_order_response.prepared_by, // TODO: add err - )] - payer: Signer<'info>, - - #[account( - init_if_needed, - payer = payer, - space = 8 + PayerSequence::INIT_SPACE, - seeds = [ - PayerSequence::SEED_PREFIX, - payer.key().as_ref() - ], - bump, - )] - payer_sequence: Box>, - - /// This program's Wormhole (Core Bridge) emitter authority. - /// - /// CHECK: Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - )] - custodian: AccountInfo<'info>, - - auction_config: Box>, - - /// CHECK: Must be owned by the Wormhole Core Bridge program. This account will be read via - /// zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader. - #[account(owner = core_bridge_program::id())] - fast_vaa: AccountInfo<'info>, - - #[account( - mut, - close = payer, - seeds = [ - PreparedOrderResponse::SEED_PREFIX, - payer.key().as_ref(), - wormhole_cctp_solana::wormhole::VaaAccount::load(&fast_vaa)?.digest().as_ref() - ], - bump = prepared_order_response.bump, - )] - prepared_order_response: Box>, - - /// There should be no account data here because an auction was never created. - #[account( - mut, - seeds = [ - Auction::SEED_PREFIX, - prepared_order_response.fast_vaa_hash.as_ref(), - ], - bump = auction.bump, - constraint = utils::is_valid_active_auction( - &auction_config, - &auction, - Some(best_offer_token.key()), - None, - )? - )] - auction: Box>, - - /// CHECK: Must equal the best offer token in the auction data account. - #[account(mut)] - best_offer_token: AccountInfo<'info>, - - /// CHECK: Must be the same mint as the best offer token. - #[account(mut)] - executor_token: AccountInfo<'info>, - - /// Mint recipient token account, which is encoded as the mint recipient in the CCTP message. - /// The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message - /// from its custody account to this account. - /// - /// CHECK: Mutable. Seeds must be \["custody"\]. - /// - /// NOTE: This account must be encoded as the mint recipient in the CCTP message. - #[account( - mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, - )] - cctp_mint_recipient: AccountInfo<'info>, - - /// Seeds must be \["endpoint", chain.to_be_bytes()\]. - #[account( - seeds = [ - RouterEndpoint::SEED_PREFIX, - to_router_endpoint.chain.to_be_bytes().as_ref(), - ], - bump = to_router_endpoint.bump, - )] - to_router_endpoint: Box>, - - /// Circle-supported mint. - /// - /// CHECK: Mutable. This token account's mint must be the same as the one found in the CCTP - /// Token Messenger Minter program's local token account. - #[account(mut)] - mint: AccountInfo<'info>, - - /// CHECK: Seeds must be \["Bridge"\] (Wormhole Core Bridge program). - #[account(mut)] - core_bridge_config: UncheckedAccount<'info>, - - /// CHECK: Mutable. Seeds must be \["core-msg", payer, payer_sequence.value\]. - #[account( - mut, - seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, - payer.key().as_ref(), - payer_sequence.value.to_be_bytes().as_ref(), - ], - bump, - )] - core_message: AccountInfo<'info>, - - /// CHECK: Mutable. Seeds must be \["cctp-msg", payer, payer_sequence.value\]. - #[account( - mut, - seeds = [ - common::constants::CCTP_MESSAGE_SEED_PREFIX, - payer.key().as_ref(), - payer_sequence.value.to_be_bytes().as_ref(), - ], - bump, - )] - cctp_message: AccountInfo<'info>, - - /// CHECK: Seeds must be \["Sequence"\, custodian] (Wormhole Core Bridge program). - #[account(mut)] - core_emitter_sequence: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["fee_collector"\] (Wormhole Core Bridge program). - #[account(mut)] - core_fee_collector: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["sender_authority"\] (CCTP Token Messenger Minter program). - token_messenger_minter_sender_authority: UncheckedAccount<'info>, - - /// CHECK: Mutable. Seeds must be \["message_transmitter"\] (CCTP Message Transmitter program). - #[account(mut)] - message_transmitter_config: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["token_messenger"\] (CCTP Token Messenger Minter program). - token_messenger: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["remote_token_messenger"\, remote_domain.to_string()] (CCTP Token - /// Messenger Minter program). - remote_token_messenger: UncheckedAccount<'info>, - - /// 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. - /// - /// CHECK: Mutable. Seeds must be \["local_token", mint\] (CCTP Token Messenger Minter program). - #[account(mut)] - local_token: UncheckedAccount<'info>, - - /// 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>, - message_transmitter_program: Program<'info, message_transmitter_program::MessageTransmitter>, - token_program: Program<'info, token::Token>, - system_program: Program<'info, System>, - - /// CHECK: Wormhole Core Bridge needs the clock sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::clock::id())] - clock: AccountInfo<'info>, - - /// CHECK: Wormhole Core Bridge needs the rent sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::rent::id())] - rent: AccountInfo<'info>, -} - -/// TODO: add docstring -pub fn settle_auction_active_cctp(ctx: Context) -> Result<()> { - match ctx.accounts.to_router_endpoint.protocol { - MessageProtocol::Cctp { domain } => handle_settle_auction_active_cctp(ctx, domain), - _ => err!(MatchingEngineError::InvalidCctpEndpoint), - } -} - -fn handle_settle_auction_active_cctp( - ctx: Context, - destination_cctp_domain: u32, -) -> Result<()> { - let super::SettledActive { - user_amount: amount, - fill, - sequence_seed, - } = super::settle_active_and_prepare_fill(super::SettleActiveAndPrepareFill { - custodian: &ctx.accounts.custodian, - auction_config: &ctx.accounts.auction_config, - fast_vaa: &ctx.accounts.fast_vaa, - auction: &mut ctx.accounts.auction, - prepared_order_response: &ctx.accounts.prepared_order_response, - executor_token: &ctx.accounts.executor_token, - best_offer_token: &ctx.accounts.best_offer_token, - cctp_mint_recipient: &ctx.accounts.cctp_mint_recipient, - payer_sequence: &mut ctx.accounts.payer_sequence, - token_program: &ctx.accounts.token_program, - })?; - - // This returns the CCTP nonce, but we do not need it. - wormhole_cctp_solana::cpi::burn_and_publish( - CpiContext::new_with_signer( - ctx.accounts - .token_messenger_minter_program - .to_account_info(), - wormhole_cctp_solana::cpi::DepositForBurnWithCaller { - 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(), - burn_token: ctx.accounts.cctp_mint_recipient.to_account_info(), - message_transmitter_config: ctx - .accounts - .message_transmitter_config - .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(), - 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 - .to_account_info(), - token_messenger_minter_program: ctx - .accounts - .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::SIGNER_SEEDS, - &[ - common::constants::CCTP_MESSAGE_SEED_PREFIX, - ctx.accounts.payer.key().as_ref(), - sequence_seed.as_ref(), - &[ctx.bumps.cctp_message], - ], - ], - ), - CpiContext::new_with_signer( - ctx.accounts.core_bridge_program.to_account_info(), - wormhole_cctp_solana::cpi::PostMessage { - payer: ctx.accounts.payer.to_account_info(), - message: ctx.accounts.core_message.to_account_info(), - emitter: ctx.accounts.custodian.to_account_info(), - config: ctx.accounts.core_bridge_config.to_account_info(), - emitter_sequence: ctx.accounts.core_emitter_sequence.to_account_info(), - fee_collector: ctx.accounts.core_fee_collector.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - clock: ctx.accounts.clock.to_account_info(), - rent: ctx.accounts.rent.to_account_info(), - }, - &[ - Custodian::SIGNER_SEEDS, - &[ - common::constants::CORE_MESSAGE_SEED_PREFIX, - ctx.accounts.payer.key().as_ref(), - sequence_seed.as_ref(), - &[ctx.bumps.core_message], - ], - ], - ), - wormhole_cctp_solana::cpi::BurnAndPublishArgs { - burn_source: None, - destination_caller: ctx.accounts.to_router_endpoint.address, - destination_cctp_domain, - amount, - mint_recipient: ctx.accounts.to_router_endpoint.mint_recipient, - wormhole_message_nonce: common::constants::WORMHOLE_MESSAGE_NONCE, - payload: fill.to_vec_payload(), - }, - )?; - - Ok(()) -} diff --git a/solana/programs/matching-engine/src/processor/auction/settle/active/local.rs b/solana/programs/matching-engine/src/processor/auction/settle/active/local.rs deleted file mode 100644 index 70fac6037..000000000 --- a/solana/programs/matching-engine/src/processor/auction/settle/active/local.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::{ - error::MatchingEngineError, - state::{ - Auction, AuctionConfig, Custodian, MessageProtocol, PayerSequence, PreparedOrderResponse, - RouterEndpoint, - }, - utils, -}; -use anchor_lang::prelude::*; -use anchor_spl::token; -use common::{ - wormhole_cctp_solana::wormhole::{core_bridge_program, VaaAccount, SOLANA_CHAIN}, - wormhole_io::TypePrefixedPayload, -}; - -/// Accounts required for [settle_auction_active_local]. -#[derive(Accounts)] -pub struct SettleAuctionActiveLocal<'info> { - #[account( - mut, - address = prepared_order_response.prepared_by, // TODO: add err - )] - payer: Signer<'info>, - - #[account( - init_if_needed, - payer = payer, - space = 8 + PayerSequence::INIT_SPACE, - seeds = [ - PayerSequence::SEED_PREFIX, - payer.key().as_ref() - ], - bump, - )] - payer_sequence: Account<'info, PayerSequence>, - - /// This program's Wormhole (Core Bridge) emitter authority. - /// - /// CHECK: Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - )] - custodian: AccountInfo<'info>, - - auction_config: Box>, - - /// CHECK: Must be owned by the Wormhole Core Bridge program. This account will be read via - /// zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader. - #[account(owner = core_bridge_program::id())] - fast_vaa: AccountInfo<'info>, - - #[account( - mut, - close = payer, - seeds = [ - PreparedOrderResponse::SEED_PREFIX, - payer.key().as_ref(), - VaaAccount::load(&fast_vaa)?.digest().as_ref() - ], - bump = prepared_order_response.bump, - )] - prepared_order_response: Box>, - - /// There should be no account data here because an auction was never created. - #[account( - mut, - seeds = [ - Auction::SEED_PREFIX, - prepared_order_response.fast_vaa_hash.as_ref(), - ], - bump = auction.bump, - constraint = utils::is_valid_active_auction( - &auction_config, - &auction, - Some(best_offer_token.key()), - None, - )? - )] - auction: Box>, - - /// Seeds must be \["endpoint", chain.to_be_bytes()\]. - #[account( - seeds = [ - RouterEndpoint::SEED_PREFIX, - SOLANA_CHAIN.to_be_bytes().as_ref(), - ], - bump = to_router_endpoint.bump, - constraint = to_router_endpoint.protocol != MessageProtocol::None @ MatchingEngineError::EndpointDisabled, - )] - to_router_endpoint: Box>, - - /// CHECK: Must equal the best offer token in the auction data account. - #[account(mut)] - best_offer_token: AccountInfo<'info>, - - /// CHECK: Must be the same mint as the best offer token. - #[account(mut)] - executor_token: AccountInfo<'info>, - - /// Mint recipient token account, which is encoded as the mint recipient in the CCTP message. - /// The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message - /// from its custody account to this account. - /// - /// CHECK: Mutable. Seeds must be \["custody"\]. - /// - /// NOTE: This account must be encoded as the mint recipient in the CCTP message. - #[account( - mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, - )] - cctp_mint_recipient: AccountInfo<'info>, - - /// CHECK: Seeds must be \["Bridge"\] (Wormhole Core Bridge program). - #[account(mut)] - core_bridge_config: UncheckedAccount<'info>, - - /// CHECK: Mutable. Seeds must be \["msg", payer, payer_sequence.value\]. - #[account( - mut, - seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, - payer.key().as_ref(), - payer_sequence.value.to_be_bytes().as_ref(), - ], - bump, - )] - core_message: AccountInfo<'info>, - - /// CHECK: Seeds must be \["Sequence"\, custodian] (Wormhole Core Bridge program). - #[account(mut)] - core_emitter_sequence: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["fee_collector"\] (Wormhole Core Bridge program). - #[account(mut)] - core_fee_collector: UncheckedAccount<'info>, - - core_bridge_program: Program<'info, core_bridge_program::CoreBridge>, - token_program: Program<'info, token::Token>, - system_program: Program<'info, System>, - - /// CHECK: Wormhole Core Bridge needs the clock sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::clock::id())] - clock: AccountInfo<'info>, - - /// CHECK: Wormhole Core Bridge needs the rent sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::rent::id())] - rent: AccountInfo<'info>, -} - -/// TODO: add docstring -pub fn settle_auction_active_local(ctx: Context) -> Result<()> { - let super::SettledActive { - user_amount: amount, - fill, - sequence_seed, - } = super::settle_active_and_prepare_fill(super::SettleActiveAndPrepareFill { - custodian: &ctx.accounts.custodian, - auction_config: &ctx.accounts.auction_config, - fast_vaa: &ctx.accounts.fast_vaa, - auction: &mut ctx.accounts.auction, - prepared_order_response: &ctx.accounts.prepared_order_response, - executor_token: &ctx.accounts.executor_token, - best_offer_token: &ctx.accounts.best_offer_token, - cctp_mint_recipient: &ctx.accounts.cctp_mint_recipient, - payer_sequence: &mut ctx.accounts.payer_sequence, - token_program: &ctx.accounts.token_program, - })?; - - // Publish message via Core Bridge. - core_bridge_program::cpi::post_message( - CpiContext::new_with_signer( - ctx.accounts.core_bridge_program.to_account_info(), - core_bridge_program::cpi::PostMessage { - payer: ctx.accounts.payer.to_account_info(), - message: ctx.accounts.core_message.to_account_info(), - emitter: ctx.accounts.custodian.to_account_info(), - config: ctx.accounts.core_bridge_config.to_account_info(), - emitter_sequence: ctx.accounts.core_emitter_sequence.to_account_info(), - fee_collector: ctx.accounts.core_fee_collector.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - clock: ctx.accounts.clock.to_account_info(), - rent: ctx.accounts.rent.to_account_info(), - }, - &[ - Custodian::SIGNER_SEEDS, - &[ - common::constants::CORE_MESSAGE_SEED_PREFIX, - ctx.accounts.payer.key().as_ref(), - sequence_seed.as_ref(), - &[ctx.bumps.core_message], - ], - ], - ), - core_bridge_program::cpi::PostMessageArgs { - nonce: common::constants::WORMHOLE_MESSAGE_NONCE, - payload: common::messages::FastFill { amount, fill }.to_vec_payload(), - commitment: core_bridge_program::Commitment::Finalized, - }, - ) -} diff --git a/solana/programs/matching-engine/src/processor/auction/settle/active/mod.rs b/solana/programs/matching-engine/src/processor/auction/settle/active/mod.rs deleted file mode 100644 index 6ae45c28a..000000000 --- a/solana/programs/matching-engine/src/processor/auction/settle/active/mod.rs +++ /dev/null @@ -1,141 +0,0 @@ -mod cctp; -pub use cctp::*; - -mod local; -pub use local::*; - -use crate::{ - state::{ - Auction, AuctionConfig, AuctionStatus, Custodian, PayerSequence, PreparedOrderResponse, - }, - utils::{self, auction::DepositPenalty}, -}; -use anchor_lang::prelude::*; -use anchor_spl::token; -use common::{ - messages::{ - raw::{LiquidityLayerMessage, MessageToVec}, - Fill, - }, - wormhole_cctp_solana::wormhole::VaaAccount, -}; - -struct SettleActiveAndPrepareFill<'ctx, 'info> { - custodian: &'ctx AccountInfo<'info>, - auction_config: &'ctx Account<'info, AuctionConfig>, - fast_vaa: &'ctx AccountInfo<'info>, - auction: &'ctx mut Account<'info, Auction>, - prepared_order_response: &'ctx Account<'info, PreparedOrderResponse>, - executor_token: &'ctx AccountInfo<'info>, - best_offer_token: &'ctx AccountInfo<'info>, - cctp_mint_recipient: &'ctx AccountInfo<'info>, - payer_sequence: &'ctx mut Account<'info, PayerSequence>, - token_program: &'ctx Program<'info, token::Token>, -} - -struct SettledActive { - user_amount: u64, - fill: Fill, - sequence_seed: [u8; 8], -} - -fn settle_active_and_prepare_fill( - accounts: SettleActiveAndPrepareFill<'_, '_>, -) -> Result { - let SettleActiveAndPrepareFill { - custodian, - auction_config, - fast_vaa, - auction, - prepared_order_response, - executor_token, - best_offer_token, - cctp_mint_recipient, - payer_sequence, - token_program, - } = accounts; - - let fast_vaa = VaaAccount::load_unchecked(fast_vaa); - let order = LiquidityLayerMessage::try_from(fast_vaa.payload()) - .unwrap() - .to_fast_market_order_unchecked(); - - // This means the slow message beat the fast message. We need to refund the bidder and - // (potentially) take a penalty for not fulfilling their obligation. The `penalty` CAN be zero - // in this case, since the auction grace period might not have ended yet. - let (executor_amount, mut best_offer_amount, user_amount, final_status) = { - let auction_info = auction.info.as_ref().unwrap(); - - let DepositPenalty { - penalty, - user_reward, - } = utils::auction::compute_deposit_penalty( - auction_config, - auction.info.as_ref().unwrap(), - Clock::get().map(|clock| clock.slot)?, - ); - - // TODO: do math to adjust base fee and reward by amount_out / amount_in. - let base_fee = accounts.prepared_order_response.base_fee; - - // NOTE: The sum of all amounts should be 2 * amount_in + security_deposit. - // * amount_in + security_deposit comes from the auction participation. - // * amount_in comes from the inbound transfer. - ( - penalty + base_fee, - auction_info.total_deposit() - penalty - user_reward, - auction_info.amount_in + user_reward - base_fee, - AuctionStatus::Settled { - base_fee, - penalty: Some(penalty), - }, - ) - }; - - if executor_token.key() != best_offer_token.key() { - // Transfer the penalty amount to the caller. The caller also earns the base fee for relaying - // the slow VAA. - token::transfer( - CpiContext::new_with_signer( - token_program.to_account_info(), - token::Transfer { - from: cctp_mint_recipient.to_account_info(), - to: executor_token.to_account_info(), - authority: custodian.to_account_info(), - }, - &[Custodian::SIGNER_SEEDS], - ), - executor_amount, - )?; - } else { - best_offer_amount += executor_amount; - } - - // Transfer to the best offer token what he deserves. - token::transfer( - CpiContext::new_with_signer( - token_program.to_account_info(), - token::Transfer { - from: cctp_mint_recipient.to_account_info(), - to: best_offer_token.to_account_info(), - authority: custodian.to_account_info(), - }, - &[Custodian::SIGNER_SEEDS], - ), - best_offer_amount, - )?; - - // Everyone's whole, set the auction as completed. - auction.status = final_status; - - Ok(SettledActive { - user_amount, - fill: Fill { - source_chain: prepared_order_response.source_chain, - order_sender: order.sender(), - redeemer: order.redeemer(), - redeemer_message: order.message_to_vec().into(), - }, - sequence_seed: payer_sequence.take_and_uptick().to_be_bytes(), - }) -} diff --git a/solana/programs/matching-engine/src/processor/auction/settle/complete.rs b/solana/programs/matching-engine/src/processor/auction/settle/complete.rs index 7ba42d213..d79a79aa4 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/complete.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/complete.rs @@ -1,100 +1,168 @@ use crate::{ error::MatchingEngineError, - state::{Auction, AuctionStatus, Custodian, PreparedOrderResponse}, + state::{Auction, AuctionStatus, PreparedOrderResponse}, }; use anchor_lang::prelude::*; use anchor_spl::token; #[derive(Accounts)] pub struct SettleAuctionComplete<'info> { - /// This program's Wormhole (Core Bridge) emitter authority. - /// - /// CHECK: Seeds must be \["emitter"\]. + /// CHECK: To prevent squatters from preparing order responses on behalf of the auction winner, + /// we will always reward the owner of the executor token account with the lamports from the + /// prepared order response and its custody token account when we close these accounts. This + /// means we disregard the `prepared_by` field in the prepared order response. + #[account(mut)] + executor: AccountInfo<'info>, + #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, + mut, + associated_token::mint = best_offer_token.mint, + associated_token::authority = executor, )] - custodian: Account<'info, Custodian>, + executor_token: Account<'info, token::TokenAccount>, - /// CHECK: Must be the account that created the prepared slow order. - #[account(mut)] - prepared_by: AccountInfo<'info>, + /// Destination token account, which the redeemer may not own. But because the redeemer is a + /// signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent + /// to any account he chooses (this one). + /// + /// CHECK: This token account must already exist. + #[account( + mut, + address = auction.info.as_ref().unwrap().best_offer_token, + )] + best_offer_token: Account<'info, token::TokenAccount>, #[account( mut, - close = prepared_by, + close = executor, seeds = [ PreparedOrderResponse::SEED_PREFIX, - prepared_by.key().as_ref(), prepared_order_response.fast_vaa_hash.as_ref() ], bump = prepared_order_response.bump, )] prepared_order_response: Account<'info, PreparedOrderResponse>, + /// CHECK: Seeds must be \["prepared-custody"\, prepared_order_response.key()]. #[account( + mut, seeds = [ - Auction::SEED_PREFIX, - prepared_order_response.fast_vaa_hash.as_ref(), + crate::PREPARED_CUSTODY_TOKEN_SEED_PREFIX, + prepared_order_response.key().as_ref(), ], - bump = auction.bump, - constraint = { - require!( - matches!(auction.status, AuctionStatus::Completed { .. }), - MatchingEngineError::AuctionNotCompleted, - ); - - require_keys_eq!( - best_offer_token.key(), - auction.info.as_ref().unwrap().best_offer_token, - MatchingEngineError::BestOfferTokenMismatch, - ); - true - } + bump, )] - auction: Account<'info, Auction>, + prepared_custody_token: AccountInfo<'info>, - /// Destination token account, which the redeemer may not own. But because the redeemer is a - /// signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent - /// to any account he chooses (this one). - /// - /// CHECK: This token account must already exist. - #[account(mut)] - best_offer_token: AccountInfo<'info>, - - /// Mint recipient token account, which is encoded as the mint recipient in the CCTP message. - /// The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message - /// from its custody account to this account. - /// - /// Mutable. Seeds must be \["custody"\]. - /// - /// NOTE: This account must be encoded as the mint recipient in the CCTP message. #[account( mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, + seeds = [ + Auction::SEED_PREFIX, + prepared_order_response.fast_vaa_hash.as_ref(), + ], + bump = auction.bump, )] - cctp_mint_recipient: Account<'info, token::TokenAccount>, + auction: Account<'info, Auction>, token_program: Program<'info, token::Token>, } pub fn settle_auction_complete(ctx: Context) -> Result<()> { + match ctx.accounts.auction.status { + AuctionStatus::Completed { + slot: _, + execute_penalty, + } => handle_settle_auction_complete(ctx, execute_penalty), + _ => err!(MatchingEngineError::AuctionNotCompleted), + } +} + +fn handle_settle_auction_complete( + ctx: Context, + execute_penalty: Option, +) -> Result<()> { + let prepared_order_response = &ctx.accounts.prepared_order_response; + let base_fee = prepared_order_response.base_fee; + ctx.accounts.auction.status = AuctionStatus::Settled { - base_fee: ctx.accounts.prepared_order_response.base_fee, - penalty: None, + base_fee, + total_penalty: execute_penalty.map(|v| v + base_fee), + }; + + let executor_token = &ctx.accounts.executor_token; + let best_offer_token = &ctx.accounts.best_offer_token; + let prepared_custody_token = &ctx.accounts.prepared_custody_token; + let token_program = &ctx.accounts.token_program; + + let prepared_order_response_signer_seeds = &[ + PreparedOrderResponse::SEED_PREFIX, + prepared_order_response.fast_vaa_hash.as_ref(), + &[prepared_order_response.bump], + ]; + + // We may deduct from this account if the winning participant was penalized. + let mut repayment = ctx.accounts.auction.info.as_ref().unwrap().amount_in; + + match execute_penalty { + None => { + // If there is no penalty, we require that the executor token and best offer token be + // equal. The winning offer should not be penalized for calling this instruction when he + // has executed the order within the grace period. + // + // By requiring that these pubkeys are equal, we enforce that the owner of the best + // offer token gets rewarded the lamports from the prepared order response and its + // custody account. + require_keys_eq!( + executor_token.key(), + best_offer_token.key(), + MatchingEngineError::ExecutorTokenMismatch + ) + } + _ => { + if executor_token.key() != best_offer_token.key() { + // Because the auction participant was penalized for executing the order late, he + // will be deducted the base fee. This base fee will be sent to the executor token + // account if it is not the same as the best offer token account. + token::transfer( + CpiContext::new_with_signer( + token_program.to_account_info(), + token::Transfer { + from: prepared_custody_token.to_account_info(), + to: executor_token.to_account_info(), + authority: prepared_order_response.to_account_info(), + }, + &[prepared_order_response_signer_seeds], + ), + base_fee, + )?; + + repayment -= base_fee; + } + } }; - // Finally transfer the funds back to the highest bidder. + // Transfer the funds back to the highest bidder. token::transfer( CpiContext::new_with_signer( - ctx.accounts.token_program.to_account_info(), + token_program.to_account_info(), token::Transfer { - from: ctx.accounts.cctp_mint_recipient.to_account_info(), - to: ctx.accounts.best_offer_token.to_account_info(), - authority: ctx.accounts.custodian.to_account_info(), + from: prepared_custody_token.to_account_info(), + to: best_offer_token.to_account_info(), + authority: prepared_order_response.to_account_info(), }, - &[Custodian::SIGNER_SEEDS], + &[prepared_order_response_signer_seeds], ), - ctx.accounts.auction.info.as_ref().unwrap().amount_in, - ) + repayment, + )?; + + // Finally close the prepared custody token account. + token::close_account(CpiContext::new_with_signer( + token_program.to_account_info(), + token::CloseAccount { + account: prepared_custody_token.to_account_info(), + destination: ctx.accounts.executor.to_account_info(), + authority: prepared_order_response.to_account_info(), + }, + &[prepared_order_response_signer_seeds], + )) } diff --git a/solana/programs/matching-engine/src/processor/auction/settle/mod.rs b/solana/programs/matching-engine/src/processor/auction/settle/mod.rs index 9f3ec74fe..1d520d9af 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/mod.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/mod.rs @@ -1,5 +1,5 @@ -mod active; -pub use active::*; +// mod active; +// pub use active::*; mod complete; pub use complete::*; diff --git a/solana/programs/matching-engine/src/processor/auction/settle/none/cctp.rs b/solana/programs/matching-engine/src/processor/auction/settle/none/cctp.rs index f17969f20..fd6b2b7aa 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/none/cctp.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/none/cctp.rs @@ -1,27 +1,17 @@ use crate::{ + composite::*, error::MatchingEngineError, - state::{ - Auction, Custodian, MessageProtocol, PayerSequence, PreparedOrderResponse, RouterEndpoint, - }, + state::{Auction, Custodian, MessageProtocol, PayerSequence, RouterEndpoint}, + utils, }; use anchor_lang::prelude::*; use anchor_spl::token; -use common::{ - wormhole_cctp_solana::{ - self, - cctp::{message_transmitter_program, token_messenger_minter_program}, - wormhole::{core_bridge_program, VaaAccount, SOLANA_CHAIN}, - }, - wormhole_io::TypePrefixedPayload, -}; +use common::{wormhole_cctp_solana, wormhole_io::TypePrefixedPayload}; /// Accounts required for [settle_auction_none_cctp]. #[derive(Accounts)] pub struct SettleAuctionNoneCctp<'info> { - #[account( - mut, - address = prepared_order_response.prepared_by, // TODO: add err - )] + #[account(mut)] payer: Signer<'info>, #[account( @@ -36,185 +26,79 @@ pub struct SettleAuctionNoneCctp<'info> { )] payer_sequence: Box>, - /// This program's Wormhole (Core Bridge) emitter authority. - /// - /// CHECK: Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - has_one = fee_recipient_token @ MatchingEngineError::FeeRecipientTokenMismatch, - )] - custodian: Box>, - - /// CHECK: Must be owned by the Wormhole Core Bridge program. This account will be read via - /// zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader. - #[account(owner = core_bridge_program::id())] - fast_vaa: AccountInfo<'info>, - - // /// CHECK: Must be the account that created the prepared slow order. This account will most - // /// likely be the same as the payer. - // #[account(mut)] - // prepared_by: AccountInfo<'info>, + /// CHECK: Mutable. Seeds must be \["core-msg", payer, payer_sequence.value\]. #[account( mut, - close = payer, seeds = [ - PreparedOrderResponse::SEED_PREFIX, + common::constants::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), - VaaAccount::load(&fast_vaa)?.digest().as_ref() + &payer_sequence.value.to_be_bytes(), ], - bump = prepared_order_response.bump, + bump, )] - prepared_order_response: Box>, + core_message: AccountInfo<'info>, - /// There should be no account data here because an auction was never created. + /// CHECK: Mutable. Seeds must be \["cctp-msg", payer, payer_sequence.value\]. #[account( - init, - payer = payer, - space = 8 + Auction::INIT_SPACE_NO_AUCTION, + mut, seeds = [ - Auction::SEED_PREFIX, - prepared_order_response.fast_vaa_hash.as_ref(), + common::constants::CCTP_MESSAGE_SEED_PREFIX, + payer.key().as_ref(), + &payer_sequence.value.to_be_bytes(), ], - bump + bump, )] - auction: Box>, + cctp_message: AccountInfo<'info>, - /// Mint recipient token account, which is encoded as the mint recipient in the CCTP message. - /// The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message - /// from its custody account to this account. - /// - /// CHECK: Mutable. Seeds must be \["custody"\]. - /// - /// NOTE: This account must be encoded as the mint recipient in the CCTP message. - #[account( - mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, - )] - cctp_mint_recipient: AccountInfo<'info>, + custodian: CheckedCustodian<'info>, /// Destination token account, which the redeemer may not own. But because the redeemer is a /// signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent /// to any account he chooses (this one). /// /// CHECK: This token account must already exist. - #[account(mut)] - fee_recipient_token: AccountInfo<'info>, - - /// Seeds must be \["endpoint", chain.to_be_bytes()\]. - #[account( - seeds = [ - RouterEndpoint::SEED_PREFIX, - from_router_endpoint.chain.to_be_bytes().as_ref(), - ], - bump = from_router_endpoint.bump, - )] - from_router_endpoint: Box>, - - /// Seeds must be \["endpoint", chain.to_be_bytes()\]. - #[account( - seeds = [ - RouterEndpoint::SEED_PREFIX, - to_router_endpoint.chain.to_be_bytes().as_ref(), - ], - bump = to_router_endpoint.bump, - constraint = { - to_router_endpoint.chain != SOLANA_CHAIN - } @ MatchingEngineError::InvalidChain - )] - to_router_endpoint: Box>, - - /// Circle-supported mint. - /// - /// CHECK: Mutable. This token account's mint must be the same as the one found in the CCTP - /// Token Messenger Minter program's local token account. #[account( mut, - address = common::constants::USDC_MINT, + address = custodian.fee_recipient_token, )] - mint: AccountInfo<'info>, - - /// CHECK: Seeds must be \["Bridge"\] (Wormhole Core Bridge program). - #[account(mut)] - core_bridge_config: UncheckedAccount<'info>, + fee_recipient_token: AccountInfo<'info>, - /// CHECK: Mutable. Seeds must be \["core-msg", payer, payer_sequence.value\]. #[account( - mut, - seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, - payer.key().as_ref(), - payer_sequence.value.to_be_bytes().as_ref(), - ], - bump, + constraint = utils::require_vaa_hash_equals( + &prepared, + &fast_order_path.fast_vaa.load_unchecked() + )?, )] - core_message: AccountInfo<'info>, + prepared: ClosePreparedOrderResponse<'info>, - /// CHECK: Mutable. Seeds must be \["cctp-msg", payer, payer_sequence.value\]. + fast_order_path: FastOrderPath<'info>, + + /// There should be no account data here because an auction was never created. #[account( - mut, + init, + payer = payer, + space = 8 + Auction::INIT_SPACE_NO_AUCTION, seeds = [ - common::constants::CCTP_MESSAGE_SEED_PREFIX, - payer.key().as_ref(), - payer_sequence.value.to_be_bytes().as_ref(), + Auction::SEED_PREFIX, + prepared.order_response.fast_vaa_hash.as_ref(), ], - bump, + bump )] - cctp_message: AccountInfo<'info>, - - /// CHECK: Seeds must be \["Sequence"\, custodian] (Wormhole Core Bridge program). - #[account(mut)] - core_emitter_sequence: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["fee_collector"\] (Wormhole Core Bridge program). - #[account(mut)] - core_fee_collector: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["sender_authority"\] (CCTP Token Messenger Minter program). - token_messenger_minter_sender_authority: UncheckedAccount<'info>, - - /// CHECK: Mutable. Seeds must be \["message_transmitter"\] (CCTP Message Transmitter program). - #[account(mut)] - message_transmitter_config: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["token_messenger"\] (CCTP Token Messenger Minter program). - token_messenger: UncheckedAccount<'info>, - - /// CHECK: Seeds must be \["remote_token_messenger"\, remote_domain.to_string()] (CCTP Token - /// Messenger Minter program). - remote_token_messenger: UncheckedAccount<'info>, - - /// CHECK Seeds must be \["token_minter"\] (CCTP Token Messenger Minter program). - token_minter: UncheckedAccount<'info>, + auction: Box>, - /// Local token account, which this program uses to validate the `mint` used to burn. - /// - /// CHECK: Mutable. Seeds must be \["local_token", mint\] (CCTP Token Messenger Minter program). - #[account(mut)] - local_token: UncheckedAccount<'info>, + wormhole: WormholePublishMessage<'info>, - /// CHECK: Seeds must be \["__event_authority"\] (CCTP Token Messenger Minter program). - token_messenger_minter_event_authority: UncheckedAccount<'info>, + cctp: CctpDepositForBurn<'info>, - core_bridge_program: Program<'info, core_bridge_program::CoreBridge>, - token_messenger_minter_program: - Program<'info, token_messenger_minter_program::TokenMessengerMinter>, - message_transmitter_program: Program<'info, message_transmitter_program::MessageTransmitter>, token_program: Program<'info, token::Token>, system_program: Program<'info, System>, - /// CHECK: Wormhole Core Bridge needs the clock sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::clock::id())] - clock: AccountInfo<'info>, - - /// CHECK: Wormhole Core Bridge needs the rent sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::rent::id())] - rent: AccountInfo<'info>, + sysvars: RequiredSysvars<'info>, } /// TODO: add docstring pub fn settle_auction_none_cctp(ctx: Context) -> Result<()> { - match ctx.accounts.to_router_endpoint.protocol { + match ctx.accounts.fast_order_path.to.protocol { MessageProtocol::Cctp { domain } => handle_settle_auction_none_cctp(ctx, domain), _ => err!(MatchingEngineError::InvalidCctpEndpoint), } @@ -231,24 +115,31 @@ fn handle_settle_auction_none_cctp( sequence_seed, } = super::settle_none_and_prepare_fill( super::SettleNoneAndPrepareFill { - custodian: &ctx.accounts.custodian, - prepared_order_response: &ctx.accounts.prepared_order_response, - fast_vaa: &ctx.accounts.fast_vaa, + payer_sequence: &mut ctx.accounts.payer_sequence, + fast_vaa: &ctx.accounts.fast_order_path.fast_vaa, + prepared_order_response: &ctx.accounts.prepared.order_response, + prepared_custody_token: &ctx.accounts.prepared.custody_token, auction: &mut ctx.accounts.auction, - from_router_endpoint: &ctx.accounts.from_router_endpoint, - to_router_endpoint: &ctx.accounts.to_router_endpoint, fee_recipient_token: &ctx.accounts.fee_recipient_token, - cctp_mint_recipient: &ctx.accounts.cctp_mint_recipient, - payer_sequence: &mut ctx.accounts.payer_sequence, + dst_token: &ctx.accounts.cctp.burn_source, token_program: &ctx.accounts.token_program, }, ctx.bumps.auction, )?; + let RouterEndpoint { + bump: _, + chain: _, + address: destination_caller, + mint_recipient, + protocol: _, + } = ctx.accounts.fast_order_path.to.as_ref(); + // This returns the CCTP nonce, but we do not need it. wormhole_cctp_solana::cpi::burn_and_publish( CpiContext::new_with_signer( ctx.accounts + .cctp .token_messenger_minter_program .to_account_info(), wormhole_cctp_solana::cpi::DepositForBurnWithCaller { @@ -256,31 +147,36 @@ fn handle_settle_auction_none_cctp( payer: ctx.accounts.payer.to_account_info(), token_messenger_minter_sender_authority: ctx .accounts + .cctp .token_messenger_minter_sender_authority .to_account_info(), - burn_token: ctx.accounts.cctp_mint_recipient.to_account_info(), + burn_token: ctx.accounts.cctp.burn_source.to_account_info(), message_transmitter_config: ctx .accounts + .cctp .message_transmitter_config .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(), - local_token: ctx.accounts.local_token.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), + token_messenger: ctx.accounts.cctp.token_messenger.to_account_info(), + remote_token_messenger: ctx.accounts.cctp.remote_token_messenger.to_account_info(), + token_minter: ctx.accounts.cctp.token_minter.to_account_info(), + local_token: ctx.accounts.cctp.local_token.to_account_info(), + mint: ctx.accounts.cctp.mint.to_account_info(), cctp_message: ctx.accounts.cctp_message.to_account_info(), message_transmitter_program: ctx .accounts + .cctp .message_transmitter_program .to_account_info(), token_messenger_minter_program: ctx .accounts + .cctp .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 + .cctp .token_messenger_minter_event_authority .to_account_info(), }, @@ -295,17 +191,17 @@ fn handle_settle_auction_none_cctp( ], ), CpiContext::new_with_signer( - ctx.accounts.core_bridge_program.to_account_info(), + ctx.accounts.wormhole.core_bridge_program.to_account_info(), wormhole_cctp_solana::cpi::PostMessage { payer: ctx.accounts.payer.to_account_info(), message: ctx.accounts.core_message.to_account_info(), emitter: ctx.accounts.custodian.to_account_info(), - config: ctx.accounts.core_bridge_config.to_account_info(), - emitter_sequence: ctx.accounts.core_emitter_sequence.to_account_info(), - fee_collector: ctx.accounts.core_fee_collector.to_account_info(), + config: ctx.accounts.wormhole.config.to_account_info(), + emitter_sequence: ctx.accounts.wormhole.emitter_sequence.to_account_info(), + fee_collector: ctx.accounts.wormhole.fee_collector.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), - clock: ctx.accounts.clock.to_account_info(), - rent: ctx.accounts.rent.to_account_info(), + clock: ctx.accounts.sysvars.clock.to_account_info(), + rent: ctx.accounts.sysvars.rent.to_account_info(), }, &[ Custodian::SIGNER_SEEDS, @@ -319,11 +215,10 @@ fn handle_settle_auction_none_cctp( ), wormhole_cctp_solana::cpi::BurnAndPublishArgs { burn_source: None, - destination_caller: ctx.accounts.to_router_endpoint.address, + destination_caller: *destination_caller, destination_cctp_domain, amount, - // TODO: add mint recipient to the router endpoint account to future proof this? - mint_recipient: ctx.accounts.to_router_endpoint.address, + mint_recipient: *mint_recipient, wormhole_message_nonce: common::constants::WORMHOLE_MESSAGE_NONCE, payload: fill.to_vec_payload(), }, diff --git a/solana/programs/matching-engine/src/processor/auction/settle/none/local.rs b/solana/programs/matching-engine/src/processor/auction/settle/none/local.rs index 8dd77f7b7..f554fe7df 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/none/local.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/none/local.rs @@ -1,23 +1,15 @@ use crate::{ - error::MatchingEngineError, - state::{ - Auction, Custodian, MessageProtocol, PayerSequence, PreparedOrderResponse, RouterEndpoint, - }, + composite::*, + state::{Auction, PayerSequence}, + utils, }; use anchor_lang::prelude::*; use anchor_spl::token; -use common::{ - wormhole_cctp_solana::wormhole::{core_bridge_program, VaaAccount, SOLANA_CHAIN}, - wormhole_io::TypePrefixedPayload, -}; /// Accounts required for [settle_auction_none_local]. #[derive(Accounts)] pub struct SettleAuctionNoneLocal<'info> { - #[account( - mut, - address = prepared_order_response.prepared_by, // TODO: add err - )] + #[account(mut)] payer: Signer<'info>, #[account( @@ -32,127 +24,73 @@ pub struct SettleAuctionNoneLocal<'info> { )] payer_sequence: Box>, - /// This program's Wormhole (Core Bridge) emitter authority. - /// - /// CHECK: Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - has_one = fee_recipient_token @ MatchingEngineError::FeeRecipientTokenMismatch, - )] - custodian: Box>, - - /// CHECK: Must be owned by the Wormhole Core Bridge program. This account will be read via - /// zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader. - #[account(owner = core_bridge_program::id())] - fast_vaa: AccountInfo<'info>, - + /// CHECK: Mutable. Seeds must be \["msg", payer, payer_sequence.value\]. #[account( mut, - close = payer, seeds = [ - PreparedOrderResponse::SEED_PREFIX, + common::constants::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), - VaaAccount::load(&fast_vaa)?.digest().as_ref() - ], - bump = prepared_order_response.bump, - )] - prepared_order_response: Account<'info, PreparedOrderResponse>, - - /// There should be no account data here because an auction was never created. - #[account( - init, - payer = payer, - space = 8 + Auction::INIT_SPACE_NO_AUCTION, - seeds = [ - Auction::SEED_PREFIX, - prepared_order_response.fast_vaa_hash.as_ref(), + payer_sequence.value.to_be_bytes().as_ref(), ], - bump + bump, )] - auction: Box>, + core_message: AccountInfo<'info>, - /// Mint recipient token account, which is encoded as the mint recipient in the CCTP message. - /// The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message - /// from its custody account to this account. - /// - /// CHECK: Mutable. Seeds must be \["custody"\]. - /// - /// NOTE: This account must be encoded as the mint recipient in the CCTP message. - #[account( - mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, - )] - cctp_mint_recipient: AccountInfo<'info>, + custodian: CheckedCustodian<'info>, /// Destination token account, which the redeemer may not own. But because the redeemer is a /// signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent /// to any account he chooses (this one). /// /// CHECK: This token account must already exist. - #[account(mut)] + #[account( + mut, + address = custodian.fee_recipient_token, + )] fee_recipient_token: AccountInfo<'info>, - /// Seeds must be \["endpoint", chain.to_be_bytes()\]. #[account( - seeds = [ - RouterEndpoint::SEED_PREFIX, - from_router_endpoint.chain.to_be_bytes().as_ref(), - ], - bump = from_router_endpoint.bump, - constraint = from_router_endpoint.protocol != MessageProtocol::None @ MatchingEngineError::EndpointDisabled, - constraint = { - from_router_endpoint.chain != SOLANA_CHAIN - } @ MatchingEngineError::InvalidChain + constraint = utils::require_vaa_hash_equals( + &prepared, + &fast_order_path.fast_vaa.load_unchecked() + )?, + )] + prepared: ClosePreparedOrderResponse<'info>, + + #[account( + constraint = utils::require_local_endpoint(&fast_order_path.to)?, )] - from_router_endpoint: Box>, + fast_order_path: FastOrderPath<'info>, - /// Seeds must be \["endpoint", chain.to_be_bytes()\]. + /// There should be no account data here because an auction was never created. #[account( + init, + payer = payer, + space = 8 + Auction::INIT_SPACE_NO_AUCTION, seeds = [ - RouterEndpoint::SEED_PREFIX, - SOLANA_CHAIN.to_be_bytes().as_ref(), + Auction::SEED_PREFIX, + prepared.order_response.fast_vaa_hash.as_ref(), ], - bump = to_router_endpoint.bump, - constraint = to_router_endpoint.protocol != MessageProtocol::None @ MatchingEngineError::EndpointDisabled, + bump, )] - to_router_endpoint: Box>, + auction: Box>, - /// CHECK: Seeds must be \["Bridge"\] (Wormhole Core Bridge program). - #[account(mut)] - core_bridge_config: UncheckedAccount<'info>, + wormhole: WormholePublishMessage<'info>, - /// CHECK: Mutable. Seeds must be \["msg", payer, payer_sequence.value\]. #[account( mut, seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, - payer.key().as_ref(), - payer_sequence.value.to_be_bytes().as_ref(), + crate::LOCAL_CUSTODY_TOKEN_SEED_PREFIX, + &fast_order_path.fast_vaa.load_unchecked().emitter_chain().to_be_bytes(), ], bump, )] - core_message: AccountInfo<'info>, - - /// CHECK: Seeds must be \["Sequence"\, custodian] (Wormhole Core Bridge program). - #[account(mut)] - core_emitter_sequence: UncheckedAccount<'info>, + local_custody_token: Box>, - /// CHECK: Seeds must be \["fee_collector"\] (Wormhole Core Bridge program). - #[account(mut)] - core_fee_collector: UncheckedAccount<'info>, - - core_bridge_program: Program<'info, core_bridge_program::CoreBridge>, token_program: Program<'info, token::Token>, system_program: Program<'info, System>, - /// CHECK: Wormhole Core Bridge needs the clock sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::clock::id())] - clock: AccountInfo<'info>, - - /// CHECK: Wormhole Core Bridge needs the rent sysvar based on its legacy implementation. - #[account(address = solana_program::sysvar::rent::id())] - rent: AccountInfo<'info>, + sysvars: RequiredSysvars<'info>, } /// TODO: add docstring @@ -163,49 +101,29 @@ pub fn settle_auction_none_local(ctx: Context) -> Result sequence_seed, } = super::settle_none_and_prepare_fill( super::SettleNoneAndPrepareFill { - custodian: &ctx.accounts.custodian, - prepared_order_response: &ctx.accounts.prepared_order_response, - fast_vaa: &ctx.accounts.fast_vaa, + payer_sequence: &mut ctx.accounts.payer_sequence, + fast_vaa: &ctx.accounts.fast_order_path.fast_vaa, + prepared_order_response: &ctx.accounts.prepared.order_response, + prepared_custody_token: &ctx.accounts.prepared.custody_token, auction: &mut ctx.accounts.auction, - from_router_endpoint: &ctx.accounts.from_router_endpoint, - to_router_endpoint: &ctx.accounts.to_router_endpoint, fee_recipient_token: &ctx.accounts.fee_recipient_token, - cctp_mint_recipient: &ctx.accounts.cctp_mint_recipient, - payer_sequence: &mut ctx.accounts.payer_sequence, + dst_token: &ctx.accounts.local_custody_token, token_program: &ctx.accounts.token_program, }, ctx.bumps.auction, )?; - // Publish message via Core Bridge. - core_bridge_program::cpi::post_message( - CpiContext::new_with_signer( - ctx.accounts.core_bridge_program.to_account_info(), - core_bridge_program::cpi::PostMessage { - payer: ctx.accounts.payer.to_account_info(), - message: ctx.accounts.core_message.to_account_info(), - emitter: ctx.accounts.custodian.to_account_info(), - config: ctx.accounts.core_bridge_config.to_account_info(), - emitter_sequence: ctx.accounts.core_emitter_sequence.to_account_info(), - fee_collector: ctx.accounts.core_fee_collector.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - clock: ctx.accounts.clock.to_account_info(), - rent: ctx.accounts.rent.to_account_info(), - }, - &[ - Custodian::SIGNER_SEEDS, - &[ - common::constants::CORE_MESSAGE_SEED_PREFIX, - ctx.accounts.payer.key().as_ref(), - sequence_seed.as_ref(), - &[ctx.bumps.core_message], - ], - ], - ), - core_bridge_program::cpi::PostMessageArgs { - nonce: common::constants::WORMHOLE_MESSAGE_NONCE, - payload: common::messages::FastFill { amount, fill }.to_vec_payload(), - commitment: core_bridge_program::Commitment::Finalized, + utils::wormhole::post_matching_engine_message( + utils::wormhole::PostMatchingEngineMessage { + wormhole: &ctx.accounts.wormhole, + core_message: &ctx.accounts.core_message, + custodian: &ctx.accounts.custodian, + payer: &ctx.accounts.payer, + system_program: &ctx.accounts.system_program, + sysvars: &ctx.accounts.sysvars, }, + common::messages::FastFill { amount, fill }, + &sequence_seed, + ctx.bumps.core_message, ) } diff --git a/solana/programs/matching-engine/src/processor/auction/settle/none/mod.rs b/solana/programs/matching-engine/src/processor/auction/settle/none/mod.rs index 37216fbb1..28795af0a 100644 --- a/solana/programs/matching-engine/src/processor/auction/settle/none/mod.rs +++ b/solana/programs/matching-engine/src/processor/auction/settle/none/mod.rs @@ -4,29 +4,25 @@ pub use cctp::*; mod local; pub use local::*; -use crate::state::{ - Auction, AuctionStatus, Custodian, PayerSequence, PreparedOrderResponse, RouterEndpoint, +use crate::{ + composite::*, + state::{Auction, AuctionStatus, PayerSequence, PreparedOrderResponse}, }; use anchor_lang::prelude::*; use anchor_spl::token; -use common::{ - messages::{ - raw::{LiquidityLayerMessage, MessageToVec}, - Fill, - }, - wormhole_cctp_solana::wormhole::VaaAccount, +use common::messages::{ + raw::{LiquidityLayerMessage, MessageToVec}, + Fill, }; struct SettleNoneAndPrepareFill<'ctx, 'info> { - custodian: &'ctx Account<'info, Custodian>, + payer_sequence: &'ctx mut Account<'info, PayerSequence>, + fast_vaa: &'ctx LiquidityLayerVaa<'info>, prepared_order_response: &'ctx Account<'info, PreparedOrderResponse>, - fast_vaa: &'ctx AccountInfo<'info>, + prepared_custody_token: &'ctx AccountInfo<'info>, auction: &'ctx mut Account<'info, Auction>, - from_router_endpoint: &'ctx Account<'info, RouterEndpoint>, - to_router_endpoint: &'ctx Account<'info, RouterEndpoint>, fee_recipient_token: &'ctx AccountInfo<'info>, - cctp_mint_recipient: &'ctx AccountInfo<'info>, - payer_sequence: &'ctx mut Account<'info, PayerSequence>, + dst_token: &'ctx Account<'info, token::TokenAccount>, token_program: &'ctx Program<'info, token::Token>, } @@ -41,31 +37,26 @@ fn settle_none_and_prepare_fill( auction_bump_seed: u8, ) -> Result { let SettleNoneAndPrepareFill { - custodian, - prepared_order_response, + payer_sequence, fast_vaa, + prepared_order_response, + prepared_custody_token, auction, - from_router_endpoint, - to_router_endpoint, fee_recipient_token, - cctp_mint_recipient, - payer_sequence, + dst_token, token_program, } = accounts; - let fast_vaa = VaaAccount::load_unchecked(fast_vaa); + let fast_vaa = fast_vaa.load_unchecked(); let order = LiquidityLayerMessage::try_from(fast_vaa.payload()) .unwrap() .to_fast_market_order_unchecked(); - // NOTE: We need to verify the router path, since an auction was never created and this check is - // done in the `place_initial_offer` instruction. - crate::utils::require_valid_router_path( - &fast_vaa, - from_router_endpoint, - to_router_endpoint, - order.target_chain(), - )?; + let prepared_order_response_signer_seeds = &[ + PreparedOrderResponse::SEED_PREFIX, + prepared_order_response.fast_vaa_hash.as_ref(), + &[prepared_order_response.bump], + ]; // Pay the `fee_recipient` the base fee. This ensures that the protocol relayer is paid for // relaying slow VAAs that do not have an associated auction. This prevents the protocol relayer @@ -75,15 +66,29 @@ fn settle_none_and_prepare_fill( CpiContext::new_with_signer( token_program.to_account_info(), token::Transfer { - from: cctp_mint_recipient.to_account_info(), + from: prepared_custody_token.to_account_info(), to: fee_recipient_token.to_account_info(), - authority: custodian.to_account_info(), + authority: prepared_order_response.to_account_info(), }, - &[Custodian::SIGNER_SEEDS], + &[prepared_order_response_signer_seeds], ), base_fee, )?; + let user_amount = order.amount_in() - base_fee; + token::transfer( + CpiContext::new_with_signer( + token_program.to_account_info(), + token::Transfer { + from: prepared_custody_token.to_account_info(), + to: dst_token.to_account_info(), + authority: prepared_order_response.to_account_info(), + }, + &[prepared_order_response_signer_seeds], + ), + user_amount, + )?; + // This is a necessary security check. This will prevent a relayer from starting an auction with // the fast transfer VAA, even though the slow relayer already delivered the slow VAA. Not // setting this could lead to trapped funds (which would require an upgrade to fix). @@ -92,13 +97,13 @@ fn settle_none_and_prepare_fill( vaa_hash: fast_vaa.digest().0, status: AuctionStatus::Settled { base_fee, - penalty: None, + total_penalty: None, }, info: None, }); Ok(SettledNone { - user_amount: order.amount_in() - base_fee, + user_amount, fill: Fill { source_chain: prepared_order_response.source_chain, order_sender: order.sender(), diff --git a/solana/programs/matching-engine/src/processor/complete_fast_fill.rs b/solana/programs/matching-engine/src/processor/complete_fast_fill.rs index 1c281ddf9..a8ee3f1dc 100644 --- a/solana/programs/matching-engine/src/processor/complete_fast_fill.rs +++ b/solana/programs/matching-engine/src/processor/complete_fast_fill.rs @@ -1,13 +1,13 @@ +use crate::{ + composite::*, + error::MatchingEngineError, + state::{RedeemedFastFill, RouterEndpoint}, +}; use anchor_lang::prelude::*; use anchor_spl::token; use common::{ messages::raw::LiquidityLayerMessage, - wormhole_cctp_solana::wormhole::{core_bridge_program, VaaAccount, SOLANA_CHAIN}, -}; - -use crate::{ - error::MatchingEngineError, - state::{router_endpoint::*, Custodian, LiveRouterEndpoint, RedeemedFastFill}, + wormhole_cctp_solana::wormhole::{VaaAccount, SOLANA_CHAIN}, }; /// Accounts required for [complete_fast_fill]. @@ -16,12 +16,27 @@ pub struct CompleteFastFill<'info> { #[account(mut)] payer: Signer<'info>, - custodian: Account<'info, Custodian>, + custodian: CheckedCustodian<'info>, + + #[account( + constraint = { + // Make sure that this VAA was emitted from the matching engine. + let vaa = fast_fill_vaa.load_unchecked(); + require_eq!( + vaa.emitter_chain(), + SOLANA_CHAIN, + MatchingEngineError::InvalidEmitterForFastFill + ); + require_keys_eq!( + Pubkey::from(vaa.emitter_address()), + custodian.key(), + MatchingEngineError::InvalidEmitterForFastFill + ); - /// CHECK: Must be owned by the Wormhole Core Bridge program. This account will be read via - /// zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader. - #[account(owner = core_bridge_program::id())] - vaa: AccountInfo<'info>, + true + } + )] + fast_fill_vaa: LiquidityLayerVaa<'info>, #[account( init, @@ -29,7 +44,7 @@ pub struct CompleteFastFill<'info> { space = 8 + RedeemedFastFill::INIT_SPACE, seeds = [ RedeemedFastFill::SEED_PREFIX, - VaaAccount::load(&vaa)?.digest().as_ref() + VaaAccount::load(&fast_fill_vaa)?.digest().as_ref() ], bump )] @@ -40,7 +55,7 @@ pub struct CompleteFastFill<'info> { #[account( mut, - token::mint = cctp_mint_recipient.mint, + token::mint = local_custody_token.mint, token::authority = token_router_emitter, )] token_router_custody_token: Account<'info, token::TokenAccount>, @@ -57,16 +72,25 @@ pub struct CompleteFastFill<'info> { )] router_endpoint: LiveRouterEndpoint<'info>, - /// Mint recipient token account, which is encoded as the mint recipient in the CCTP message. - /// The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message - /// from its custody account to this account. - /// - /// Mutable. Seeds must be \["custody"\]. #[account( mut, - address = crate::cctp_mint_recipient::id() @ MatchingEngineError::InvalidCustodyToken, + seeds = [ + crate::LOCAL_CUSTODY_TOKEN_SEED_PREFIX, + { + let vaa = fast_fill_vaa.load_unchecked(); + let msg = LiquidityLayerMessage::try_from(vaa.payload()).unwrap(); + + // Is this a fast fill? + let fast_fill = msg + .fast_fill() + .ok_or(MatchingEngineError::InvalidPayloadId)?; + + &fast_fill.fill().source_chain().to_be_bytes() + }, + ], + bump, )] - cctp_mint_recipient: Account<'info, token::TokenAccount>, + local_custody_token: Box>, token_program: Program<'info, token::Token>, system_program: Program<'info, System>, @@ -74,49 +98,33 @@ pub struct CompleteFastFill<'info> { /// TODO: docstring pub fn complete_fast_fill(ctx: Context) -> Result<()> { - let vaa = VaaAccount::load_unchecked(&ctx.accounts.vaa); - - // Emitter must be the matching engine (this program). - { - let emitter = vaa.emitter_info(); - require_eq!( - emitter.chain, - SOLANA_CHAIN, - MatchingEngineError::InvalidEmitterForFastFill - ); - require_keys_eq!( - Pubkey::from(emitter.address), - ctx.accounts.custodian.key(), - MatchingEngineError::InvalidEmitterForFastFill - ); - - // Fill redeemed fast fill data. - ctx.accounts.redeemed_fast_fill.set_inner(RedeemedFastFill { - bump: ctx.bumps.redeemed_fast_fill, - vaa_hash: vaa.digest().0, - sequence: emitter.sequence, - }); - } - - // Check whether this message is a deposit message we recognize. - let msg = LiquidityLayerMessage::try_from(vaa.payload()) - .map_err(|_| error!(MatchingEngineError::InvalidVaa))?; - - // Is this a fast fill? - let fast_fill = msg - .fast_fill() - .ok_or(MatchingEngineError::InvalidPayloadId)?; + let vaa = ctx.accounts.fast_fill_vaa.load_unchecked(); + + // Fill redeemed fast fill data. + ctx.accounts.redeemed_fast_fill.set_inner(RedeemedFastFill { + bump: ctx.bumps.redeemed_fast_fill, + vaa_hash: vaa.digest().0, + sequence: vaa.sequence(), + }); + + let fast_fill = LiquidityLayerMessage::try_from(vaa.payload()) + .unwrap() + .to_fast_fill_unchecked(); // Finally transfer to local token router's token account. token::transfer( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), token::Transfer { - from: ctx.accounts.cctp_mint_recipient.to_account_info(), + from: ctx.accounts.local_custody_token.to_account_info(), to: ctx.accounts.token_router_custody_token.to_account_info(), - authority: ctx.accounts.custodian.to_account_info(), + authority: ctx.accounts.router_endpoint.to_account_info(), }, - &[Custodian::SIGNER_SEEDS], + &[&[ + RouterEndpoint::SEED_PREFIX, + &ctx.accounts.router_endpoint.chain.to_be_bytes(), + &[ctx.accounts.router_endpoint.bump], + ]], ), fast_fill.amount(), ) diff --git a/solana/programs/matching-engine/src/state/auction.rs b/solana/programs/matching-engine/src/state/auction.rs index 07d97def2..b6e187e7e 100644 --- a/solana/programs/matching-engine/src/state/auction.rs +++ b/solana/programs/matching-engine/src/state/auction.rs @@ -1,12 +1,19 @@ use crate::state::AuctionParameters; use anchor_lang::prelude::*; -#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, InitSpace, PartialEq, Eq)] +#[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, InitSpace, PartialEq, Eq, Default)] pub enum AuctionStatus { + #[default] NotStarted, Active, - Completed { slot: u64 }, - Settled { base_fee: u64, penalty: Option }, + Completed { + slot: u64, + execute_penalty: Option, + }, + Settled { + base_fee: u64, + total_penalty: Option, + }, } impl std::fmt::Display for AuctionStatus { @@ -14,12 +21,22 @@ impl std::fmt::Display for AuctionStatus { match self { AuctionStatus::NotStarted => write!(f, "NotStarted"), AuctionStatus::Active => write!(f, "Active"), - AuctionStatus::Completed { slot } => write!(f, "Completed {{ slot: {} }}", slot), - AuctionStatus::Settled { base_fee, penalty } => { + AuctionStatus::Completed { + slot, + execute_penalty, + } => write!( + f, + "Completed {{ slot: {}, execute_penalty: {:?} }}", + slot, execute_penalty + ), + AuctionStatus::Settled { + base_fee, + total_penalty, + } => { write!( f, - "Settled {{ base_fee: {}, penalty: {:?} }}", - base_fee, penalty + "Settled {{ base_fee: {}, total_penalty: {:?} }}", + base_fee, total_penalty ) } } @@ -30,6 +47,8 @@ impl std::fmt::Display for AuctionStatus { pub struct AuctionInfo { pub config_id: u32, + pub custody_token_bump: u8, + /// Sequence of the fast market order VAA. pub vaa_sequence: u64, @@ -58,6 +77,8 @@ pub struct AuctionInfo { /// The amount of tokens to be sent to the user. For CCTP fast transfers, this amount will equal /// the [amount_in](Self::amount_in). pub amount_out: u64, + + pub end_early: bool, } impl AuctionInfo { diff --git a/solana/programs/matching-engine/src/state/custodian.rs b/solana/programs/matching-engine/src/state/custodian.rs index 323b8fb1a..56929dec6 100644 --- a/solana/programs/matching-engine/src/state/custodian.rs +++ b/solana/programs/matching-engine/src/state/custodian.rs @@ -1,6 +1,4 @@ use anchor_lang::prelude::*; - -use crate::error::MatchingEngineError; use common::admin; #[account] @@ -57,50 +55,6 @@ impl admin::OwnerAssistant for Custodian { } } -#[derive(Accounts)] -pub struct OwnerCustodian<'info> { - pub owner: Signer<'info>, - - #[account(has_one = owner @ MatchingEngineError::OwnerOnly)] - pub custodian: Account<'info, Custodian>, -} - -#[derive(Accounts)] -pub struct OwnerMutCustodian<'info> { - pub owner: Signer<'info>, - - #[account( - mut, - has_one = owner @ MatchingEngineError::OwnerOnly, - )] - pub custodian: Account<'info, Custodian>, -} - -#[derive(Accounts)] -pub struct AdminCustodian<'info> { - #[account( - constraint = { - admin::utils::assistant::only_authorized(&custodian, &owner_or_assistant.key()) - } @ MatchingEngineError::OwnerOrAssistantOnly, - )] - pub owner_or_assistant: Signer<'info>, - - pub custodian: Account<'info, Custodian>, -} - -#[derive(Accounts)] -pub struct AdminMutCustodian<'info> { - #[account( - constraint = { - admin::utils::assistant::only_authorized(&custodian, &owner_or_assistant.key()) - } @ MatchingEngineError::OwnerOrAssistantOnly, - )] - pub owner_or_assistant: Signer<'info>, - - #[account(mut)] - pub custodian: Account<'info, Custodian>, -} - #[cfg(test)] mod test { use solana_program::pubkey::Pubkey; diff --git a/solana/programs/matching-engine/src/state/mod.rs b/solana/programs/matching-engine/src/state/mod.rs index 4027e94dd..cb5247f46 100644 --- a/solana/programs/matching-engine/src/state/mod.rs +++ b/solana/programs/matching-engine/src/state/mod.rs @@ -1,7 +1,7 @@ mod auction_config; pub use auction_config::*; -mod auction; +pub(crate) mod auction; pub use auction::*; pub(crate) mod custodian; diff --git a/solana/programs/matching-engine/src/state/router_endpoint.rs b/solana/programs/matching-engine/src/state/router_endpoint.rs index 96ef3e37b..45f3dde89 100644 --- a/solana/programs/matching-engine/src/state/router_endpoint.rs +++ b/solana/programs/matching-engine/src/state/router_endpoint.rs @@ -1,6 +1,3 @@ -use std::ops::Deref; - -use crate::error::MatchingEngineError; use anchor_lang::prelude::*; #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq, InitSpace)] @@ -38,47 +35,3 @@ pub struct RouterEndpoint { impl RouterEndpoint { pub const SEED_PREFIX: &'static [u8] = b"endpoint"; } - -#[derive(Accounts)] -pub(crate) struct ExistingMutRouterEndpoint<'info> { - #[account( - mut, - seeds = [ - RouterEndpoint::SEED_PREFIX, - &inner.chain.to_be_bytes() - ], - bump = inner.bump, - )] - pub inner: Account<'info, RouterEndpoint>, -} - -#[derive(Accounts)] -pub struct LiveRouterEndpoint<'info> { - #[account( - seeds = [ - RouterEndpoint::SEED_PREFIX, - &inner.chain.to_be_bytes() - ], - bump = inner.bump, - constraint = { - inner.protocol != MessageProtocol::None - } @ MatchingEngineError::EndpointDisabled, - )] - pub inner: Account<'info, RouterEndpoint>, -} - -impl<'info> Deref for LiveRouterEndpoint<'info> { - type Target = RouterEndpoint; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -#[derive(Accounts)] -pub struct LiveRouterEndpointPair<'info> { - pub from: LiveRouterEndpoint<'info>, - - #[account(constraint = from.chain != to.chain @ MatchingEngineError::SameEndpoint)] - pub to: LiveRouterEndpoint<'info>, -} diff --git a/solana/programs/matching-engine/src/utils/auction.rs b/solana/programs/matching-engine/src/utils/auction.rs index 247404d24..62892f8cc 100644 --- a/solana/programs/matching-engine/src/utils/auction.rs +++ b/solana/programs/matching-engine/src/utils/auction.rs @@ -12,6 +12,7 @@ pub struct DepositPenalty { } #[inline] +#[allow(clippy::cast_possible_truncation)] pub fn compute_deposit_penalty( params: &AuctionParameters, info: &AuctionInfo, @@ -41,8 +42,9 @@ pub fn compute_deposit_penalty( } #[inline] -pub fn compute_min_offer_delta(params: &AuctionParameters, info: &AuctionInfo) -> u64 { - mul_bps_unsafe(info.offer_price, params.min_offer_delta_bps) +pub fn compute_min_allowed_offer(params: &AuctionParameters, info: &AuctionInfo) -> u64 { + info.offer_price + .saturating_sub(mul_bps_unsafe(info.offer_price, params.min_offer_delta_bps)) } pub fn require_valid_parameters(params: &AuctionParameters) -> Result<()> { @@ -81,6 +83,7 @@ fn split_user_penalty_reward(params: &AuctionParameters, amount: u64) -> Deposit } #[inline] +#[allow(clippy::cast_possible_truncation)] fn mul_bps_unsafe(amount: u64, bps: u32) -> u64 { ((amount as u128 * bps as u128) / FEE_PRECISION_MAX as u128) as u64 } @@ -289,9 +292,8 @@ mod test { let offer_price = 10000000; let (info, _) = set_up(0, None, offer_price); - let min_offer_delta = compute_min_offer_delta(¶ms, &info); - - assert_eq!(min_offer_delta, offer_price); + let allowed_offer = compute_min_allowed_offer(¶ms, &info); + assert_eq!(allowed_offer, 0); } #[test] @@ -302,9 +304,8 @@ mod test { let offer_price = 10000000; let (info, _) = set_up(0, None, offer_price); - let min_offer_delta = compute_min_offer_delta(¶ms, &info); - - assert_eq!(min_offer_delta, 0); + let allowed_offer = compute_min_allowed_offer(¶ms, &info); + assert_eq!(allowed_offer, offer_price); } #[test] @@ -314,9 +315,8 @@ mod test { let offer_price = 10000000; let (info, _) = set_up(0, None, offer_price); - let min_offer_delta = compute_min_offer_delta(¶ms, &info); - - assert_eq!(min_offer_delta, 500000); + let allowed_offer = compute_min_allowed_offer(¶ms, &info); + assert_eq!(allowed_offer, offer_price - 500000); } fn set_up( @@ -328,6 +328,7 @@ mod test { ( AuctionInfo { security_deposit, + custody_token_bump: Default::default(), vaa_sequence: Default::default(), start_slot: START, config_id: Default::default(), @@ -337,6 +338,7 @@ mod test { amount_in: Default::default(), offer_price, amount_out: Default::default(), + end_early: Default::default(), }, START + slots_elapsed.unwrap_or_default(), ) diff --git a/solana/programs/matching-engine/src/utils/mod.rs b/solana/programs/matching-engine/src/utils/mod.rs index 7ea004091..b594a6c72 100644 --- a/solana/programs/matching-engine/src/utils/mod.rs +++ b/solana/programs/matching-engine/src/utils/mod.rs @@ -2,70 +2,43 @@ pub mod admin; pub mod auction; -use crate::{ - error::MatchingEngineError, - state::{Auction, AuctionConfig, AuctionStatus, RouterEndpoint}, -}; -use anchor_lang::prelude::*; -use common::wormhole_cctp_solana::wormhole::VaaAccount; +pub(crate) mod wormhole; -pub fn require_valid_router_path( - vaa: &VaaAccount<'_>, - source_endpoint: &RouterEndpoint, - target_endpoint: &RouterEndpoint, - expected_target_chain: u16, -) -> Result<()> { - let emitter = vaa.emitter_info(); - require_eq!( - source_endpoint.chain, - emitter.chain, - MatchingEngineError::ErrInvalidSourceRouter - ); - require!( - source_endpoint.address == emitter.address, - MatchingEngineError::ErrInvalidSourceRouter - ); - require_eq!( - target_endpoint.chain, - expected_target_chain, - MatchingEngineError::ErrInvalidTargetRouter - ); +use crate::{error::MatchingEngineError, state::RouterEndpoint}; +use anchor_lang::prelude::*; +use common::wormhole_cctp_solana::wormhole::{VaaAccount, SOLANA_CHAIN}; - Ok(()) +pub trait VaaDigest { + fn digest(&self) -> [u8; 32]; } -pub fn is_valid_active_auction( - config: &AuctionConfig, - auction: &Auction, - best_offer_token: Option, - initial_offer_token: Option, -) -> Result { - match (&auction.status, &auction.info) { - (AuctionStatus::Active, Some(info)) => { - require_eq!( - info.config_id, - config.id, - MatchingEngineError::AuctionConfigMismatch - ); +#[derive(PartialEq, Eq)] +struct WrappedHash([u8; 32]); + +impl std::fmt::Display for WrappedHash { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "0x{}", hex::encode(self.0)) + } +} - if let Some(best_offer_token) = best_offer_token { - require_keys_eq!( - best_offer_token, - info.best_offer_token, - MatchingEngineError::BestOfferTokenMismatch, - ); - } +pub fn require_vaa_hash_equals(ctx: &A, vaa: &VaaAccount) -> Result +where + A: VaaDigest, +{ + require_eq!( + WrappedHash(vaa.digest().0), + WrappedHash(ctx.digest()), + MatchingEngineError::InvalidVaa + ); + Ok(true) +} - if let Some(initial_offer_token) = initial_offer_token { - require_keys_eq!( - initial_offer_token, - info.initial_offer_token, - MatchingEngineError::InitialOfferTokenMismatch, - ); - } +pub fn require_local_endpoint(endpoint: &RouterEndpoint) -> Result { + require_eq!( + endpoint.chain, + SOLANA_CHAIN, + MatchingEngineError::InvalidEndpoint + ); - Ok(true) - } - _ => err!(MatchingEngineError::AuctionNotActive), - } + Ok(true) } diff --git a/solana/programs/matching-engine/src/utils/wormhole.rs b/solana/programs/matching-engine/src/utils/wormhole.rs new file mode 100644 index 000000000..7ef214a66 --- /dev/null +++ b/solana/programs/matching-engine/src/utils/wormhole.rs @@ -0,0 +1,71 @@ +use crate::{composite::*, state::Custodian}; +use anchor_lang::prelude::*; +use common::{ + wormhole_cctp_solana::wormhole::core_bridge_program, wormhole_io::TypePrefixedPayload, +}; + +pub struct PostMatchingEngineMessage<'ctx, 'info> { + pub wormhole: &'ctx WormholePublishMessage<'info>, + pub core_message: &'ctx AccountInfo<'info>, + pub custodian: &'ctx CheckedCustodian<'info>, + pub payer: &'ctx Signer<'info>, + pub system_program: &'ctx Program<'info, System>, + pub sysvars: &'ctx RequiredSysvars<'info>, +} + +pub fn post_matching_engine_message( + ctx: PostMatchingEngineMessage, + message: M, + sequence_seed: &[u8; 8], + core_message_bump_seed: u8, +) -> Result<()> +where + M: TypePrefixedPayload, +{ + let PostMatchingEngineMessage { + wormhole, + core_message, + custodian, + payer, + system_program, + sysvars, + } = ctx; + + let WormholePublishMessage { + core_bridge_program, + config: core_bridge_config, + emitter_sequence, + fee_collector, + } = wormhole; + + core_bridge_program::cpi::post_message( + CpiContext::new_with_signer( + core_bridge_program.to_account_info(), + core_bridge_program::cpi::PostMessage { + payer: payer.to_account_info(), + message: core_message.to_account_info(), + emitter: custodian.to_account_info(), + config: core_bridge_config.to_account_info(), + emitter_sequence: emitter_sequence.to_account_info(), + fee_collector: fee_collector.to_account_info(), + system_program: system_program.to_account_info(), + clock: sysvars.clock.to_account_info(), + rent: sysvars.rent.to_account_info(), + }, + &[ + Custodian::SIGNER_SEEDS, + &[ + common::constants::CORE_MESSAGE_SEED_PREFIX, + payer.key().as_ref(), + sequence_seed, + &[core_message_bump_seed], + ], + ], + ), + core_bridge_program::cpi::PostMessageArgs { + nonce: common::constants::WORMHOLE_MESSAGE_NONCE, + payload: message.to_vec_payload(), + commitment: core_bridge_program::Commitment::Finalized, + }, + ) +} diff --git a/solana/programs/token-router/Cargo.toml b/solana/programs/token-router/Cargo.toml index c09cb1433..3dcc2ed68 100644 --- a/solana/programs/token-router/Cargo.toml +++ b/solana/programs/token-router/Cargo.toml @@ -35,4 +35,7 @@ ruint.workspace = true cfg-if.workspace = true [dev-dependencies] -hex-literal.workspace = true \ No newline at end of file +hex-literal.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/solana/programs/token-router/src/processor/admin/set_pause.rs b/solana/programs/token-router/src/processor/admin/set_pause.rs index 9ea275972..f72ff0eba 100644 --- a/solana/programs/token-router/src/processor/admin/set_pause.rs +++ b/solana/programs/token-router/src/processor/admin/set_pause.rs @@ -10,9 +10,11 @@ pub struct SetPause<'info> { mut, seeds = [Custodian::SEED_PREFIX], bump = Custodian::BUMP, - constraint = { - only_authorized(&custodian, &owner_or_assistant.key()) - } @ TokenRouterError::OwnerOrAssistantOnly, + constraint = only_authorized( + &custodian, + &owner_or_assistant, + error!(TokenRouterError::OwnerOrAssistantOnly) + )? )] /// Sender Config account. This program requires that the `owner` specified /// in the context equals the pubkey specified in this account. Mutable. diff --git a/solana/programs/token-router/src/processor/admin/update/owner_assistant.rs b/solana/programs/token-router/src/processor/admin/update/owner_assistant.rs index d099b3655..52266e3fa 100644 --- a/solana/programs/token-router/src/processor/admin/update/owner_assistant.rs +++ b/solana/programs/token-router/src/processor/admin/update/owner_assistant.rs @@ -28,7 +28,7 @@ pub struct UpdateOwnerAssistant<'info> { pub fn update_owner_assistant(ctx: Context) -> Result<()> { common::admin::utils::assistant::transfer_owner_assistant( &mut ctx.accounts.custodian, - &ctx.accounts.new_owner_assistant.key(), + &ctx.accounts.new_owner_assistant, ); // Done. diff --git a/solana/programs/token-router/src/processor/redeem_fill/fast.rs b/solana/programs/token-router/src/processor/redeem_fill/fast.rs index 3a3401538..21f9e982b 100644 --- a/solana/programs/token-router/src/processor/redeem_fill/fast.rs +++ b/solana/programs/token-router/src/processor/redeem_fill/fast.rs @@ -75,9 +75,10 @@ pub struct RedeemFastFill<'info> { /// CHECK: Seeds must be \["endpoint", SOLANA_CHAIN.to_be_bytes()\] (Matching Engine program). matching_engine_router_endpoint: UncheckedAccount<'info>, - /// CHECK: Mutable. Seeds must be \["custody"] (Matching Engine program). + /// CHECK: Mutable. Seeds must be \["local-custody", source_chain.to_be_bytes()\] + /// (Matching Engine program). #[account(mut)] - matching_engine_cctp_mint_recipient: UncheckedAccount<'info>, + matching_engine_local_custody_token: UncheckedAccount<'info>, matching_engine_program: Program<'info, matching_engine::program::MatchingEngine>, token_program: Program<'info, token::Token>, @@ -100,8 +101,12 @@ fn handle_redeem_fast_fill(ctx: Context) -> Result<()> { ctx.accounts.matching_engine_program.to_account_info(), matching_engine::cpi::accounts::CompleteFastFill { payer: ctx.accounts.payer.to_account_info(), - custodian: ctx.accounts.matching_engine_custodian.to_account_info(), - vaa: ctx.accounts.vaa.to_account_info(), + custodian: matching_engine::cpi::accounts::CheckedCustodian { + custodian: ctx.accounts.matching_engine_custodian.to_account_info(), + }, + fast_fill_vaa: matching_engine::cpi::accounts::LiquidityLayerVaa { + vaa: ctx.accounts.vaa.to_account_info(), + }, redeemed_fast_fill: ctx .accounts .matching_engine_redeemed_fast_fill @@ -109,14 +114,14 @@ fn handle_redeem_fast_fill(ctx: Context) -> Result<()> { token_router_emitter: ctx.accounts.custodian.to_account_info(), token_router_custody_token: ctx.accounts.prepared_custody_token.to_account_info(), router_endpoint: LiveRouterEndpoint { - inner: ctx + endpoint: ctx .accounts .matching_engine_router_endpoint .to_account_info(), }, - cctp_mint_recipient: ctx + local_custody_token: ctx .accounts - .matching_engine_cctp_mint_recipient + .matching_engine_local_custody_token .to_account_info(), token_program: ctx.accounts.token_program.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), diff --git a/solana/programs/upgrade-manager/Cargo.toml b/solana/programs/upgrade-manager/Cargo.toml index 46242fcb0..53460303d 100644 --- a/solana/programs/upgrade-manager/Cargo.toml +++ b/solana/programs/upgrade-manager/Cargo.toml @@ -35,4 +35,7 @@ hex.workspace = true cfg-if.workspace = true [dev-dependencies] -hex-literal.workspace = true \ No newline at end of file +hex-literal.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/solana/programs/upgrade-manager/src/processor/token_router_upgrade/execute.rs b/solana/programs/upgrade-manager/src/processor/token_router_upgrade/execute.rs index 32ecd2c5b..0c29e2395 100644 --- a/solana/programs/upgrade-manager/src/processor/token_router_upgrade/execute.rs +++ b/solana/programs/upgrade-manager/src/processor/token_router_upgrade/execute.rs @@ -79,7 +79,7 @@ pub fn execute_token_router_upgrade(ctx: Context) -> bump: ctx.bumps.upgrade_receipt, owner: *ctx.accounts.owner.key, buffer: *ctx.accounts.token_router_buffer.key, - slot: Clock::get().map(|clock| clock.slot)?, + slot: Clock::get().unwrap().slot, }); // First set the buffer's authority to the upgrade authority. diff --git a/solana/target/idl/matching_engine.json b/solana/target/idl/matching_engine.json index 02275e37f..908028795 100644 --- a/solana/target/idl/matching_engine.json +++ b/solana/target/idl/matching_engine.json @@ -12,15 +12,22 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "vaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "name": "fastFillVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } ] }, { @@ -42,23 +49,16 @@ "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": false, "isSigner": false } ] }, { - "name": "cctpMintRecipient", + "name": "localCustodyToken", "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "Mutable. Seeds must be \\[\"custody\"\\]." - ] + "isSigner": false }, { "name": "tokenProgram", @@ -83,27 +83,32 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } ] }, { "name": "finalizedVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "[verify_vaa_and_mint](wormhole_cctp_solana::cpi::verify_vaa_and_mint)." + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } ] }, { @@ -112,97 +117,120 @@ "isSigner": false }, { - "name": "cctpMintRecipient", + "name": "preparedCustodyToken", "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "messageTransmitterAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", - "isMut": false, "isSigner": false }, { - "name": "usedNonces", + "name": "auction", "isMut": true, "isSigner": false, - "docs": [ - "first_nonce.to_string()\\] (CCTP Message Transmitter program)." - ] - }, - { - "name": "messageTransmitterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." - ] - }, - { - "name": "tokenMinter", - "isMut": false, - "isSigner": false + "isOptional": true }, { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Token Messenger Minter's Local Token account. This program uses the mint of this account to", - "validate the `mint_recipient` token account's mint.", - "" + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } ] }, { - "name": "tokenPair", - "isMut": false, - "isSigner": false, - "docs": [ - "Token Messenger Minter program)." + "name": "cctp", + "accounts": [ + { + "name": "mintRecipient", + "accounts": [ + { + "name": "mintRecipient", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "messageTransmitterAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterConfig", + "isMut": false, + "isSigner": false + }, + { + "name": "usedNonces", + "isMut": true, + "isSigner": false, + "docs": [ + "first_nonce.to_string()\\] (CCTP Message Transmitter program)." + ] + }, + { + "name": "messageTransmitterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessenger", + "isMut": false, + "isSigner": false + }, + { + "name": "remoteTokenMessenger", + "isMut": false, + "isSigner": false, + "docs": [ + "Messenger Minter program)." + ] + }, + { + "name": "tokenMinter", + "isMut": false, + "isSigner": false + }, + { + "name": "localToken", + "isMut": true, + "isSigner": false, + "docs": [ + "Token Messenger Minter's Local Token account. This program uses the mint of this account to", + "validate the `mint_recipient` token account's mint.", + "" + ] + }, + { + "name": "tokenPair", + "isMut": false, + "isSigner": false, + "docs": [ + "Token Messenger Minter program)." + ] + }, + { + "name": "tokenMessengerMinterCustodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenMessengerMinterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessengerMinterProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterProgram", + "isMut": false, + "isSigner": false + } ] }, - { - "name": "tokenMessengerMinterCustodyToken", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -227,29 +255,20 @@ "name": "settleAuctionComplete", "accounts": [ { - "name": "custodian", - "isMut": false, + "name": "executor", + "isMut": true, "isSigner": false, "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "we will always reward the owner of the executor token account with the lamports from the", + "prepared order response and its custody token account when we close these accounts. This", + "means we disregard the `prepared_by` field in the prepared order response." ] }, { - "name": "preparedBy", - "isMut": true, - "isSigner": false - }, - { - "name": "preparedOrderResponse", + "name": "executorToken", "isMut": true, "isSigner": false }, - { - "name": "auction", - "isMut": false, - "isSigner": false - }, { "name": "bestOfferToken", "isMut": true, @@ -262,18 +281,19 @@ ] }, { - "name": "cctpMintRecipient", + "name": "preparedOrderResponse", "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "Mutable. Seeds must be \\[\"custody\"\\].", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] + "isSigner": false + }, + { + "name": "preparedCustodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "auction", + "isMut": true, + "isSigner": false }, { "name": "tokenProgram", @@ -297,46 +317,23 @@ "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." - ] - }, - { - "name": "preparedOrderResponse", + "name": "coreMessage", "isMut": true, "isSigner": false }, { - "name": "auction", + "name": "cctpMessage", "isMut": true, - "isSigner": false, - "docs": [ - "There should be no account data here because an auction was never created." - ] + "isSigner": false }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -351,310 +348,60 @@ ] }, { - "name": "fromRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Circle-supported mint.", - "", - "Token Messenger Minter program's local token account." - ] - }, - { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterSenderAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." - ] - }, - { - "name": "tokenMinter", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." - ] - }, - { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Local token account, which this program uses to validate the `mint` used to burn.", - "" - ] - }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false - } - ], - "args": [] - }, - { - "name": "settleAuctionNoneLocal", - "accounts": [ - { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "payerSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." - ] - }, - { - "name": "preparedOrderResponse", - "isMut": true, - "isSigner": false - }, - { - "name": "auction", - "isMut": true, - "isSigner": false, - "docs": [ - "There should be no account data here because an auction was never created." - ] - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "feeRecipientToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Destination token account, which the redeemer may not own. But because the redeemer is a", - "signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent", - "to any account he chooses (this one).", - "" - ] - }, - { - "name": "fromRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false - } - ], - "args": [] - }, - { - "name": "settleAuctionActiveCctp", - "accounts": [ - { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "payerSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "name": "prepared", + "accounts": [ + { + "name": "by", + "isMut": true, + "isSigner": false + }, + { + "name": "orderResponse", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + } ] }, { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "name": "fastOrderPath", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "from", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "to", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + } ] }, - { - "name": "preparedOrderResponse", - "isMut": true, - "isSigner": false - }, { "name": "auction", "isMut": true, @@ -664,131 +411,110 @@ ] }, { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "executorToken", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Circle-supported mint.", - "", - "Token Messenger Minter program's local token account." - ] - }, - { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterSenderAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." - ] - }, - { - "name": "tokenMinter", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } ] }, { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Local token account, which this program uses to validate the `mint` used to burn.", - "" + "name": "cctp", + "accounts": [ + { + "name": "burnSource", + "accounts": [ + { + "name": "mintRecipient", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "mint", + "isMut": true, + "isSigner": false, + "docs": [ + "Circle-supported mint.", + "", + "Token Messenger Minter program's local token account." + ] + }, + { + "name": "tokenMessengerMinterSenderAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterConfig", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenMessenger", + "isMut": false, + "isSigner": false + }, + { + "name": "remoteTokenMessenger", + "isMut": false, + "isSigner": false, + "docs": [ + "Messenger Minter program)." + ] + }, + { + "name": "tokenMinter", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + ] + }, + { + "name": "localToken", + "isMut": true, + "isSigner": false, + "docs": [ + "Local token account, which this program uses to validate the `mint` used to burn.", + "" + ] + }, + { + "name": "tokenMessengerMinterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessengerMinterProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterProgram", + "isMut": false, + "isSigner": false + } ] }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -800,20 +526,33 @@ "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] }, { - "name": "settleAuctionActiveLocal", + "name": "settleAuctionNoneLocal", "accounts": [ { "name": "payer", @@ -826,96 +565,124 @@ "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, + "name": "coreMessage", + "isMut": true, "isSigner": false }, { - "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { - "name": "preparedOrderResponse", - "isMut": true, - "isSigner": false - }, - { - "name": "auction", + "name": "feeRecipientToken", "isMut": true, "isSigner": false, "docs": [ - "There should be no account data here because an auction was never created." + "Destination token account, which the redeemer may not own. But because the redeemer is a", + "signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent", + "to any account he chooses (this one).", + "" ] }, { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." + "name": "prepared", + "accounts": [ + { + "name": "by", + "isMut": true, + "isSigner": false + }, + { + "name": "orderResponse", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + } ] }, { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "executorToken", - "isMut": true, - "isSigner": false + "name": "fastOrderPath", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "from", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "to", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + } + ] }, { - "name": "cctpMintRecipient", + "name": "auction", "isMut": true, "isSigner": false, "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." + "There should be no account data here because an auction was never created." ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "coreFeeCollector", + "name": "localCustodyToken", "isMut": true, "isSigner": false }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -927,14 +694,27 @@ "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] @@ -993,9 +773,14 @@ "isSigner": false }, { - "name": "mint", - "isMut": false, - "isSigner": false + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] }, { "name": "programData", @@ -1064,8 +849,13 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -1074,6 +864,33 @@ "isMut": true, "isSigner": false }, + { + "name": "localRouterEndpoint", + "isMut": false, + "isSigner": false, + "docs": [ + "Local router endpoint PDA.", + "", + "NOTE: This account may not exist yet. But we need to pass it since it will be the owner of", + "the local custody token account.", + "" + ] + }, + { + "name": "localCustodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] + }, { "name": "remoteTokenMessenger", "isMut": false, @@ -1082,6 +899,11 @@ "Messenger Minter program)." ] }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, { "name": "systemProgram", "isMut": false, @@ -1115,8 +937,13 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -1169,8 +996,13 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -1178,7 +1010,7 @@ "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": true, "isSigner": false } @@ -1200,8 +1032,13 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -1209,7 +1046,7 @@ "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": true, "isSigner": false } @@ -1246,8 +1083,13 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -1255,7 +1097,7 @@ "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": true, "isSigner": false } @@ -1300,8 +1142,13 @@ }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] }, @@ -1350,8 +1197,13 @@ }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] } @@ -1376,8 +1228,13 @@ }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] }, @@ -1424,8 +1281,13 @@ }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] }, @@ -1451,18 +1313,25 @@ "name": "updateOwnerAssistant", "accounts": [ { - "name": "owner", - "isMut": false, - "isSigner": true, - "docs": [ - "Owner of the program set in the [`OwnerConfig`] account." + "name": "admin", + "accounts": [ + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] + } ] }, - { - "name": "custodian", - "isMut": true, - "isSigner": false - }, { "name": "newOwnerAssistant", "isMut": false, @@ -1479,14 +1348,24 @@ "name": "updateFeeRecipient", "accounts": [ { - "name": "ownerOrAssistant", - "isMut": true, - "isSigner": true - }, - { - "name": "custodian", - "isMut": true, - "isSigner": false + "name": "admin", + "accounts": [ + { + "name": "ownerOrAssistant", + "isMut": false, + "isSigner": true + }, + { + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] + } + ] }, { "name": "newFeeRecipientToken", @@ -1515,11 +1394,12 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -1528,9 +1408,39 @@ "isSigner": false }, { - "name": "fastVaa", - "isMut": false, - "isSigner": false + "name": "fastOrderPath", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "from", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "to", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + } + ] }, { "name": "auction", @@ -1542,24 +1452,27 @@ ] }, { - "name": "fromRouterEndpoint", - "isMut": false, - "isSigner": false - }, - { - "name": "toRouterEndpoint", + "name": "offerToken", "isMut": false, - "isSigner": false + "isSigner": false, + "docs": [ + "the auction PDA." + ] }, { - "name": "offerToken", + "name": "auctionCustodyToken", "isMut": true, "isSigner": false }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] }, { "name": "systemProgram", @@ -1583,39 +1496,37 @@ "name": "improveOffer", "accounts": [ { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source", - "authority for CCTP transfers.", - "" + "name": "activeAuction", + "accounts": [ + { + "name": "auction", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "bestOfferToken", + "isMut": true, + "isSigner": false + } ] }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "offerAuthority", - "isMut": false, - "isSigner": true - }, - { - "name": "auction", - "isMut": true, - "isSigner": false - }, { "name": "offerToken", - "isMut": true, - "isSigner": false - }, - { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false + "isMut": false, + "isSigner": false, + "docs": [ + "the auction PDA." + ] }, { "name": "tokenProgram", @@ -1643,75 +1554,6 @@ "isMut": true, "isSigner": false }, - { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source", - "authority for CCTP transfers.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false - }, - { - "name": "auction", - "isMut": true, - "isSigner": false - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false - }, - { - "name": "executorToken", - "isMut": true, - "isSigner": false - }, - { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "initialOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Also the burn_source token account.", - "" - ] - }, - { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Circle-supported mint.", - "", - "Token Messenger Minter program's local token account." - ] - }, - { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, { "name": "coreMessage", "isMut": true, @@ -1723,75 +1565,180 @@ "isSigner": false }, { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterSenderAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." + "name": "executeOrder", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "activeAuction", + "accounts": [ + { + "name": "auction", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "bestOfferToken", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "toRouterEndpoint", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "executorToken", + "isMut": true, + "isSigner": false + }, + { + "name": "initialOfferToken", + "isMut": true, + "isSigner": false + } ] }, { - "name": "tokenMinter", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } ] }, { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Local token account, which this program uses to validate the `mint` used to burn.", - "" + "name": "cctp", + "accounts": [ + { + "name": "burnSource", + "accounts": [ + { + "name": "mintRecipient", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "mint", + "isMut": true, + "isSigner": false, + "docs": [ + "Circle-supported mint.", + "", + "Token Messenger Minter program's local token account." + ] + }, + { + "name": "tokenMessengerMinterSenderAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterConfig", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenMessenger", + "isMut": false, + "isSigner": false + }, + { + "name": "remoteTokenMessenger", + "isMut": false, + "isSigner": false, + "docs": [ + "Messenger Minter program)." + ] + }, + { + "name": "tokenMinter", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + ] + }, + { + "name": "localToken", + "isMut": true, + "isSigner": false, + "docs": [ + "Local token account, which this program uses to validate the `mint` used to burn.", + "" + ] + }, + { + "name": "tokenMessengerMinterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessengerMinterProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterProgram", + "isMut": false, + "isSigner": false + } ] }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, { "name": "systemProgram", "isMut": false, @@ -1803,14 +1750,27 @@ "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] @@ -1829,84 +1789,110 @@ "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source", - "authority for CCTP transfers.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false - }, - { - "name": "auction", - "isMut": true, - "isSigner": false - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false - }, - { - "name": "executorToken", - "isMut": true, - "isSigner": false - }, - { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "initialOfferToken", + "name": "coreMessage", "isMut": true, "isSigner": false }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Also the burn_source token account.", - "" + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false + "name": "executeOrder", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "activeAuction", + "accounts": [ + { + "name": "auction", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "bestOfferToken", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "toRouterEndpoint", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "executorToken", + "isMut": true, + "isSigner": false + }, + { + "name": "initialOfferToken", + "isMut": true, + "isSigner": false + } + ] }, { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "coreFeeCollector", + "name": "localCustodyToken", "isMut": true, "isSigner": false }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, { "name": "systemProgram", "isMut": false, @@ -1918,14 +1904,27 @@ "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] @@ -1943,8 +1942,13 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -2294,6 +2298,10 @@ "name": "configId", "type": "u32" }, + { + "name": "custodyTokenBump", + "type": "u8" + }, { "name": "vaaSequence", "docs": [ @@ -2358,6 +2366,10 @@ "the [amount_in](Self::amount_in)." ], "type": "u64" + }, + { + "name": "endEarly", + "type": "bool" } ] } @@ -2415,6 +2427,12 @@ { "name": "slot", "type": "u64" + }, + { + "name": "executePenalty", + "type": { + "option": "u64" + } } ] }, @@ -2426,7 +2444,7 @@ "type": "u64" }, { - "name": "penalty", + "name": "totalPenalty", "type": { "option": "u64" } @@ -2506,11 +2524,6 @@ "name": "OwnerOrAssistantOnly", "msg": "OwnerOrAssistantOnly" }, - { - "code": 6006, - "name": "InvalidCustodyToken", - "msg": "InvalidCustodyToken" - }, { "code": 6008, "name": "CpiDisallowed", @@ -2546,11 +2559,6 @@ "name": "ImmutableProgram", "msg": "ImmutableProgram" }, - { - "code": 6259, - "name": "NotUsdc", - "msg": "NotUsdc" - }, { "code": 6514, "name": "InvalidNewOwner", @@ -2733,8 +2741,8 @@ }, { "code": 6556, - "name": "BestOfferTokenMismatch", - "msg": "BestOfferTokenMismatch" + "name": "ExecutorTokenMismatch", + "msg": "ExecutorTokenMismatch" }, { "code": 6557, diff --git a/solana/target/idl/token_router.json b/solana/target/idl/token_router.json index 1e35b5455..227335459 100644 --- a/solana/target/idl/token_router.json +++ b/solana/target/idl/token_router.json @@ -547,9 +547,12 @@ "isSigner": false }, { - "name": "matchingEngineCctpMintRecipient", + "name": "matchingEngineLocalCustodyToken", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "(Matching Engine program)." + ] }, { "name": "matchingEngineProgram", diff --git a/solana/target/types/matching_engine.ts b/solana/target/types/matching_engine.ts index 7f26e7c5a..21819270f 100644 --- a/solana/target/types/matching_engine.ts +++ b/solana/target/types/matching_engine.ts @@ -12,15 +12,22 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "vaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "name": "fastFillVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } ] }, { @@ -42,23 +49,16 @@ export type MatchingEngine = { "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": false, "isSigner": false } ] }, { - "name": "cctpMintRecipient", + "name": "localCustodyToken", "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "Mutable. Seeds must be \\[\"custody\"\\]." - ] + "isSigner": false }, { "name": "tokenProgram", @@ -83,27 +83,32 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } ] }, { "name": "finalizedVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "[verify_vaa_and_mint](wormhole_cctp_solana::cpi::verify_vaa_and_mint)." + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } ] }, { @@ -112,97 +117,120 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "cctpMintRecipient", + "name": "preparedCustodyToken", "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "messageTransmitterAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", - "isMut": false, "isSigner": false }, { - "name": "usedNonces", + "name": "auction", "isMut": true, "isSigner": false, - "docs": [ - "first_nonce.to_string()\\] (CCTP Message Transmitter program)." - ] - }, - { - "name": "messageTransmitterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." - ] - }, - { - "name": "tokenMinter", - "isMut": false, - "isSigner": false + "isOptional": true }, { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Token Messenger Minter's Local Token account. This program uses the mint of this account to", - "validate the `mint_recipient` token account's mint.", - "" + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } ] }, { - "name": "tokenPair", - "isMut": false, - "isSigner": false, - "docs": [ - "Token Messenger Minter program)." + "name": "cctp", + "accounts": [ + { + "name": "mintRecipient", + "accounts": [ + { + "name": "mintRecipient", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "messageTransmitterAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterConfig", + "isMut": false, + "isSigner": false + }, + { + "name": "usedNonces", + "isMut": true, + "isSigner": false, + "docs": [ + "first_nonce.to_string()\\] (CCTP Message Transmitter program)." + ] + }, + { + "name": "messageTransmitterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessenger", + "isMut": false, + "isSigner": false + }, + { + "name": "remoteTokenMessenger", + "isMut": false, + "isSigner": false, + "docs": [ + "Messenger Minter program)." + ] + }, + { + "name": "tokenMinter", + "isMut": false, + "isSigner": false + }, + { + "name": "localToken", + "isMut": true, + "isSigner": false, + "docs": [ + "Token Messenger Minter's Local Token account. This program uses the mint of this account to", + "validate the `mint_recipient` token account's mint.", + "" + ] + }, + { + "name": "tokenPair", + "isMut": false, + "isSigner": false, + "docs": [ + "Token Messenger Minter program)." + ] + }, + { + "name": "tokenMessengerMinterCustodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenMessengerMinterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessengerMinterProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterProgram", + "isMut": false, + "isSigner": false + } ] }, - { - "name": "tokenMessengerMinterCustodyToken", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -227,29 +255,20 @@ export type MatchingEngine = { "name": "settleAuctionComplete", "accounts": [ { - "name": "custodian", - "isMut": false, + "name": "executor", + "isMut": true, "isSigner": false, "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "we will always reward the owner of the executor token account with the lamports from the", + "prepared order response and its custody token account when we close these accounts. This", + "means we disregard the `prepared_by` field in the prepared order response." ] }, { - "name": "preparedBy", - "isMut": true, - "isSigner": false - }, - { - "name": "preparedOrderResponse", + "name": "executorToken", "isMut": true, "isSigner": false }, - { - "name": "auction", - "isMut": false, - "isSigner": false - }, { "name": "bestOfferToken", "isMut": true, @@ -262,18 +281,19 @@ export type MatchingEngine = { ] }, { - "name": "cctpMintRecipient", + "name": "preparedOrderResponse", "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "Mutable. Seeds must be \\[\"custody\"\\].", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] + "isSigner": false + }, + { + "name": "preparedCustodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "auction", + "isMut": true, + "isSigner": false }, { "name": "tokenProgram", @@ -297,46 +317,23 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." - ] - }, - { - "name": "preparedOrderResponse", + "name": "coreMessage", "isMut": true, "isSigner": false }, { - "name": "auction", + "name": "cctpMessage", "isMut": true, - "isSigner": false, - "docs": [ - "There should be no account data here because an auction was never created." - ] + "isSigner": false }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -351,116 +348,173 @@ export type MatchingEngine = { ] }, { - "name": "fromRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." + "name": "prepared", + "accounts": [ + { + "name": "by", + "isMut": true, + "isSigner": false + }, + { + "name": "orderResponse", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + } ] }, { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Circle-supported mint.", - "", - "Token Messenger Minter program's local token account." + "name": "fastOrderPath", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "from", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "to", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + } ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterSenderAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", + "name": "auction", "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, "isSigner": false, "docs": [ - "Messenger Minter program)." + "There should be no account data here because an auction was never created." ] }, { - "name": "tokenMinter", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } ] }, { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Local token account, which this program uses to validate the `mint` used to burn.", - "" + "name": "cctp", + "accounts": [ + { + "name": "burnSource", + "accounts": [ + { + "name": "mintRecipient", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "mint", + "isMut": true, + "isSigner": false, + "docs": [ + "Circle-supported mint.", + "", + "Token Messenger Minter program's local token account." + ] + }, + { + "name": "tokenMessengerMinterSenderAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterConfig", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenMessenger", + "isMut": false, + "isSigner": false + }, + { + "name": "remoteTokenMessenger", + "isMut": false, + "isSigner": false, + "docs": [ + "Messenger Minter program)." + ] + }, + { + "name": "tokenMinter", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + ] + }, + { + "name": "localToken", + "isMut": true, + "isSigner": false, + "docs": [ + "Local token account, which this program uses to validate the `mint` used to burn.", + "" + ] + }, + { + "name": "tokenMessengerMinterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessengerMinterProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterProgram", + "isMut": false, + "isSigner": false + } ] }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -472,14 +526,27 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] @@ -498,46 +565,18 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." - ] - }, - { - "name": "preparedOrderResponse", + "name": "coreMessage", "isMut": true, "isSigner": false }, { - "name": "auction", - "isMut": true, - "isSigner": false, - "docs": [ - "There should be no account data here because an auction was never created." - ] - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -552,46 +591,98 @@ export type MatchingEngine = { ] }, { - "name": "fromRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." + "name": "prepared", + "accounts": [ + { + "name": "by", + "isMut": true, + "isSigner": false + }, + { + "name": "orderResponse", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + } ] }, { - "name": "toRouterEndpoint", - "isMut": false, + "name": "fastOrderPath", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "from", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "to", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + } + ] + }, + { + "name": "auction", + "isMut": true, "isSigner": false, "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." + "There should be no account data here because an auction was never created." ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "coreFeeCollector", + "name": "localCustodyToken", "isMut": true, "isSigner": false }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -603,194 +694,115 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] }, { - "name": "settleAuctionActiveCctp", + "name": "initialize", + "docs": [ + "This instruction is be used to generate your program's config.", + "And for convenience, we will store Wormhole-related PDAs in the", + "config so we can verify these accounts with a simple == constraint." + ], "accounts": [ { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "payerSequence", + "name": "owner", "isMut": true, - "isSigner": false - }, - { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false, + "isSigner": true, "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "Owner of the program, who presumably deployed this program." ] }, { - "name": "preparedOrderResponse", - "isMut": true, - "isSigner": false - }, - { - "name": "auction", + "name": "custodian", "isMut": true, "isSigner": false, "docs": [ - "There should be no account data here because an auction was never created." + "Custodian account, which saves program data useful for other", + "instructions." ] }, { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "executorToken", + "name": "auctionConfig", "isMut": true, "isSigner": false }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "toRouterEndpoint", + "name": "ownerAssistant", "isMut": false, "isSigner": false, "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Circle-supported mint.", - "", - "Token Messenger Minter program's local token account." + "TODO: do we prevent the owner from being the owner assistant?" ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, + "name": "feeRecipient", + "isMut": false, "isSigner": false }, { - "name": "tokenMessengerMinterSenderAuthority", + "name": "feeRecipientToken", "isMut": false, "isSigner": false }, { - "name": "messageTransmitterConfig", + "name": "cctpMintRecipient", "isMut": true, "isSigner": false }, { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." - ] - }, - { - "name": "tokenMinter", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } ] }, { - "name": "localToken", + "name": "programData", "isMut": true, "isSigner": false, "docs": [ - "Local token account, which this program uses to validate the `mint` used to burn.", - "" + "We use the program data to make sure this owner is the upgrade authority (the true owner,", + "who deployed this program)." ] }, { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", + "name": "upgradeManagerAuthority", "isMut": false, "isSigner": false }, { - "name": "messageTransmitterProgram", + "name": "upgradeManagerProgram", "isMut": false, "isSigner": false }, { - "name": "tokenProgram", + "name": "bpfLoaderUpgradeableProgram", "isMut": false, "isSigner": false }, @@ -800,20 +812,27 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "clock", + "name": "tokenProgram", "isMut": false, "isSigner": false }, { - "name": "rent", + "name": "associatedTokenProgram", "isMut": false, "isSigner": false } ], - "args": [] + "args": [ + { + "name": "auctionParams", + "type": { + "defined": "AuctionParameters" + } + } + ] }, { - "name": "settleAuctionActiveLocal", + "name": "addCctpRouterEndpoint", "accounts": [ { "name": "payer", @@ -821,274 +840,77 @@ export type MatchingEngine = { "isSigner": true }, { - "name": "payerSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "name": "admin", + "accounts": [ + { + "name": "ownerOrAssistant", + "isMut": false, + "isSigner": true + }, + { + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] + } ] }, { - "name": "auctionConfig", - "isMut": false, + "name": "routerEndpoint", + "isMut": true, "isSigner": false }, { - "name": "fastVaa", + "name": "localRouterEndpoint", "isMut": false, "isSigner": false, "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "Local router endpoint PDA.", + "", + "NOTE: This account may not exist yet. But we need to pass it since it will be the owner of", + "the local custody token account.", + "" ] }, { - "name": "preparedOrderResponse", + "name": "localCustodyToken", "isMut": true, "isSigner": false }, { - "name": "auction", - "isMut": true, - "isSigner": false, - "docs": [ - "There should be no account data here because an auction was never created." + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } ] }, { - "name": "toRouterEndpoint", + "name": "remoteTokenMessenger", "isMut": false, "isSigner": false, "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." + "Messenger Minter program)." ] }, { - "name": "bestOfferToken", - "isMut": true, + "name": "tokenProgram", + "isMut": false, "isSigner": false }, { - "name": "executorToken", - "isMut": true, + "name": "systemProgram", + "isMut": false, "isSigner": false - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false - } - ], - "args": [] - }, - { - "name": "initialize", - "docs": [ - "This instruction is be used to generate your program's config.", - "And for convenience, we will store Wormhole-related PDAs in the", - "config so we can verify these accounts with a simple == constraint." - ], - "accounts": [ - { - "name": "owner", - "isMut": true, - "isSigner": true, - "docs": [ - "Owner of the program, who presumably deployed this program." - ] - }, - { - "name": "custodian", - "isMut": true, - "isSigner": false, - "docs": [ - "Custodian account, which saves program data useful for other", - "instructions." - ] - }, - { - "name": "auctionConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "ownerAssistant", - "isMut": false, - "isSigner": false, - "docs": [ - "TODO: do we prevent the owner from being the owner assistant?" - ] - }, - { - "name": "feeRecipient", - "isMut": false, - "isSigner": false - }, - { - "name": "feeRecipientToken", - "isMut": false, - "isSigner": false - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false - }, - { - "name": "mint", - "isMut": false, - "isSigner": false - }, - { - "name": "programData", - "isMut": true, - "isSigner": false, - "docs": [ - "We use the program data to make sure this owner is the upgrade authority (the true owner,", - "who deployed this program)." - ] - }, - { - "name": "upgradeManagerAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "upgradeManagerProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "bpfLoaderUpgradeableProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "associatedTokenProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "auctionParams", - "type": { - "defined": "AuctionParameters" - } - } - ] - }, - { - "name": "addCctpRouterEndpoint", - "accounts": [ - { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "admin", - "accounts": [ - { - "name": "ownerOrAssistant", - "isMut": false, - "isSigner": true - }, - { - "name": "custodian", - "isMut": false, - "isSigner": false - } - ] - }, - { - "name": "routerEndpoint", - "isMut": true, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." - ] - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ + } + ], + "args": [ { "name": "args", "type": { @@ -1115,8 +937,13 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -1169,8 +996,13 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -1178,7 +1010,7 @@ export type MatchingEngine = { "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": true, "isSigner": false } @@ -1200,8 +1032,13 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -1209,7 +1046,7 @@ export type MatchingEngine = { "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": true, "isSigner": false } @@ -1246,8 +1083,13 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -1255,7 +1097,7 @@ export type MatchingEngine = { "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": true, "isSigner": false } @@ -1300,8 +1142,13 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] }, @@ -1350,8 +1197,13 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] } @@ -1376,8 +1228,13 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] }, @@ -1424,8 +1281,13 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] }, @@ -1451,18 +1313,25 @@ export type MatchingEngine = { "name": "updateOwnerAssistant", "accounts": [ { - "name": "owner", - "isMut": false, - "isSigner": true, - "docs": [ - "Owner of the program set in the [`OwnerConfig`] account." + "name": "admin", + "accounts": [ + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] + } ] }, - { - "name": "custodian", - "isMut": true, - "isSigner": false - }, { "name": "newOwnerAssistant", "isMut": false, @@ -1479,20 +1348,30 @@ export type MatchingEngine = { "name": "updateFeeRecipient", "accounts": [ { - "name": "ownerOrAssistant", - "isMut": true, - "isSigner": true - }, - { - "name": "custodian", - "isMut": true, - "isSigner": false - }, - { - "name": "newFeeRecipientToken", - "isMut": false, - "isSigner": false - }, + "name": "admin", + "accounts": [ + { + "name": "ownerOrAssistant", + "isMut": false, + "isSigner": true + }, + { + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] + } + ] + }, + { + "name": "newFeeRecipientToken", + "isMut": false, + "isSigner": false + }, { "name": "newFeeRecipient", "isMut": false, @@ -1515,11 +1394,12 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -1528,9 +1408,39 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "fastVaa", - "isMut": false, - "isSigner": false + "name": "fastOrderPath", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "from", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "to", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + } + ] }, { "name": "auction", @@ -1542,24 +1452,27 @@ export type MatchingEngine = { ] }, { - "name": "fromRouterEndpoint", - "isMut": false, - "isSigner": false - }, - { - "name": "toRouterEndpoint", + "name": "offerToken", "isMut": false, - "isSigner": false + "isSigner": false, + "docs": [ + "the auction PDA." + ] }, { - "name": "offerToken", + "name": "auctionCustodyToken", "isMut": true, "isSigner": false }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] }, { "name": "systemProgram", @@ -1583,39 +1496,37 @@ export type MatchingEngine = { "name": "improveOffer", "accounts": [ { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source", - "authority for CCTP transfers.", - "" + "name": "activeAuction", + "accounts": [ + { + "name": "auction", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "bestOfferToken", + "isMut": true, + "isSigner": false + } ] }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "offerAuthority", - "isMut": false, - "isSigner": true - }, - { - "name": "auction", - "isMut": true, - "isSigner": false - }, { "name": "offerToken", - "isMut": true, - "isSigner": false - }, - { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false + "isMut": false, + "isSigner": false, + "docs": [ + "the auction PDA." + ] }, { "name": "tokenProgram", @@ -1644,157 +1555,193 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source", - "authority for CCTP transfers.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false - }, - { - "name": "auction", + "name": "coreMessage", "isMut": true, "isSigner": false }, { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false - }, - { - "name": "executorToken", + "name": "cctpMessage", "isMut": true, "isSigner": false }, { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "initialOfferToken", - "isMut": true, - "isSigner": false + "name": "executeOrder", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "activeAuction", + "accounts": [ + { + "name": "auction", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "bestOfferToken", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "toRouterEndpoint", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "executorToken", + "isMut": true, + "isSigner": false + }, + { + "name": "initialOfferToken", + "isMut": true, + "isSigner": false + } + ] }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Also the burn_source token account.", - "" + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } ] }, { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Circle-supported mint.", - "", - "Token Messenger Minter program's local token account." + "name": "cctp", + "accounts": [ + { + "name": "burnSource", + "accounts": [ + { + "name": "mintRecipient", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "mint", + "isMut": true, + "isSigner": false, + "docs": [ + "Circle-supported mint.", + "", + "Token Messenger Minter program's local token account." + ] + }, + { + "name": "tokenMessengerMinterSenderAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterConfig", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenMessenger", + "isMut": false, + "isSigner": false + }, + { + "name": "remoteTokenMessenger", + "isMut": false, + "isSigner": false, + "docs": [ + "Messenger Minter program)." + ] + }, + { + "name": "tokenMinter", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + ] + }, + { + "name": "localToken", + "isMut": true, + "isSigner": false, + "docs": [ + "Local token account, which this program uses to validate the `mint` used to burn.", + "" + ] + }, + { + "name": "tokenMessengerMinterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessengerMinterProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterProgram", + "isMut": false, + "isSigner": false + } ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterSenderAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." - ] - }, - { - "name": "tokenMinter", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." - ] - }, - { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Local token account, which this program uses to validate the `mint` used to burn.", - "" - ] - }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, + "name": "systemProgram", + "isMut": false, "isSigner": false }, { @@ -1803,14 +1750,27 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] @@ -1829,84 +1789,110 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source", - "authority for CCTP transfers.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false - }, - { - "name": "auction", - "isMut": true, - "isSigner": false - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false - }, - { - "name": "executorToken", - "isMut": true, - "isSigner": false - }, - { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "initialOfferToken", + "name": "coreMessage", "isMut": true, "isSigner": false }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Also the burn_source token account.", - "" + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false + "name": "executeOrder", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "activeAuction", + "accounts": [ + { + "name": "auction", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "bestOfferToken", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "toRouterEndpoint", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "executorToken", + "isMut": true, + "isSigner": false + }, + { + "name": "initialOfferToken", + "isMut": true, + "isSigner": false + } + ] }, { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "coreFeeCollector", + "name": "localCustodyToken", "isMut": true, "isSigner": false }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, { "name": "systemProgram", "isMut": false, @@ -1918,14 +1904,27 @@ export type MatchingEngine = { "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] @@ -1943,8 +1942,13 @@ export type MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -2294,6 +2298,10 @@ export type MatchingEngine = { "name": "configId", "type": "u32" }, + { + "name": "custodyTokenBump", + "type": "u8" + }, { "name": "vaaSequence", "docs": [ @@ -2358,6 +2366,10 @@ export type MatchingEngine = { "the [amount_in](Self::amount_in)." ], "type": "u64" + }, + { + "name": "endEarly", + "type": "bool" } ] } @@ -2415,6 +2427,12 @@ export type MatchingEngine = { { "name": "slot", "type": "u64" + }, + { + "name": "executePenalty", + "type": { + "option": "u64" + } } ] }, @@ -2426,7 +2444,7 @@ export type MatchingEngine = { "type": "u64" }, { - "name": "penalty", + "name": "totalPenalty", "type": { "option": "u64" } @@ -2506,11 +2524,6 @@ export type MatchingEngine = { "name": "OwnerOrAssistantOnly", "msg": "OwnerOrAssistantOnly" }, - { - "code": 6006, - "name": "InvalidCustodyToken", - "msg": "InvalidCustodyToken" - }, { "code": 6008, "name": "CpiDisallowed", @@ -2546,11 +2559,6 @@ export type MatchingEngine = { "name": "ImmutableProgram", "msg": "ImmutableProgram" }, - { - "code": 6259, - "name": "NotUsdc", - "msg": "NotUsdc" - }, { "code": 6514, "name": "InvalidNewOwner", @@ -2733,8 +2741,8 @@ export type MatchingEngine = { }, { "code": 6556, - "name": "BestOfferTokenMismatch", - "msg": "BestOfferTokenMismatch" + "name": "ExecutorTokenMismatch", + "msg": "ExecutorTokenMismatch" }, { "code": 6557, @@ -2803,15 +2811,22 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "vaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "name": "fastFillVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } ] }, { @@ -2833,23 +2848,16 @@ export const IDL: MatchingEngine = { "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": false, "isSigner": false } ] }, { - "name": "cctpMintRecipient", + "name": "localCustodyToken", "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "Mutable. Seeds must be \\[\"custody\"\\]." - ] + "isSigner": false }, { "name": "tokenProgram", @@ -2874,238 +2882,41 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } ] }, { "name": "finalizedVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "[verify_vaa_and_mint](wormhole_cctp_solana::cpi::verify_vaa_and_mint)." - ] - }, - { - "name": "preparedOrderResponse", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "messageTransmitterAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "usedNonces", - "isMut": true, - "isSigner": false, - "docs": [ - "first_nonce.to_string()\\] (CCTP Message Transmitter program)." - ] - }, - { - "name": "messageTransmitterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." - ] - }, - { - "name": "tokenMinter", - "isMut": false, - "isSigner": false - }, - { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Token Messenger Minter's Local Token account. This program uses the mint of this account to", - "validate the `mint_recipient` token account's mint.", - "" - ] - }, - { - "name": "tokenPair", - "isMut": false, - "isSigner": false, - "docs": [ - "Token Messenger Minter program)." - ] - }, - { - "name": "tokenMessengerMinterCustodyToken", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "args", - "type": { - "defined": "CctpMessageArgs" - } - } - ] - }, - { - "name": "settleAuctionComplete", - "accounts": [ - { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } ] }, - { - "name": "preparedBy", - "isMut": true, - "isSigner": false - }, { "name": "preparedOrderResponse", "isMut": true, "isSigner": false }, { - "name": "auction", - "isMut": false, - "isSigner": false - }, - { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Destination token account, which the redeemer may not own. But because the redeemer is a", - "signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent", - "to any account he chooses (this one).", - "" - ] - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "Mutable. Seeds must be \\[\"custody\"\\].", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [] - }, - { - "name": "settleAuctionNoneCctp", - "accounts": [ - { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "payerSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." - ] - }, - { - "name": "preparedOrderResponse", + "name": "preparedCustodyToken", "isMut": true, "isSigner": false }, @@ -3113,145 +2924,112 @@ export const IDL: MatchingEngine = { "name": "auction", "isMut": true, "isSigner": false, - "docs": [ - "There should be no account data here because an auction was never created." - ] - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "feeRecipientToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Destination token account, which the redeemer may not own. But because the redeemer is a", - "signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent", - "to any account he chooses (this one).", - "" - ] - }, - { - "name": "fromRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Circle-supported mint.", - "", - "Token Messenger Minter program's local token account." - ] - }, - { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterSenderAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." - ] + "isOptional": true }, { - "name": "tokenMinter", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } ] }, { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Local token account, which this program uses to validate the `mint` used to burn.", - "" + "name": "cctp", + "accounts": [ + { + "name": "mintRecipient", + "accounts": [ + { + "name": "mintRecipient", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "messageTransmitterAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterConfig", + "isMut": false, + "isSigner": false + }, + { + "name": "usedNonces", + "isMut": true, + "isSigner": false, + "docs": [ + "first_nonce.to_string()\\] (CCTP Message Transmitter program)." + ] + }, + { + "name": "messageTransmitterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessenger", + "isMut": false, + "isSigner": false + }, + { + "name": "remoteTokenMessenger", + "isMut": false, + "isSigner": false, + "docs": [ + "Messenger Minter program)." + ] + }, + { + "name": "tokenMinter", + "isMut": false, + "isSigner": false + }, + { + "name": "localToken", + "isMut": true, + "isSigner": false, + "docs": [ + "Token Messenger Minter's Local Token account. This program uses the mint of this account to", + "validate the `mint_recipient` token account's mint.", + "" + ] + }, + { + "name": "tokenPair", + "isMut": false, + "isSigner": false, + "docs": [ + "Token Messenger Minter program)." + ] + }, + { + "name": "tokenMessengerMinterCustodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenMessengerMinterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessengerMinterProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterProgram", + "isMut": false, + "isSigner": false + } ] }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -3261,78 +3039,37 @@ export const IDL: MatchingEngine = { "name": "systemProgram", "isMut": false, "isSigner": false - }, - { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false } ], - "args": [] + "args": [ + { + "name": "args", + "type": { + "defined": "CctpMessageArgs" + } + } + ] }, { - "name": "settleAuctionNoneLocal", + "name": "settleAuctionComplete", "accounts": [ { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "payerSequence", + "name": "executor", "isMut": true, - "isSigner": false - }, - { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "fastVaa", - "isMut": false, "isSigner": false, "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "we will always reward the owner of the executor token account with the lamports from the", + "prepared order response and its custody token account when we close these accounts. This", + "means we disregard the `prepared_by` field in the prepared order response." ] }, { - "name": "preparedOrderResponse", + "name": "executorToken", "isMut": true, "isSigner": false }, { - "name": "auction", - "isMut": true, - "isSigner": false, - "docs": [ - "There should be no account data here because an auction was never created." - ] - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." - ] - }, - { - "name": "feeRecipientToken", + "name": "bestOfferToken", "isMut": true, "isSigner": false, "docs": [ @@ -3343,71 +3080,30 @@ export const IDL: MatchingEngine = { ] }, { - "name": "fromRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." - ] - }, - { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", + "name": "preparedOrderResponse", "isMut": true, "isSigner": false }, { - "name": "coreEmitterSequence", + "name": "preparedCustodyToken", "isMut": true, "isSigner": false }, { - "name": "coreFeeCollector", + "name": "auction", "isMut": true, "isSigner": false }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false } ], "args": [] }, { - "name": "settleAuctionActiveCctp", + "name": "settleAuctionNoneCctp", "accounts": [ { "name": "payer", @@ -3420,166 +3116,204 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." - ] - }, - { - "name": "preparedOrderResponse", + "name": "coreMessage", "isMut": true, "isSigner": false }, { - "name": "auction", - "isMut": true, - "isSigner": false, - "docs": [ - "There should be no account data here because an auction was never created." - ] - }, - { - "name": "bestOfferToken", + "name": "cctpMessage", "isMut": true, "isSigner": false }, { - "name": "executorToken", - "isMut": true, - "isSigner": false + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "cctpMintRecipient", + "name": "feeRecipientToken", "isMut": true, "isSigner": false, "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." + "Destination token account, which the redeemer may not own. But because the redeemer is a", + "signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent", + "to any account he chooses (this one).", + "" ] }, { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." + "name": "prepared", + "accounts": [ + { + "name": "by", + "isMut": true, + "isSigner": false + }, + { + "name": "orderResponse", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + } ] }, { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Circle-supported mint.", - "", - "Token Messenger Minter program's local token account." + "name": "fastOrderPath", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "from", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "to", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + } ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterSenderAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", + "name": "auction", "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false - }, - { - "name": "remoteTokenMessenger", - "isMut": false, "isSigner": false, "docs": [ - "Messenger Minter program)." + "There should be no account data here because an auction was never created." ] }, { - "name": "tokenMinter", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } ] }, { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Local token account, which this program uses to validate the `mint` used to burn.", - "" + "name": "cctp", + "accounts": [ + { + "name": "burnSource", + "accounts": [ + { + "name": "mintRecipient", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "mint", + "isMut": true, + "isSigner": false, + "docs": [ + "Circle-supported mint.", + "", + "Token Messenger Minter program's local token account." + ] + }, + { + "name": "tokenMessengerMinterSenderAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterConfig", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenMessenger", + "isMut": false, + "isSigner": false + }, + { + "name": "remoteTokenMessenger", + "isMut": false, + "isSigner": false, + "docs": [ + "Messenger Minter program)." + ] + }, + { + "name": "tokenMinter", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + ] + }, + { + "name": "localToken", + "isMut": true, + "isSigner": false, + "docs": [ + "Local token account, which this program uses to validate the `mint` used to burn.", + "" + ] + }, + { + "name": "tokenMessengerMinterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessengerMinterProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterProgram", + "isMut": false, + "isSigner": false + } ] }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -3591,20 +3325,33 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] }, { - "name": "settleAuctionActiveLocal", + "name": "settleAuctionNoneLocal", "accounts": [ { "name": "payer", @@ -3617,96 +3364,124 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, + "name": "coreMessage", + "isMut": true, "isSigner": false }, { - "name": "fastVaa", - "isMut": false, - "isSigner": false, - "docs": [ - "zero-copy using the [VaaAccount](core_bridge_program::sdk::VaaAccount) reader." + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { - "name": "preparedOrderResponse", - "isMut": true, - "isSigner": false - }, - { - "name": "auction", + "name": "feeRecipientToken", "isMut": true, "isSigner": false, "docs": [ - "There should be no account data here because an auction was never created." + "Destination token account, which the redeemer may not own. But because the redeemer is a", + "signer and is the one encoded in the Deposit Fill message, he may have the tokens be sent", + "to any account he chooses (this one).", + "" ] }, { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false, - "docs": [ - "Seeds must be \\[\"endpoint\", chain.to_be_bytes()\\]." + "name": "prepared", + "accounts": [ + { + "name": "by", + "isMut": true, + "isSigner": false + }, + { + "name": "orderResponse", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + } ] }, { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "executorToken", - "isMut": true, - "isSigner": false + "name": "fastOrderPath", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "from", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "to", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + } + ] }, { - "name": "cctpMintRecipient", + "name": "auction", "isMut": true, "isSigner": false, "docs": [ - "Mint recipient token account, which is encoded as the mint recipient in the CCTP message.", - "The CCTP Token Messenger Minter program will transfer the amount encoded in the CCTP message", - "from its custody account to this account.", - "", - "", - "NOTE: This account must be encoded as the mint recipient in the CCTP message." + "There should be no account data here because an auction was never created." ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false - }, - { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "coreFeeCollector", + "name": "localCustodyToken", "isMut": true, "isSigner": false }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, { "name": "tokenProgram", "isMut": false, @@ -3718,14 +3493,27 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] @@ -3784,9 +3572,14 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "mint", - "isMut": false, - "isSigner": false + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] }, { "name": "programData", @@ -3855,8 +3648,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -3865,6 +3663,33 @@ export const IDL: MatchingEngine = { "isMut": true, "isSigner": false }, + { + "name": "localRouterEndpoint", + "isMut": false, + "isSigner": false, + "docs": [ + "Local router endpoint PDA.", + "", + "NOTE: This account may not exist yet. But we need to pass it since it will be the owner of", + "the local custody token account.", + "" + ] + }, + { + "name": "localCustodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] + }, { "name": "remoteTokenMessenger", "isMut": false, @@ -3873,6 +3698,11 @@ export const IDL: MatchingEngine = { "Messenger Minter program)." ] }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, { "name": "systemProgram", "isMut": false, @@ -3906,8 +3736,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -3960,8 +3795,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -3969,7 +3809,7 @@ export const IDL: MatchingEngine = { "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": true, "isSigner": false } @@ -3991,8 +3831,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -4000,7 +3845,7 @@ export const IDL: MatchingEngine = { "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": true, "isSigner": false } @@ -4037,8 +3882,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -4046,7 +3896,7 @@ export const IDL: MatchingEngine = { "name": "routerEndpoint", "accounts": [ { - "name": "inner", + "name": "endpoint", "isMut": true, "isSigner": false } @@ -4091,8 +3941,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] }, @@ -4141,8 +3996,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] } @@ -4167,8 +4027,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] }, @@ -4215,8 +4080,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": true, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] } ] }, @@ -4242,18 +4112,25 @@ export const IDL: MatchingEngine = { "name": "updateOwnerAssistant", "accounts": [ { - "name": "owner", - "isMut": false, - "isSigner": true, - "docs": [ - "Owner of the program set in the [`OwnerConfig`] account." + "name": "admin", + "accounts": [ + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] + } ] }, - { - "name": "custodian", - "isMut": true, - "isSigner": false - }, { "name": "newOwnerAssistant", "isMut": false, @@ -4270,14 +4147,24 @@ export const IDL: MatchingEngine = { "name": "updateFeeRecipient", "accounts": [ { - "name": "ownerOrAssistant", - "isMut": true, - "isSigner": true - }, - { - "name": "custodian", - "isMut": true, - "isSigner": false + "name": "admin", + "accounts": [ + { + "name": "ownerOrAssistant", + "isMut": false, + "isSigner": true + }, + { + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": true, + "isSigner": false + } + ] + } + ] }, { "name": "newFeeRecipientToken", @@ -4306,11 +4193,12 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -4319,9 +4207,39 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "fastVaa", - "isMut": false, - "isSigner": false + "name": "fastOrderPath", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "from", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "to", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + } + ] }, { "name": "auction", @@ -4333,24 +4251,27 @@ export const IDL: MatchingEngine = { ] }, { - "name": "fromRouterEndpoint", - "isMut": false, - "isSigner": false - }, - { - "name": "toRouterEndpoint", + "name": "offerToken", "isMut": false, - "isSigner": false + "isSigner": false, + "docs": [ + "the auction PDA." + ] }, { - "name": "offerToken", + "name": "auctionCustodyToken", "isMut": true, "isSigner": false }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] }, { "name": "systemProgram", @@ -4374,39 +4295,37 @@ export const IDL: MatchingEngine = { "name": "improveOffer", "accounts": [ { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source", - "authority for CCTP transfers.", - "" + "name": "activeAuction", + "accounts": [ + { + "name": "auction", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "bestOfferToken", + "isMut": true, + "isSigner": false + } ] }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "offerAuthority", - "isMut": false, - "isSigner": true - }, - { - "name": "auction", - "isMut": true, - "isSigner": false - }, { "name": "offerToken", - "isMut": true, - "isSigner": false - }, - { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false + "isMut": false, + "isSigner": false, + "docs": [ + "the auction PDA." + ] }, { "name": "tokenProgram", @@ -4434,75 +4353,6 @@ export const IDL: MatchingEngine = { "isMut": true, "isSigner": false }, - { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source", - "authority for CCTP transfers.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false - }, - { - "name": "auction", - "isMut": true, - "isSigner": false - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false - }, - { - "name": "executorToken", - "isMut": true, - "isSigner": false - }, - { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "initialOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Also the burn_source token account.", - "" - ] - }, - { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Circle-supported mint.", - "", - "Token Messenger Minter program's local token account." - ] - }, - { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, { "name": "coreMessage", "isMut": true, @@ -4514,75 +4364,180 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false - }, - { - "name": "coreFeeCollector", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessengerMinterSenderAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenMessenger", - "isMut": false, - "isSigner": false + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "remoteTokenMessenger", - "isMut": false, - "isSigner": false, - "docs": [ - "Messenger Minter program)." + "name": "executeOrder", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "activeAuction", + "accounts": [ + { + "name": "auction", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "bestOfferToken", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "toRouterEndpoint", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "executorToken", + "isMut": true, + "isSigner": false + }, + { + "name": "initialOfferToken", + "isMut": true, + "isSigner": false + } ] }, { - "name": "tokenMinter", - "isMut": false, - "isSigner": false, - "docs": [ - "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } ] }, { - "name": "localToken", - "isMut": true, - "isSigner": false, - "docs": [ - "Local token account, which this program uses to validate the `mint` used to burn.", - "" + "name": "cctp", + "accounts": [ + { + "name": "burnSource", + "accounts": [ + { + "name": "mintRecipient", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "mint", + "isMut": true, + "isSigner": false, + "docs": [ + "Circle-supported mint.", + "", + "Token Messenger Minter program's local token account." + ] + }, + { + "name": "tokenMessengerMinterSenderAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterConfig", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenMessenger", + "isMut": false, + "isSigner": false + }, + { + "name": "remoteTokenMessenger", + "isMut": false, + "isSigner": false, + "docs": [ + "Messenger Minter program)." + ] + }, + { + "name": "tokenMinter", + "isMut": false, + "isSigner": false, + "docs": [ + "CHECK Seeds must be \\[\"token_minter\"\\] (CCTP Token Messenger Minter program)." + ] + }, + { + "name": "localToken", + "isMut": true, + "isSigner": false, + "docs": [ + "Local token account, which this program uses to validate the `mint` used to burn.", + "" + ] + }, + { + "name": "tokenMessengerMinterEventAuthority", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenMessengerMinterProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "messageTransmitterProgram", + "isMut": false, + "isSigner": false + } ] }, - { - "name": "tokenMessengerMinterEventAuthority", - "isMut": false, - "isSigner": false - }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenMessengerMinterProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "messageTransmitterProgram", - "isMut": false, - "isSigner": false - }, { "name": "systemProgram", "isMut": false, @@ -4594,14 +4549,27 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] @@ -4620,84 +4588,110 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority. This is also the burn-source", - "authority for CCTP transfers.", - "" - ] - }, - { - "name": "auctionConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "fastVaa", - "isMut": false, - "isSigner": false - }, - { - "name": "auction", - "isMut": true, - "isSigner": false - }, - { - "name": "toRouterEndpoint", - "isMut": false, - "isSigner": false - }, - { - "name": "executorToken", - "isMut": true, - "isSigner": false - }, - { - "name": "bestOfferToken", - "isMut": true, - "isSigner": false - }, - { - "name": "initialOfferToken", + "name": "coreMessage", "isMut": true, "isSigner": false }, { - "name": "cctpMintRecipient", - "isMut": true, - "isSigner": false, - "docs": [ - "Also the burn_source token account.", - "" + "name": "custodian", + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { - "name": "coreBridgeConfig", - "isMut": true, - "isSigner": false - }, - { - "name": "coreMessage", - "isMut": true, - "isSigner": false + "name": "executeOrder", + "accounts": [ + { + "name": "fastVaa", + "accounts": [ + { + "name": "vaa", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "activeAuction", + "accounts": [ + { + "name": "auction", + "isMut": true, + "isSigner": false + }, + { + "name": "custodyToken", + "isMut": true, + "isSigner": false + }, + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "bestOfferToken", + "isMut": true, + "isSigner": false + } + ] + }, + { + "name": "toRouterEndpoint", + "accounts": [ + { + "name": "endpoint", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "executorToken", + "isMut": true, + "isSigner": false + }, + { + "name": "initialOfferToken", + "isMut": true, + "isSigner": false + } + ] }, { - "name": "coreEmitterSequence", - "isMut": true, - "isSigner": false + "name": "wormhole", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "emitterSequence", + "isMut": true, + "isSigner": false + }, + { + "name": "feeCollector", + "isMut": true, + "isSigner": false + }, + { + "name": "coreBridgeProgram", + "isMut": false, + "isSigner": false + } + ] }, { - "name": "coreFeeCollector", + "name": "localCustodyToken", "isMut": true, "isSigner": false }, - { - "name": "coreBridgeProgram", - "isMut": false, - "isSigner": false - }, { "name": "systemProgram", "isMut": false, @@ -4709,14 +4703,27 @@ export const IDL: MatchingEngine = { "isSigner": false }, { - "name": "clock", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false + "name": "sysvars", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the clock sysvar based on its legacy implementation.", + "" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Wormhole Core Bridge needs the rent sysvar based on its legacy implementation.", + "" + ] + } + ] } ], "args": [] @@ -4734,8 +4741,13 @@ export const IDL: MatchingEngine = { }, { "name": "custodian", - "isMut": false, - "isSigner": false + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } + ] } ] }, @@ -5085,6 +5097,10 @@ export const IDL: MatchingEngine = { "name": "configId", "type": "u32" }, + { + "name": "custodyTokenBump", + "type": "u8" + }, { "name": "vaaSequence", "docs": [ @@ -5149,6 +5165,10 @@ export const IDL: MatchingEngine = { "the [amount_in](Self::amount_in)." ], "type": "u64" + }, + { + "name": "endEarly", + "type": "bool" } ] } @@ -5206,6 +5226,12 @@ export const IDL: MatchingEngine = { { "name": "slot", "type": "u64" + }, + { + "name": "executePenalty", + "type": { + "option": "u64" + } } ] }, @@ -5217,7 +5243,7 @@ export const IDL: MatchingEngine = { "type": "u64" }, { - "name": "penalty", + "name": "totalPenalty", "type": { "option": "u64" } @@ -5297,11 +5323,6 @@ export const IDL: MatchingEngine = { "name": "OwnerOrAssistantOnly", "msg": "OwnerOrAssistantOnly" }, - { - "code": 6006, - "name": "InvalidCustodyToken", - "msg": "InvalidCustodyToken" - }, { "code": 6008, "name": "CpiDisallowed", @@ -5337,11 +5358,6 @@ export const IDL: MatchingEngine = { "name": "ImmutableProgram", "msg": "ImmutableProgram" }, - { - "code": 6259, - "name": "NotUsdc", - "msg": "NotUsdc" - }, { "code": 6514, "name": "InvalidNewOwner", @@ -5524,8 +5540,8 @@ export const IDL: MatchingEngine = { }, { "code": 6556, - "name": "BestOfferTokenMismatch", - "msg": "BestOfferTokenMismatch" + "name": "ExecutorTokenMismatch", + "msg": "ExecutorTokenMismatch" }, { "code": 6557, diff --git a/solana/target/types/token_router.ts b/solana/target/types/token_router.ts index bee01f9de..53d2bb331 100644 --- a/solana/target/types/token_router.ts +++ b/solana/target/types/token_router.ts @@ -547,9 +547,12 @@ export type TokenRouter = { "isSigner": false }, { - "name": "matchingEngineCctpMintRecipient", + "name": "matchingEngineLocalCustodyToken", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "(Matching Engine program)." + ] }, { "name": "matchingEngineProgram", @@ -1917,9 +1920,12 @@ export const IDL: TokenRouter = { "isSigner": false }, { - "name": "matchingEngineCctpMintRecipient", + "name": "matchingEngineLocalCustodyToken", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "(Matching Engine program)." + ] }, { "name": "matchingEngineProgram", diff --git a/solana/ts/src/matchingEngine/index.ts b/solana/ts/src/matchingEngine/index.ts index 8f9703a2c..658cfff2b 100644 --- a/solana/ts/src/matchingEngine/index.ts +++ b/solana/ts/src/matchingEngine/index.ts @@ -107,7 +107,7 @@ export type RedeemFastFillAccounts = { custodian: PublicKey; redeemedFastFill: PublicKey; routerEndpoint: PublicKey; - cctpMintRecipient: PublicKey; + localCustodyToken: PublicKey; matchingEngineProgram: PublicKey; }; @@ -183,6 +183,7 @@ export class MatchingEngineProgram { async fetchAuction(input: VaaHash | { address: PublicKey }): Promise { const addr = "address" in input ? input.address : this.auctionAddress(input); + // @ts-ignore This is BS. This is correct. return this.program.account.auction.fetch(addr); } @@ -243,18 +244,58 @@ export class MatchingEngineProgram { return this.program.account.redeemedFastFill.fetch(addr); } - preparedOrderResponseAddress(preparedBy: PublicKey, fastVaaHash: VaaHash): PublicKey { - return PreparedOrderResponse.address(this.ID, preparedBy, fastVaaHash); + preparedOrderResponseAddress(fastVaaHash: VaaHash): PublicKey { + return PreparedOrderResponse.address(this.ID, fastVaaHash); } - fetchPreparedOrderResponse( - input: [PublicKey, VaaHash] | { address: PublicKey }, + async fetchPreparedOrderResponse( + input: VaaHash | { address: PublicKey }, ): Promise { - const addr = - "address" in input ? input.address : this.preparedOrderResponseAddress(...input); + const addr = "address" in input ? input.address : this.preparedOrderResponseAddress(input); return this.program.account.preparedOrderResponse.fetch(addr); } + preparedCustodyTokenAddress(preparedOrderResponse: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from("prepared-custody"), preparedOrderResponse.toBuffer()], + this.ID, + )[0]; + } + + auctionCustodyTokenAddress(auction: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from("auction-custody"), auction.toBuffer()], + this.ID, + )[0]; + } + + async fetchAuctionCustodyTokenBalance(auction: PublicKey): Promise { + return splToken + .getAccount(this.program.provider.connection, this.auctionCustodyTokenAddress(auction)) + .then((token) => token.amount) + .catch((_) => 0n); + } + + localCustodyTokenAddress(sourceChain: number): PublicKey { + const encodedSourceChain = Buffer.alloc(2); + encodedSourceChain.writeUInt16BE(sourceChain); + + return PublicKey.findProgramAddressSync( + [Buffer.from("local-custody"), encodedSourceChain], + this.ID, + )[0]; + } + + async fetchLocalCustodyTokenBalance(sourceChain: number): Promise { + return splToken + .getAccount( + this.program.provider.connection, + this.localCustodyTokenAddress(sourceChain), + ) + .then((token) => token.amount) + .catch((_) => 0n); + } + async approveCustodianIx( owner: PublicKey, amount: bigint | number, @@ -267,6 +308,23 @@ export class MatchingEngineProgram { ); } + async approveAuctionIx( + accounts: { + auction: PublicKey; + owner: PublicKey; + }, + amount: bigint | number, + ): Promise { + const { auction, owner } = accounts; + + return splToken.createApproveInstruction( + splToken.getAssociatedTokenAddressSync(USDC_MINT_ADDRESS, owner), + auction, + owner, + amount, + ); + } + async commonAccounts(): Promise { const custodian = this.custodianAddress(); const { coreBridgeConfig, coreEmitterSequence, coreFeeCollector, coreBridgeProgram } = @@ -306,6 +364,143 @@ export class MatchingEngineProgram { }; } + checkedCustodianComposite(addr?: PublicKey): { custodian: PublicKey } { + return { custodian: addr ?? this.custodianAddress() }; + } + + adminComposite( + ownerOrAssistant: PublicKey, + custodian?: PublicKey, + ): { ownerOrAssistant: PublicKey; custodian: { custodian: PublicKey } } { + return { ownerOrAssistant, custodian: this.checkedCustodianComposite(custodian) }; + } + + ownerOnlyComposite( + owner: PublicKey, + custodian?: PublicKey, + ): { owner: PublicKey; custodian: { custodian: PublicKey } } { + return { owner, custodian: this.checkedCustodianComposite(custodian) }; + } + + routerEndpointComposite(addr: PublicKey): { endpoint: PublicKey } { + return { + endpoint: addr, + }; + } + + liquidityLayerVaaComposite(vaa: PublicKey): { vaa: PublicKey } { + return { + vaa, + }; + } + + usdcComposite(mint?: PublicKey): { mint: PublicKey } { + return { + mint: mint ?? this.mint, + }; + } + + localTokenRouterComposite(tokenRouterProgram: PublicKey): { + tokenRouterProgram: PublicKey; + tokenRouterEmitter: PublicKey; + tokenRouterMintRecipient: PublicKey; + } { + const [tokenRouterEmitter] = PublicKey.findProgramAddressSync( + [Buffer.from("emitter")], + tokenRouterProgram, + ); + return { + tokenRouterProgram, + tokenRouterEmitter, + tokenRouterMintRecipient: splToken.getAssociatedTokenAddressSync( + this.mint, + tokenRouterEmitter, + true, + ), + }; + } + + async activeAuctionComposite( + accounts: { + auction: PublicKey; + config?: PublicKey; + bestOfferToken?: PublicKey; + }, + cached: { + info?: AuctionInfo | null; + } = {}, + ) { + const { auction, config: inputConfig, bestOfferToken: inputBestOfferToken } = accounts; + let { info: inputInfo } = cached; + inputInfo ??= null; + + const { config, bestOfferToken } = await (async () => { + if (inputConfig === undefined || inputBestOfferToken === undefined) { + const { info } = + inputInfo === null + ? await this.fetchAuction({ address: auction }) + : { info: inputInfo }; + if (info === null) { + throw new Error("Auction info not found"); + } + + const { configId, bestOfferToken } = info; + return { + config: inputConfig ?? this.auctionConfigAddress(configId), + bestOfferToken: inputBestOfferToken ?? bestOfferToken, + }; + } else { + return { + config: inputConfig, + bestOfferToken: inputBestOfferToken, + }; + } + })(); + + return { + custodyToken: this.auctionCustodyTokenAddress(auction), + auction, + config, + bestOfferToken, + }; + } + + closePreparedOrderResponseComposite(accounts: { by: PublicKey; orderResponse: PublicKey }): { + by: PublicKey; + orderResponse: PublicKey; + custodyToken: PublicKey; + } { + const { by, orderResponse } = accounts; + return { + by, + orderResponse, + custodyToken: this.preparedCustodyTokenAddress(orderResponse), + }; + } + + fastOrderPathComposite(accounts: { fastVaa: PublicKey; from: PublicKey; to: PublicKey }): { + fastVaa: { + vaa: PublicKey; + }; + from: { + endpoint: PublicKey; + }; + to: { endpoint: PublicKey }; + } { + const { fastVaa, from, to } = accounts; + return { + fastVaa: { vaa: fastVaa }, + from: { endpoint: from }, + to: { endpoint: to }, + }; + } + + cctpMintRecipientComposite(): { mintRecipient: PublicKey } { + return { + mintRecipient: this.cctpMintRecipientAddress(), + }; + } + async initializeIx( accounts: { owner: PublicKey; @@ -328,7 +523,7 @@ export class MatchingEngineProgram { feeRecipient, feeRecipientToken: splToken.getAssociatedTokenAddressSync(this.mint, feeRecipient), cctpMintRecipient: this.cctpMintRecipientAddress(), - mint: inputMint ?? this.mint, + usdc: this.usdcComposite(inputMint), programData: programDataAddress(this.ID), upgradeManagerAuthority: upgradeManager.upgradeAuthorityAddress(), upgradeManagerProgram: upgradeManager.ID, @@ -346,10 +541,7 @@ export class MatchingEngineProgram { return this.program.methods .submitOwnershipTransferRequest() .accounts({ - admin: { - owner, - custodian: inputCustodian ?? this.custodianAddress(), - }, + admin: this.ownerOnlyComposite(owner, inputCustodian), newOwner, }) .instruction(); @@ -377,10 +569,7 @@ export class MatchingEngineProgram { return this.program.methods .cancelOwnershipTransferRequest() .accounts({ - admin: { - owner, - custodian: inputCustodian ?? this.custodianAddress(), - }, + admin: this.ownerOnlyComposite(owner, inputCustodian), }) .instruction(); } @@ -394,8 +583,7 @@ export class MatchingEngineProgram { return this.program.methods .updateOwnerAssistant() .accounts({ - owner, - custodian: inputCustodian ?? this.custodianAddress(), + admin: this.ownerOnlyComposite(owner, inputCustodian), newOwnerAssistant, }) .instruction(); @@ -426,12 +614,12 @@ export class MatchingEngineProgram { .addCctpRouterEndpoint(args) .accounts({ payer: inputPayer ?? ownerOrAssistant, - admin: { - ownerOrAssistant, - custodian: inputCustodian ?? this.custodianAddress(), - }, + admin: this.adminComposite(ownerOrAssistant, inputCustodian), routerEndpoint: inputRouterEndpoint ?? this.routerEndpointAddress(chain), + localRouterEndpoint: this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), + localCustodyToken: this.localCustodyTokenAddress(chain), remoteTokenMessenger: inputRemoteTokenMessenger ?? derivedRemoteTokenMessenger, + usdc: this.usdcComposite(), }) .instruction(); } @@ -458,13 +646,10 @@ export class MatchingEngineProgram { return this.program.methods .updateCctpRouterEndpoint(args) .accounts({ - admin: { - owner, - custodian: inputCustodian ?? this.custodianAddress(), - }, - routerEndpoint: { - inner: inputRouterEndpoint ?? this.routerEndpointAddress(chain), - }, + admin: this.ownerOnlyComposite(owner, inputCustodian), + routerEndpoint: this.routerEndpointComposite( + inputRouterEndpoint ?? this.routerEndpointAddress(chain), + ), remoteTokenMessenger: inputRemoteTokenMessenger ?? derivedRemoteTokenMessenger, }) .instruction(); @@ -492,7 +677,7 @@ export class MatchingEngineProgram { payer: inputPayer ?? ownerOrAssistant, admin: { ownerOrAssistant, - custodian: inputCustodian ?? this.custodianAddress(), + custodian: this.checkedCustodianComposite(inputCustodian), }, proposal: inputProposal ?? (await this.proposalAddress()), epochSchedule: SYSVAR_EPOCH_SCHEDULE_PUBKEY, @@ -509,10 +694,7 @@ export class MatchingEngineProgram { return this.program.methods .closeProposal() .accounts({ - admin: { - owner, - custodian: this.custodianAddress(), - }, + admin: this.ownerOnlyComposite(owner), proposedBy, proposal, }) @@ -548,10 +730,7 @@ export class MatchingEngineProgram { .updateAuctionParameters() .accounts({ payer: inputPayer ?? owner, - admin: { - owner, - custodian: inputCustodian ?? this.custodianAddress(), - }, + admin: this.ownerOnlyComposite(owner, inputCustodian), proposal: inputProposal ?? (await this.proposalAddress()), auctionConfig, }) @@ -572,29 +751,15 @@ export class MatchingEngineProgram { custodian: inputCustodian, routerEndpoint: inputRouterEndpoint, } = accounts; - const [tokenRouterEmitter] = PublicKey.findProgramAddressSync( - [Buffer.from("emitter")], - tokenRouterProgram, - ); + return this.program.methods .addLocalRouterEndpoint() .accounts({ payer: inputPayer ?? ownerOrAssistant, - admin: { - ownerOrAssistant, - custodian: inputCustodian ?? this.custodianAddress(), - }, + admin: this.adminComposite(ownerOrAssistant, inputCustodian), routerEndpoint: inputRouterEndpoint ?? this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), - local: { - tokenRouterProgram, - tokenRouterEmitter, - tokenRouterMintRecipient: splToken.getAssociatedTokenAddressSync( - this.mint, - tokenRouterEmitter, - true, - ), - }, + local: this.localTokenRouterComposite(tokenRouterProgram), }) .instruction(); } @@ -611,31 +776,18 @@ export class MatchingEngineProgram { custodian: inputCustodian, routerEndpoint: inputRouterEndpoint, } = accounts; - const [tokenRouterEmitter] = PublicKey.findProgramAddressSync( - [Buffer.from("emitter")], - tokenRouterProgram, - ); + return this.program.methods .updateLocalRouterEndpoint() .accounts({ admin: { owner, - custodian: inputCustodian ?? this.custodianAddress(), - }, - routerEndpoint: { - inner: - inputRouterEndpoint ?? - this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), - }, - local: { - tokenRouterProgram, - tokenRouterEmitter, - tokenRouterMintRecipient: splToken.getAssociatedTokenAddressSync( - this.mint, - tokenRouterEmitter, - true, - ), + custodian: this.checkedCustodianComposite(inputCustodian), }, + routerEndpoint: this.routerEndpointComposite( + inputRouterEndpoint ?? this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), + ), + local: this.localTokenRouterComposite(tokenRouterProgram), }) .instruction(); } @@ -652,13 +804,10 @@ export class MatchingEngineProgram { return this.program.methods .disableRouterEndpoint() .accounts({ - admin: { - owner, - custodian: inputCustodian ?? this.custodianAddress(), - }, - routerEndpoint: { - inner: inputRouterEndpoint ?? this.routerEndpointAddress(chain), - }, + admin: this.ownerOnlyComposite(owner, inputCustodian), + routerEndpoint: this.routerEndpointComposite( + inputRouterEndpoint ?? this.routerEndpointAddress(chain), + ), }) .instruction(); } @@ -673,8 +822,10 @@ export class MatchingEngineProgram { return this.program.methods .updateFeeRecipient() .accounts({ - ownerOrAssistant, - custodian: inputCustodian ?? this.custodianAddress(), + admin: { + ownerOrAssistant, + custodian: this.checkedCustodianComposite(inputCustodian), + }, newFeeRecipient, newFeeRecipientToken: splToken.getAssociatedTokenAddressSync( this.mint, @@ -728,8 +879,6 @@ export class MatchingEngineProgram { totalDeposit: inputTotalDeposit, } = accounts; - const cctpMintRecipient = this.cctpMintRecipientAddress(); - const offerToken = await (async () => { if (inputOfferToken !== undefined) { return inputOfferToken; @@ -774,6 +923,8 @@ export class MatchingEngineProgram { } })(); + const auctionCustodyToken = this.auctionCustodyTokenAddress(auction); + const auctionConfig = await (async () => { if (inputAuctionConfig === undefined) { const { auctionConfigId } = await this.fetchCustodian(); @@ -783,19 +934,22 @@ export class MatchingEngineProgram { } })(); - const approveIx = await this.approveCustodianIx(payer, totalDeposit); + const approveIx = await this.approveAuctionIx({ auction, owner: payer }, totalDeposit); const placeInitialOfferIx = await this.program.methods .placeInitialOffer(new BN(feeOffer.toString())) .accounts({ payer, - custodian: this.custodianAddress(), + custodian: this.checkedCustodianComposite(), auctionConfig, auction, - fromRouterEndpoint, - toRouterEndpoint, + fastOrderPath: this.fastOrderPathComposite({ + fastVaa, + from: fromRouterEndpoint, + to: toRouterEndpoint, + }), offerToken, - cctpMintRecipient, - fastVaa, + auctionCustodyToken, + usdc: this.usdcComposite(), }) .instruction(); @@ -809,7 +963,7 @@ export class MatchingEngineProgram { auctionConfig?: PublicKey; bestOfferToken?: PublicKey; }, - feeOffer: bigint, + offerPrice: bigint, ): Promise<[approveIx: TransactionInstruction, improveOfferIx: TransactionInstruction]> { const { offerAuthority, @@ -818,37 +972,25 @@ export class MatchingEngineProgram { bestOfferToken: inputBestOfferToken, } = accounts; + // TODO: add cached args above const { info } = await this.fetchAuction({ address: auction }); if (info === null) { throw new Error("no auction info found"); } - const { auctionConfig, bestOfferToken } = await (async () => { - if (inputAuctionConfig === undefined || inputBestOfferToken === undefined) { - return { - auctionConfig: inputAuctionConfig ?? this.auctionConfigAddress(info.configId), - bestOfferToken: inputBestOfferToken ?? info.bestOfferToken, - }; - } else { - return { - auctionConfig: inputAuctionConfig, - bestOfferToken: inputBestOfferToken, - }; - } - })(); - const approveIx = await this.approveCustodianIx( - offerAuthority, + const approveIx = await this.approveAuctionIx( + { auction, owner: offerAuthority }, info.amountIn.add(info.securityDeposit).toNumber(), ); + const improveOfferIx = await this.program.methods - .improveOffer(new BN(feeOffer.toString())) + .improveOffer(new BN(offerPrice.toString())) .accounts({ - offerAuthority, - custodian: this.custodianAddress(), - auctionConfig, - auction, + activeAuction: await this.activeAuctionComposite( + { auction, config: inputAuctionConfig, bestOfferToken: inputBestOfferToken }, + { info }, + ), offerToken: splToken.getAssociatedTokenAddressSync(this.mint, offerAuthority), - bestOfferToken, }) .instruction(); @@ -860,11 +1002,11 @@ export class MatchingEngineProgram { payer: PublicKey; fastVaa: PublicKey; finalizedVaa: PublicKey; - mint?: PublicKey; }, args: CctpMessageArgs, + hasAuction: boolean = false, ): Promise { - const { payer, fastVaa, finalizedVaa, mint: inputMint } = accounts; + const { payer, fastVaa, finalizedVaa } = accounts; const fastVaaAcct = await VaaAccount.fetch(this.program.provider.connection, fastVaa); const { encodedCctpMessage } = args; @@ -883,322 +1025,96 @@ export class MatchingEngineProgram { messageTransmitterProgram, tokenMessengerMinterEventAuthority, } = this.messageTransmitterProgram().receiveTokenMessengerMinterMessageAccounts( - inputMint ?? this.mint, + this.mint, encodedCctpMessage, ); + const preparedOrderResponse = this.preparedOrderResponseAddress(fastVaaAcct.digest()); return this.program.methods .prepareOrderResponseCctp(args) .accounts({ payer, - custodian: this.custodianAddress(), - fastVaa, - finalizedVaa, - preparedOrderResponse: this.preparedOrderResponseAddress( - payer, - fastVaaAcct.digest(), - ), - cctpMintRecipient: this.cctpMintRecipientAddress(), - messageTransmitterAuthority, - messageTransmitterConfig, - usedNonces, - messageTransmitterEventAuthority, - tokenMessenger, - remoteTokenMessenger, - tokenMinter, - localToken, - tokenPair, - tokenMessengerMinterCustodyToken, - tokenMessengerMinterEventAuthority, - tokenMessengerMinterProgram, - messageTransmitterProgram, + custodian: this.checkedCustodianComposite(), + fastVaa: this.liquidityLayerVaaComposite(fastVaa), + finalizedVaa: this.liquidityLayerVaaComposite(finalizedVaa), + preparedOrderResponse, + preparedCustodyToken: this.preparedCustodyTokenAddress(preparedOrderResponse), + usdc: this.usdcComposite(), + auction: hasAuction ? this.auctionAddress(fastVaaAcct.digest()) : null, + cctp: { + mintRecipient: this.cctpMintRecipientComposite(), + messageTransmitterAuthority, + messageTransmitterConfig, + usedNonces, + messageTransmitterEventAuthority, + tokenMessenger, + remoteTokenMessenger, + tokenMinter, + localToken, + tokenPair, + tokenMessengerMinterCustodyToken, + tokenMessengerMinterEventAuthority, + tokenMessengerMinterProgram, + messageTransmitterProgram, + }, }) .instruction(); } async settleAuctionCompleteIx(accounts: { + executor: PublicKey; preparedOrderResponse: PublicKey; auction?: PublicKey; - preparedBy?: PublicKey; bestOfferToken?: PublicKey; }) { const { + executor, preparedOrderResponse, auction: inputAuction, - preparedBy: inputPreparedBy, bestOfferToken: inputBestOfferToken, } = accounts; - const { preparedBy, auction } = await (async () => { - if (inputPreparedBy !== undefined && inputAuction !== undefined) { + const { auction } = await (async () => { + if (inputAuction !== undefined) { return { - preparedBy: inputPreparedBy, auction: inputAuction, }; } else { - const { preparedBy, fastVaaHash } = await this.fetchPreparedOrderResponse({ + const { fastVaaHash } = await this.fetchPreparedOrderResponse({ address: preparedOrderResponse, }); return { - preparedBy: inputPreparedBy ?? preparedBy, auction: inputAuction ?? this.auctionAddress(fastVaaHash), }; } })(); - const bestOfferToken = await (async () => { - if (inputBestOfferToken !== undefined) { - return inputBestOfferToken; - } else { + const { bestOfferToken } = await (async () => { + if (inputBestOfferToken === undefined) { const { info } = await this.fetchAuction({ address: auction }); if (info === null) { throw new Error("no auction info found"); } - return info.bestOfferToken; - } - })(); - - return this.program.methods - .settleAuctionComplete() - .accounts({ - custodian: this.custodianAddress(), - preparedBy, - preparedOrderResponse, - auction, - bestOfferToken, - cctpMintRecipient: this.cctpMintRecipientAddress(), - }) - .instruction(); - } - - async settleAuctionActiveLocalIx(accounts: { - payer: PublicKey; - fastVaa: PublicKey; - executorToken: PublicKey; - preparedOrderResponse?: PublicKey; - auction?: PublicKey; - bestOfferToken?: PublicKey; - auctionConfig?: PublicKey; - }) { - const { - payer, - preparedOrderResponse: inputPreparedOrderResponse, - auction, - fastVaa, - executorToken, - bestOfferToken: inputBestOfferToken, - auctionConfig: inputAuctionConfig, - } = accounts; - const fastVaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); - - const mint = this.mint; - const auctionAddress = auction ?? this.auctionAddress(fastVaaAccount.digest()); - const preparedOrderResponse = - inputPreparedOrderResponse ?? - this.preparedOrderResponseAddress(payer, fastVaaAccount.digest()); - - const { auctionConfig, bestOfferToken } = await (async () => { - if (inputAuctionConfig === undefined || inputBestOfferToken === undefined) { - const { info } = await this.fetchAuction({ address: auctionAddress }); - if (info === null) { - throw new Error("no auction info found"); - } - const { configId, bestOfferToken } = info; return { - auctionConfig: inputAuctionConfig ?? this.auctionConfigAddress(configId), - bestOfferToken: inputBestOfferToken ?? bestOfferToken, + bestOfferToken: inputBestOfferToken ?? info.bestOfferToken, }; } else { return { - auctionConfig: inputAuctionConfig, bestOfferToken: inputBestOfferToken, }; } })(); - const { targetChain, toRouterEndpoint } = await (async () => { - const message = LiquidityLayerMessage.decode(fastVaaAccount.payload()); - if (message.fastMarketOrder == undefined) { - throw new Error("Message not FastMarketOrder"); - } - - const targetChain = message.fastMarketOrder.targetChain; - const toRouterEndpoint = this.routerEndpointAddress( - message.fastMarketOrder.targetChain, - ); - - return { targetChain, toRouterEndpoint }; - })(); - - const payerSequence = this.payerSequenceAddress(payer); - const payerSequenceValue = await this.fetchPayerSequenceValue({ - address: payerSequence, - }); - const { - custodian, - coreMessage, - coreBridgeConfig, - coreEmitterSequence, - coreFeeCollector, - coreBridgeProgram, - } = await this.publishMessageAccounts(payer, payerSequenceValue); - - const cctpMintRecipient = this.cctpMintRecipientAddress(); return this.program.methods - .settleAuctionActiveLocal() + .settleAuctionComplete() .accounts({ - payer, - payerSequence, - custodian, - auctionConfig, - fastVaa, + executor, + executorToken: splToken.getAssociatedTokenAddressSync(this.mint, executor), preparedOrderResponse, + preparedCustodyToken: this.preparedCustodyTokenAddress(preparedOrderResponse), auction, - cctpMintRecipient, - toRouterEndpoint, - coreBridgeConfig, - coreMessage, - coreEmitterSequence, - coreBridgeProgram, - tokenProgram: splToken.TOKEN_PROGRAM_ID, - systemProgram: SystemProgram.programId, - clock: SYSVAR_CLOCK_PUBKEY, - coreFeeCollector, - rent: SYSVAR_RENT_PUBKEY, - bestOfferToken, - executorToken, - }) - .instruction(); - } - - async settleAuctionActiveCctpIx( - accounts: { - payer: PublicKey; - executorToken: PublicKey; - preparedOrderResponse?: PublicKey; - auction?: PublicKey; - fastVaa: PublicKey; - fastVaaAccount: VaaAccount; - auctionConfig?: PublicKey; - bestOfferToken?: PublicKey; - encodedCctpMessage: Buffer; - }, - args: { targetChain: wormholeSdk.ChainId; remoteDomain?: number }, - ) { - const { - payer, - auction: inputAuction, - executorToken, - preparedOrderResponse: inputPreparedOrderResponse, - fastVaa, - fastVaaAccount, - auctionConfig: inputAuctionConfig, - bestOfferToken: inputBestOfferToken, - encodedCctpMessage, - } = accounts; - const auctionAddress = inputAuction ?? this.auctionAddress(fastVaaAccount.digest()); - - const mint = this.mint; - - const preparedOrderResponse = - inputPreparedOrderResponse ?? - this.preparedOrderResponseAddress(payer, fastVaaAccount.digest()); - - const { auctionConfig, bestOfferToken } = await (async () => { - if (inputAuctionConfig === undefined || inputBestOfferToken === undefined) { - const { info } = await this.fetchAuction({ address: auctionAddress }); - if (info === null) { - throw new Error("no auction info found"); - } - const { configId, bestOfferToken } = info; - return { - auctionConfig: inputAuctionConfig ?? this.auctionConfigAddress(configId), - bestOfferToken: inputBestOfferToken ?? bestOfferToken, - }; - } else { - return { - auctionConfig: inputAuctionConfig, - bestOfferToken: inputBestOfferToken, - }; - } - })(); - - const targetChain = await (async () => { - const message = LiquidityLayerMessage.decode(fastVaaAccount.payload()); - if (message.fastMarketOrder == undefined) { - throw new Error("Message not FastMarketOrder"); - } - - const targetChain = message.fastMarketOrder.targetChain; - - return targetChain; - })(); - - const { - protocol: { cctp }, - } = await this.fetchRouterEndpoint(targetChain); - if (cctp === undefined) { - throw new Error("CCTP domain is not undefined"); - } - const destinationCctpDomain = cctp.domain; - - const routerEndpoint = this.routerEndpointAddress(targetChain); - const { - custodian, - payerSequence, - tokenMessengerMinterSenderAuthority, - coreBridgeConfig, - coreMessage, - cctpMessage, - coreEmitterSequence, - coreFeeCollector, - coreBridgeProgram, - messageTransmitterConfig, - tokenMessengerMinterProgram, - tokenMinter, - localToken, - tokenMessenger, - tokenMessengerMinterEventAuthority, - messageTransmitterProgram, - } = await this.burnAndPublishAccounts( - { payer, mint }, - { targetChain, destinationCctpDomain }, - ); - - return this.program.methods - .settleAuctionActiveCctp() - .accounts({ - payer, - payerSequence, - custodian, - fastVaa, - preparedOrderResponse, - auction: auctionAddress, - executorToken, - cctpMintRecipient: this.cctpMintRecipientAddress(), - auctionConfig, bestOfferToken, - toRouterEndpoint: routerEndpoint, - mint, - messageTransmitterConfig, - coreBridgeConfig, - coreEmitterSequence, - coreFeeCollector, - coreMessage, - cctpMessage, - localToken, - tokenMinter, - tokenMessenger, - tokenMessengerMinterProgram, - tokenMessengerMinterSenderAuthority, - remoteTokenMessenger: - this.tokenMessengerMinterProgram().remoteTokenMessengerAddress( - destinationCctpDomain, - ), - tokenMessengerMinterEventAuthority, - messageTransmitterProgram, - coreBridgeProgram, }) .instruction(); } @@ -1217,11 +1133,9 @@ export class MatchingEngineProgram { } = accounts; const fastVaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); - const mint = this.mint; - const preparedOrderResponse = inputPreparedOrderResponse ?? - this.preparedOrderResponseAddress(payer, fastVaaAccount.digest()); + this.preparedOrderResponseAddress(fastVaaAccount.digest()); const { targetChain, toRouterEndpoint } = await (async () => { const message = LiquidityLayerMessage.decode(fastVaaAccount.payload()); @@ -1251,30 +1165,31 @@ export class MatchingEngineProgram { } = await this.publishMessageAccounts(payer, payerSequenceValue); const { feeRecipientToken } = await this.fetchCustodian(); - const cctpMintRecipient = this.cctpMintRecipientAddress(); return this.program.methods .settleAuctionNoneLocal() .accounts({ payer, payerSequence, - custodian, - fastVaa, - preparedOrderResponse, - auction, - cctpMintRecipient, - feeRecipientToken, - fromRouterEndpoint: this.routerEndpointAddress(fastVaaAccount.emitterInfo().chain), - toRouterEndpoint, - coreBridgeConfig, coreMessage, - coreEmitterSequence, - coreBridgeProgram, - tokenProgram: splToken.TOKEN_PROGRAM_ID, - systemProgram: SystemProgram.programId, - clock: SYSVAR_CLOCK_PUBKEY, - coreFeeCollector, - rent: SYSVAR_RENT_PUBKEY, + custodian: this.checkedCustodianComposite(custodian), + feeRecipientToken, + prepared: this.closePreparedOrderResponseComposite({ + by: payer, + orderResponse: preparedOrderResponse, + }), + fastOrderPath: this.fastOrderPathComposite({ + fastVaa, + from: this.routerEndpointAddress(fastVaaAccount.emitterInfo().chain), + to: toRouterEndpoint, + }), + auction, + wormhole: { + config: coreBridgeConfig, + emitterSequence: coreEmitterSequence, + feeCollector: coreFeeCollector, + coreBridgeProgram, + }, }) .instruction(); } @@ -1323,30 +1238,39 @@ export class MatchingEngineProgram { .accounts({ payer, payerSequence, - custodian, - fastVaa, - preparedOrderResponse, - auction: this.auctionAddress(fastVaaAccount.digest()), - cctpMintRecipient: this.cctpMintRecipientAddress(), - feeRecipientToken, - mint: this.mint, - fromRouterEndpoint: this.routerEndpointAddress(fastVaaAccount.emitterInfo().chain), - toRouterEndpoint, - coreBridgeConfig, coreMessage, cctpMessage, - coreEmitterSequence, - coreFeeCollector, - tokenMessengerMinterSenderAuthority, - messageTransmitterConfig, - tokenMessenger, - remoteTokenMessenger, - tokenMinter, - localToken, - tokenMessengerMinterEventAuthority, - coreBridgeProgram, - tokenMessengerMinterProgram, - messageTransmitterProgram, + custodian: this.checkedCustodianComposite(custodian), + feeRecipientToken, + prepared: this.closePreparedOrderResponseComposite({ + by: payer, + orderResponse: preparedOrderResponse, + }), + fastOrderPath: this.fastOrderPathComposite({ + fastVaa, + from: this.routerEndpointAddress(fastVaaAccount.emitterInfo().chain), + to: toRouterEndpoint, + }), + auction: this.auctionAddress(fastVaaAccount.digest()), + wormhole: { + config: coreBridgeConfig, + emitterSequence: coreEmitterSequence, + feeCollector: coreFeeCollector, + coreBridgeProgram, + }, + cctp: { + burnSource: this.cctpMintRecipientComposite(), + mint: this.mint, + tokenMessengerMinterSenderAuthority, + messageTransmitterConfig, + tokenMessenger, + remoteTokenMessenger, + tokenMinter, + localToken, + tokenMessengerMinterEventAuthority, + tokenMessengerMinterProgram, + messageTransmitterProgram, + }, }) .instruction(); } @@ -1379,27 +1303,20 @@ export class MatchingEngineProgram { const auction = inputAuction ?? this.auctionAddress(vaaAccount.digest()); - const { auctionConfig, initialOfferToken, bestOfferToken } = await (async () => { - if ( - inputAuctionConfig === undefined || - inputInitialOfferToken === undefined || - inputBestOfferToken === undefined - ) { + const { info, initialOfferToken } = await (async () => { + if (inputInitialOfferToken === undefined) { const { info } = await this.fetchAuction({ address: auction }); if (info === null) { throw new Error("no auction info found"); } - const { configId, initialOfferToken, bestOfferToken } = info; return { - auctionConfig: inputAuctionConfig ?? this.auctionConfigAddress(configId), - initialOfferToken: inputInitialOfferToken ?? initialOfferToken, - bestOfferToken: inputBestOfferToken ?? bestOfferToken, + info, + initialOfferToken: inputInitialOfferToken ?? info.initialOfferToken, }; } else { return { - auctionConfig: inputAuctionConfig, + info: null, initialOfferToken: inputInitialOfferToken, - bestOfferToken: inputBestOfferToken, }; } })(); @@ -1430,33 +1347,44 @@ export class MatchingEngineProgram { .executeFastOrderCctp() .accounts({ payer, - custodian, - auctionConfig, - fastVaa, - auction, - toRouterEndpoint, - executorToken: - inputExecutorToken ?? splToken.getAssociatedTokenAddressSync(mint, payer), - bestOfferToken, - initialOfferToken, - cctpMintRecipient: this.cctpMintRecipientAddress(), - mint, payerSequence, - coreBridgeConfig, coreMessage, cctpMessage, - coreEmitterSequence, - coreFeeCollector, - tokenMessengerMinterSenderAuthority, - messageTransmitterConfig, - tokenMessenger, - remoteTokenMessenger, - tokenMinter, - localToken, - tokenMessengerMinterEventAuthority, - coreBridgeProgram, - tokenMessengerMinterProgram, - messageTransmitterProgram, + executeOrder: { + fastVaa: this.liquidityLayerVaaComposite(fastVaa), + activeAuction: await this.activeAuctionComposite( + { + auction, + config: inputAuctionConfig, + bestOfferToken: inputBestOfferToken, + }, + { info }, + ), + toRouterEndpoint: this.routerEndpointComposite(toRouterEndpoint), + executorToken: + inputExecutorToken ?? splToken.getAssociatedTokenAddressSync(mint, payer), + initialOfferToken, + }, + custodian: this.checkedCustodianComposite(custodian), + wormhole: { + config: coreBridgeConfig, + emitterSequence: coreEmitterSequence, + feeCollector: coreFeeCollector, + coreBridgeProgram, + }, + cctp: { + burnSource: this.cctpMintRecipientComposite(), + mint, + tokenMessengerMinterSenderAuthority, + messageTransmitterConfig, + tokenMessenger, + remoteTokenMessenger, + tokenMinter, + localToken, + tokenMessengerMinterEventAuthority, + tokenMessengerMinterProgram, + messageTransmitterProgram, + }, }) .instruction(); } @@ -1485,30 +1413,13 @@ export class MatchingEngineProgram { const vaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); const auction = inputAuction ?? this.auctionAddress(vaaAccount.digest()); - const { auctionConfig, initialOfferToken, bestOfferToken } = await (async () => { - if ( - inputAuctionConfig === undefined || - inputInitialOfferToken === undefined || - inputBestOfferToken === undefined - ) { - const { info } = await this.fetchAuction({ address: auction }); - if (info === null) { - throw new Error("no auction info found"); - } - const { configId, initialOfferToken, bestOfferToken } = info; - return { - auctionConfig: inputAuctionConfig ?? this.auctionConfigAddress(configId), - initialOfferToken: inputInitialOfferToken ?? initialOfferToken, - bestOfferToken: inputBestOfferToken ?? bestOfferToken, - }; - } else { - return { - auctionConfig: inputAuctionConfig, - initialOfferToken: inputInitialOfferToken, - bestOfferToken: inputBestOfferToken, - }; - } - })(); + // TODO: Add caching so we do not have to do an rpc call here. + const { info } = await this.fetchAuction({ address: auction }); + if (info === null) { + throw new Error("no auction info found"); + } + const sourceChain = info.sourceChain; + const initialOfferToken = inputInitialOfferToken ?? info.initialOfferToken; const payerSequence = this.payerSequenceAddress(payer); const payerSequenceValue = await this.fetchPayerSequenceValue({ @@ -1527,40 +1438,64 @@ export class MatchingEngineProgram { .executeFastOrderLocal() .accounts({ payer, - custodian, - auctionConfig, - fastVaa, - auction, - toRouterEndpoint: - inputToRouterEndpoint ?? - this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), - executorToken: - inputExecutorToken ?? splToken.getAssociatedTokenAddressSync(this.mint, payer), - bestOfferToken, - initialOfferToken, - cctpMintRecipient: this.cctpMintRecipientAddress(), payerSequence, - coreBridgeConfig, + custodian: this.checkedCustodianComposite(custodian), coreMessage, - coreEmitterSequence, - coreFeeCollector, - coreBridgeProgram, + executeOrder: { + fastVaa: this.liquidityLayerVaaComposite(fastVaa), + activeAuction: await this.activeAuctionComposite( + { + auction, + config: inputAuctionConfig, + bestOfferToken: inputBestOfferToken, + }, + { info }, + ), + toRouterEndpoint: this.routerEndpointComposite( + inputToRouterEndpoint ?? + this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), + ), + executorToken: + inputExecutorToken ?? + splToken.getAssociatedTokenAddressSync(this.mint, payer), + initialOfferToken, + }, + wormhole: { + config: coreBridgeConfig, + emitterSequence: coreEmitterSequence, + feeCollector: coreFeeCollector, + coreBridgeProgram, + }, + localCustodyToken: this.localCustodyTokenAddress(sourceChain), }) .instruction(); } async redeemFastFillAccounts( vaa: PublicKey, + sourceChain?: number, ): Promise<{ vaaAccount: VaaAccount; accounts: RedeemFastFillAccounts }> { const vaaAccount = await VaaAccount.fetch(this.program.provider.connection, vaa); + const localCustodyToken = this.localCustodyTokenAddress( + sourceChain ?? + (() => { + const { fastFill } = LiquidityLayerMessage.decode(vaaAccount.payload()); + if (fastFill === undefined) { + throw new Error("Message not FastFill"); + } + + return fastFill.fill.sourceChain; + })(), + ); + return { vaaAccount, accounts: { custodian: this.custodianAddress(), redeemedFastFill: this.redeemedFastFillAddress(vaaAccount.digest()), routerEndpoint: this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), - cctpMintRecipient: this.cctpMintRecipientAddress(), + localCustodyToken, matchingEngineProgram: this.ID, }, }; diff --git a/solana/ts/src/matchingEngine/state/Auction.ts b/solana/ts/src/matchingEngine/state/Auction.ts index 368189070..19ebfc00f 100644 --- a/solana/ts/src/matchingEngine/state/Auction.ts +++ b/solana/ts/src/matchingEngine/state/Auction.ts @@ -1,19 +1,19 @@ -import { PublicKey } from "@solana/web3.js"; import { BN } from "@coral-xyz/anchor"; -import { AuctionParameters } from "./AuctionConfig"; +import { PublicKey } from "@solana/web3.js"; export type AuctionStatus = { notStarted?: {}; active?: {}; - completed?: { slot: BN }; + completed?: { slot: BN; executePenalty: BN | null }; settled?: { baseFee: BN; - penalty: BN | null; + totalPenalty: BN | null; }; }; export type AuctionInfo = { configId: number; + custodyTokenBump: number; vaaSequence: BN; sourceChain: number; bestOfferToken: PublicKey; @@ -23,12 +23,13 @@ export type AuctionInfo = { securityDeposit: BN; offerPrice: BN; amountOut: BN | null; + endEarly: boolean; }; export class Auction { bump: number; vaaHash: number[]; - status: Object; + status: AuctionStatus; info: AuctionInfo | null; constructor(bump: number, vaaHash: number[], status: AuctionStatus, info: AuctionInfo | null) { diff --git a/solana/ts/src/matchingEngine/state/PreparedOrderResponse.ts b/solana/ts/src/matchingEngine/state/PreparedOrderResponse.ts index 59ee67623..03658c933 100644 --- a/solana/ts/src/matchingEngine/state/PreparedOrderResponse.ts +++ b/solana/ts/src/matchingEngine/state/PreparedOrderResponse.ts @@ -14,7 +14,7 @@ export class PreparedOrderResponse { preparedBy: PublicKey, fastVaaHash: Array, sourceChain: number, - baseFee: BN + baseFee: BN, ) { this.bump = bump; this.preparedBy = preparedBy; @@ -23,10 +23,10 @@ export class PreparedOrderResponse { this.baseFee = baseFee; } - static address(programId: PublicKey, preparedBy: PublicKey, fastVaaHash: VaaHash) { + static address(programId: PublicKey, fastVaaHash: VaaHash) { return PublicKey.findProgramAddressSync( - [Buffer.from("order-response"), preparedBy.toBuffer(), Buffer.from(fastVaaHash)], - programId + [Buffer.from("order-response"), Buffer.from(fastVaaHash)], + programId, )[0]; } } diff --git a/solana/ts/src/tokenRouter/index.ts b/solana/ts/src/tokenRouter/index.ts index caf083ae3..54c2f291b 100644 --- a/solana/ts/src/tokenRouter/index.ts +++ b/solana/ts/src/tokenRouter/index.ts @@ -95,7 +95,7 @@ export type RedeemFastFillAccounts = { matchingEngineCustodian: PublicKey; matchingEngineRedeemedFastFill: PublicKey; matchingEngineRouterEndpoint: PublicKey; - matchingEngineCctpMintRecipient: PublicKey; + matchingEngineLocalCustodyToken: PublicKey; matchingEngineProgram: PublicKey; }; @@ -555,17 +555,20 @@ export class TokenRouterProgram { .instruction(); } - async redeemFastFillAccounts(vaa: PublicKey): Promise { + async redeemFastFillAccounts( + vaa: PublicKey, + sourceChain?: number, + ): Promise { const { vaaAccount, accounts: { custodian: matchingEngineCustodian, redeemedFastFill: matchingEngineRedeemedFastFill, routerEndpoint: matchingEngineRouterEndpoint, - cctpMintRecipient: matchingEngineCctpMintRecipient, + localCustodyToken: matchingEngineLocalCustodyToken, matchingEngineProgram, }, - } = await this.matchingEngineProgram().redeemFastFillAccounts(vaa); + } = await this.matchingEngineProgram().redeemFastFillAccounts(vaa, sourceChain); return { custodian: this.custodianAddress(), @@ -574,7 +577,7 @@ export class TokenRouterProgram { matchingEngineCustodian, matchingEngineRedeemedFastFill, matchingEngineRouterEndpoint, - matchingEngineCctpMintRecipient, + matchingEngineLocalCustodyToken, matchingEngineProgram, }; } @@ -590,7 +593,7 @@ export class TokenRouterProgram { matchingEngineCustodian, matchingEngineRedeemedFastFill, matchingEngineRouterEndpoint, - matchingEngineCctpMintRecipient, + matchingEngineLocalCustodyToken, matchingEngineProgram, } = await this.redeemFastFillAccounts(vaa); @@ -606,7 +609,7 @@ export class TokenRouterProgram { matchingEngineCustodian, matchingEngineRedeemedFastFill, matchingEngineRouterEndpoint, - matchingEngineCctpMintRecipient, + matchingEngineLocalCustodyToken, matchingEngineProgram, }) .instruction(); diff --git a/solana/ts/tests/01__matchingEngine.ts b/solana/ts/tests/01__matchingEngine.ts index 70acd1d5d..97f39217e 100644 --- a/solana/ts/tests/01__matchingEngine.ts +++ b/solana/ts/tests/01__matchingEngine.ts @@ -8,7 +8,6 @@ import { Connection, Keypair, PublicKey, - SYSVAR_RENT_PUBKEY, SystemProgram, TransactionInstruction, VersionedTransactionResponse, @@ -21,6 +20,7 @@ import { Fill, LiquidityLayerDeposit, LiquidityLayerMessage, + SlowOrderResponse, } from "../src"; import { Auction, @@ -34,6 +34,7 @@ import { } from "../src/matchingEngine"; import { VaaAccount } from "../src/wormhole"; import { + CHAIN_TO_DOMAIN, CircleAttester, ETHEREUM_USDC_ADDRESS, LOCALHOST, @@ -41,6 +42,7 @@ import { OWNER_ASSISTANT_KEYPAIR, OWNER_KEYPAIR, PAYER_KEYPAIR, + REGISTERED_TOKEN_ROUTERS, USDC_MINT_ADDRESS, bigintToU64BN, expectIxErr, @@ -75,15 +77,15 @@ describe("Matching Engine", function () { const liquidator = Keypair.generate(); // Foreign endpoints. - const ethChain = wormholeSdk.CHAINS.ethereum; - const ethRouter = Array.from(Buffer.alloc(32, "deadbeef", "hex")); - const ethDomain = 0; - const arbChain = wormholeSdk.CHAINS.arbitrum; - const arbRouter = Array.from(Buffer.alloc(32, "bead", "hex")); - const arbDomain = 3; - const solanaChain = wormholeSdk.CHAINS.solana; - const solanaRouter = Array.from(Buffer.alloc(32, "c0ffee", "hex")); - const solanaDomain = 5; + const ethChain = wormholeSdk.coalesceChainId("ethereum"); + const ethRouter = REGISTERED_TOKEN_ROUTERS["ethereum"]!; + const ethDomain = CHAIN_TO_DOMAIN["ethereum"]!; + + const arbChain = wormholeSdk.coalesceChainId("arbitrum"); + const arbRouter = REGISTERED_TOKEN_ROUTERS["arbitrum"]!; + const arbDomain = CHAIN_TO_DOMAIN["arbitrum"]!; + + const solanaChain = wormholeSdk.coalesceChainId("solana"); // Matching Engine program. const engine = new MatchingEngineProgram(connection, localnet(), USDC_MINT_ADDRESS); @@ -99,6 +101,13 @@ describe("Matching Engine", function () { minOfferDeltaBps: 20000, // 2% }; + let testCctpNonce = 2n ** 64n - 1n; + + // Hack to prevent math overflow error when invoking CCTP programs. + testCctpNonce -= 10n * 6400n; + + let wormholeSequence = 1000n; + describe("Admin", function () { describe("Initialize", function () { const localVariables = new Map(); @@ -115,17 +124,7 @@ describe("Matching Engine", function () { }, auctionParams, ); - const unknownAta = splToken.getAssociatedTokenAddressSync( - mint, - engine.custodianAddress(), - true, - ); - await expectIxErr( - connection, - [ix], - [payer], - `Instruction references an unknown account ${unknownAta.toString()}`, - ); + await expectIxErr(connection, [ix], [payer], "mint. Error Code: ConstraintAddress"); }); it("Cannot Initialize with Default Owner Assistant", async function () { @@ -1115,13 +1114,6 @@ describe("Matching Engine", function () { }); describe("Business Logic", function () { - let testCctpNonce = 2n ** 64n - 1n; - - // Hack to prevent math overflow error when invoking CCTP programs. - testCctpNonce -= 10n * 6400n; - - let wormholeSequence = 1000n; - const baseFastOrder: FastMarketOrder = { amountIn: 50000000000n, minAmountOut: 0n, @@ -1138,33 +1130,12 @@ describe("Matching Engine", function () { describe("Place Initial Offer", function () { for (const offerPrice of [0n, baseFastOrder.maxFee / 2n, baseFastOrder.maxFee]) { it(`Place Initial Offer (Price == ${offerPrice})`, async function () { - // Fetch the balances before. - const offerBalanceBefore = await getUsdcAtaBalance( - connection, - offerAuthorityOne.publicKey, - ); - const { amount: custodyBalanceBefore } = await engine.fetchCctpMintRecipient(); - - const { fastVaa, txDetails } = await placeInitialOfferForTest( + const { fastVaa, txDetails, auction } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, offerPrice, ); - - // Validate balance changes. - const offerBalanceAfter = await getUsdcAtaBalance( - connection, - offerAuthorityOne.publicKey, - ); - const { amount: custodyBalanceAfter } = await engine.fetchCctpMintRecipient(); - const balanceChange = baseFastOrder.amountIn + baseFastOrder.maxFee; - - expect(offerBalanceAfter).equals(offerBalanceBefore - balanceChange); - expect(custodyBalanceAfter).equals(custodyBalanceBefore + balanceChange); - - await checkAfterEffects({ txDetails, fastVaa, offerPrice }); }); } @@ -1172,31 +1143,12 @@ describe("Matching Engine", function () { const fastOrder = { ...baseFastOrder } as FastMarketOrder; fastOrder.maxFee = fastOrder.amountIn - 1n; - // Fetch the balances before. - const offerBalanceBefore = await getUsdcAtaBalance( - connection, - offerAuthorityOne.publicKey, - ); - const { amount: custodyBalanceBefore } = await engine.fetchCctpMintRecipient(); - - const { fastVaa, txDetails } = await placeInitialOfferForTest( + await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, fastOrder, ethRouter, + fastOrder.maxFee, ); - - // Validate balance changes. - const offerBalanceAfter = await getUsdcAtaBalance( - connection, - offerAuthorityOne.publicKey, - ); - const { amount: custodyBalanceAfter } = await engine.fetchCctpMintRecipient(); - const balanceChange = fastOrder.amountIn + fastOrder.maxFee; - expect(offerBalanceAfter).equals(offerBalanceBefore - balanceChange); - expect(custodyBalanceAfter).equals(custodyBalanceBefore + balanceChange); - - await checkAfterEffects({ txDetails, fastVaa, offerPrice: fastOrder.maxFee }); }); it("Place Initial Offer (With Deadline)", async function () { @@ -1210,32 +1162,7 @@ describe("Matching Engine", function () { } fastOrder.deadline = currTime + 10; - // Fetch the balances before. - const offerBalanceBefore = await getUsdcAtaBalance( - connection, - offerAuthorityOne.publicKey, - ); - const { amount: custodyBalanceBefore } = await engine.fetchCctpMintRecipient(); - - const { fastVaa, txDetails } = await placeInitialOfferForTest( - offerAuthorityOne, - wormholeSequence++, - fastOrder, - ethRouter, - offerPrice, - ); - - // Validate balance changes. - const offerBalanceAfter = await getUsdcAtaBalance( - connection, - offerAuthorityOne.publicKey, - ); - const { amount: custodyBalanceAfter } = await engine.fetchCctpMintRecipient(); - const balanceChange = fastOrder.amountIn + fastOrder.maxFee; - expect(offerBalanceAfter).equals(offerBalanceBefore - balanceChange); - expect(custodyBalanceAfter).equals(custodyBalanceBefore + balanceChange); - - await checkAfterEffects({ txDetails, fastVaa, offerPrice }); + await placeInitialOfferForTest(offerAuthorityOne, fastOrder, ethRouter, offerPrice); }); it("Cannot Place Initial Offer (Invalid VAA)", async function () { @@ -1486,53 +1413,6 @@ describe("Matching Engine", function () { ); }); - async function checkAfterEffects(args: { - txDetails: VersionedTransactionResponse; - fastVaa: PublicKey; - offerPrice: bigint; - }) { - const { txDetails, fastVaa, offerPrice } = args; - - // Confirm the auction data. - const vaaAccount = await VaaAccount.fetch(connection, fastVaa); - const { fastMarketOrder } = LiquidityLayerMessage.decode(vaaAccount.payload()); - - const vaaHash = vaaAccount.digest(); - const auctionData = await engine.fetchAuction(vaaHash); - const { bump } = auctionData; - - const { auctionConfigId } = await engine.fetchCustodian(); - - const offerToken = splToken.getAssociatedTokenAddressSync( - USDC_MINT_ADDRESS, - offerAuthorityOne.publicKey, - ); - - expect(fastMarketOrder).is.not.undefined; - const { amountIn, maxFee } = fastMarketOrder!; - - const expectedAmountIn = bigintToU64BN(amountIn); - expect(auctionData).to.eql( - new Auction( - bump, - Array.from(vaaHash), - { active: {} }, - { - configId: auctionConfigId, - vaaSequence: bigintToU64BN(vaaAccount.emitterInfo().sequence), - sourceChain: ethChain, - bestOfferToken: offerToken, - initialOfferToken: offerToken, - startSlot: numberToU64BN(txDetails.slot), - amountIn: expectedAmountIn, - securityDeposit: bigintToU64BN(maxFee), - offerPrice: bigintToU64BN(offerPrice), - amountOut: expectedAmountIn, - }, - ), - ); - } - before("Register To Router Endpoints", async function () { const ix = await engine.addCctpRouterEndpointIx( { @@ -1581,7 +1461,6 @@ describe("Matching Engine", function () { it(`Improve Offer (Price == ${newOffer})`, async function () { const { auction, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -1623,7 +1502,6 @@ describe("Matching Engine", function () { it("Improve Offer By Min Offer Delta", async function () { const { auction, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -1668,7 +1546,6 @@ describe("Matching Engine", function () { it("Improve Offer With Same Best Offer Token Account", async function () { const { auction, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -1708,7 +1585,6 @@ describe("Matching Engine", function () { it("Cannot Improve Offer (Auction Expired)", async function () { const { auction, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -1742,7 +1618,6 @@ describe("Matching Engine", function () { it("Cannot Improve Offer (Invalid Best Offer Token Account)", async function () { const { auction, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -1762,39 +1637,13 @@ describe("Matching Engine", function () { connection, [approveIx, ix], [offerAuthorityOne], - "Error Code: BestOfferTokenMismatch", - ); - }); - - it("Cannot Improve Offer (Offer Price Not Improved)", async function () { - const { auction, auctionDataBefore } = await placeInitialOfferForTest( - offerAuthorityOne, - wormholeSequence++, - baseFastOrder, - ethRouter, - ); - - const newOffer = BigInt(auctionDataBefore.info!.offerPrice.toString()); - const [approveIx, ix] = await engine.improveOfferIx( - { - auction, - offerAuthority: offerAuthorityTwo.publicKey, - }, - newOffer, - ); - - await expectIxErr( - connection, - [approveIx, ix], - [offerAuthorityTwo], - "Error Code: OfferPriceNotImproved", + "best_offer_token. Error Code: ConstraintAddress", ); }); it("Cannot Improve Offer (Carping Not Allowed)", async function () { const { auction, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -1842,6 +1691,7 @@ describe("Matching Engine", function () { const { bump, vaaHash, status, info } = auctionDataBefore; const { configId, + custodyTokenBump, vaaSequence, bestOfferToken: prevBestOfferToken, initialOfferToken, @@ -1858,6 +1708,7 @@ describe("Matching Engine", function () { expect(auctionDataAfter).to.eql( new Auction(bump, vaaHash, status, { configId, + custodyTokenBump, vaaSequence, sourceChain, bestOfferToken, @@ -1867,6 +1718,7 @@ describe("Matching Engine", function () { securityDeposit, offerPrice: bigintToU64BN(offerPrice), amountOut, + endEarly: false, }), ); @@ -1917,12 +1769,7 @@ describe("Matching Engine", function () { fastVaa, auction, auctionDataBefore: initialData, - } = await placeInitialOfferForTest( - offerAuthorityTwo, - wormholeSequence++, - baseFastOrder, - ethRouter, - ); + } = await placeInitialOfferForTest(offerAuthorityTwo, baseFastOrder, ethRouter); const improveBy = Number( await engine.computeMinOfferDelta( @@ -1945,7 +1792,7 @@ describe("Matching Engine", function () { connection, initialOfferToken, ); - const { amount: custodyTokenBefore } = await engine.fetchCctpMintRecipient(); + const custodyTokenBefore = await engine.fetchAuctionCustodyTokenBalance(auction); const { duration, gracePeriod } = await engine.fetchAuctionParameters(); await waitUntilSlot( @@ -1958,7 +1805,15 @@ describe("Matching Engine", function () { fastVaa, }); - const txDetails = await expectIxOkDetails(connection, [ix], [offerAuthorityOne]); + const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ + units: 300_000, + }); + + const txDetails = await expectIxOkDetails( + connection, + [computeIx, ix], + [offerAuthorityOne], + ); await checkAfterEffects( txDetails!, @@ -1978,7 +1833,7 @@ describe("Matching Engine", function () { localVariables.set("auction", auction); }); - it("Cannot Improve Offer", async function () { + it("Cannot Improve Offer After Execute Order", async function () { const auction = localVariables.get("auction") as PublicKey; expect(localVariables.delete("auction")).is.true; @@ -1994,7 +1849,7 @@ describe("Matching Engine", function () { connection, [approveIx, ix], [offerAuthorityOne], - "Error Code: AuctionNotActive", + "custody_token. Error Code: AccountNotInitialized", ); }); @@ -2005,12 +1860,7 @@ describe("Matching Engine", function () { fastVaa, auction, auctionDataBefore: initialData, - } = await placeInitialOfferForTest( - offerAuthorityTwo, - wormholeSequence++, - baseFastOrder, - ethRouter, - ); + } = await placeInitialOfferForTest(offerAuthorityTwo, baseFastOrder, ethRouter); const improveBy = Number( await engine.computeMinOfferDelta( @@ -2033,7 +1883,7 @@ describe("Matching Engine", function () { connection, initialOfferToken, ); - const { amount: custodyTokenBefore } = await engine.fetchCctpMintRecipient(); + const custodyTokenBefore = await engine.fetchAuctionCustodyTokenBalance(auction); const { duration, gracePeriod, penaltyPeriod } = await engine.fetchAuctionParameters(); @@ -2049,7 +1899,15 @@ describe("Matching Engine", function () { fastVaa, }); - const txDetails = await expectIxOkDetails(connection, [ix], [offerAuthorityOne]); + const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ + units: 300_000, + }); + + const txDetails = await expectIxOkDetails( + connection, + [computeIx, ix], + [offerAuthorityOne], + ); await checkAfterEffects( txDetails!, @@ -2074,12 +1932,7 @@ describe("Matching Engine", function () { fastVaa, auction, auctionDataBefore: initialData, - } = await placeInitialOfferForTest( - offerAuthorityTwo, - wormholeSequence++, - baseFastOrder, - ethRouter, - ); + } = await placeInitialOfferForTest(offerAuthorityTwo, baseFastOrder, ethRouter); const improveBy = Number( await engine.computeMinOfferDelta( @@ -2102,7 +1955,7 @@ describe("Matching Engine", function () { connection, initialOfferToken, ); - const { amount: custodyTokenBefore } = await engine.fetchCctpMintRecipient(); + const custodyTokenBefore = await engine.fetchAuctionCustodyTokenBalance(auction); const liquidatorToken = splToken.getAssociatedTokenAddressSync( USDC_MINT_ADDRESS, @@ -2131,10 +1984,14 @@ describe("Matching Engine", function () { units: 300_000, }); + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); const txDetails = await expectIxOkDetails( connection, [computeIx, ix], [liquidator], + { addressLookupTableAccounts: [lookupTableAccount!] }, ); await checkAfterEffects( @@ -2161,12 +2018,7 @@ describe("Matching Engine", function () { fastVaa, auction, auctionDataBefore: initialData, - } = await placeInitialOfferForTest( - offerAuthorityTwo, - wormholeSequence++, - baseFastOrder, - ethRouter, - ); + } = await placeInitialOfferForTest(offerAuthorityTwo, baseFastOrder, ethRouter); const improveBy = Number( await engine.computeMinOfferDelta( @@ -2189,7 +2041,7 @@ describe("Matching Engine", function () { connection, initialOfferToken, ); - const { amount: custodyTokenBefore } = await engine.fetchCctpMintRecipient(); + const custodyTokenBefore = await engine.fetchAuctionCustodyTokenBalance(auction); const liquidatorToken = splToken.getAssociatedTokenAddressSync( USDC_MINT_ADDRESS, @@ -2218,10 +2070,14 @@ describe("Matching Engine", function () { units: 300_000, }); + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); const txDetails = await expectIxOkDetails( connection, [computeIx, ix], [liquidator], + { addressLookupTableAccounts: [lookupTableAccount!] }, ); await checkAfterEffects( @@ -2248,7 +2104,6 @@ describe("Matching Engine", function () { const { fastVaa, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, fastOrder, ethRouter, ); @@ -2275,7 +2130,6 @@ describe("Matching Engine", function () { const { fastVaaAccount, auction, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, baseFastOrder.maxFee, @@ -2284,7 +2138,6 @@ describe("Matching Engine", function () { const { fastVaa: anotherFastVaa, fastVaaAccount: anotherFastVaaAccount } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, baseFastOrder.maxFee, @@ -2303,18 +2156,12 @@ describe("Matching Engine", function () { auction, }); - await expectIxErr( - connection, - [ix], - [offerAuthorityOne], - "account: auction. Error Code: ConstraintSeeds", - ); + await expectIxErr(connection, [ix], [offerAuthorityOne], "Error Code: InvalidVaa"); }); it("Cannot Execute Fast Order (Invalid Best Offer Token Account)", async function () { const { fastVaa, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -2335,14 +2182,13 @@ describe("Matching Engine", function () { connection, [ix], [offerAuthorityOne], - "Error Code: BestOfferTokenMismatch", + "best_offer_token. Error Code: ConstraintAddress", ); }); it("Cannot Execute Fast Order (Invalid Initial Offer Token Account)", async function () { const { fastVaa, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -2363,7 +2209,7 @@ describe("Matching Engine", function () { connection, [ix], [offerAuthorityOne], - "Error Code: InitialOfferTokenMismatch", + "initial_offer_token. Error Code: ConstraintAddress", ); }); @@ -2372,7 +2218,6 @@ describe("Matching Engine", function () { // check that the initial offer is refunded. const { fastVaa, auctionDataBefore } = await placeInitialOfferForTest( offerAuthorityTwo, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -2401,14 +2246,13 @@ describe("Matching Engine", function () { connection, [ix], [offerAuthorityOne], - "Error Code: AuctionNotActive", + "custody_token. Error Code: AccountNotInitialized", ); }); it("Cannot Execute Fast Order (Auction Period Not Expired)", async function () { const { fastVaa } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -2429,7 +2273,6 @@ describe("Matching Engine", function () { it("Cannot Execute Fast Order Solana (Invalid Chain)", async function () { const { fastVaa, fastVaaAccount } = await placeInitialOfferForTest( offerAuthorityOne, - wormholeSequence++, baseFastOrder, ethRouter, ); @@ -2444,7 +2287,7 @@ describe("Matching Engine", function () { }), ], [offerAuthorityOne], - "Error Code: ConstraintSeeds", + "Error Code: InvalidEndpoint", ); }); @@ -2473,14 +2316,6 @@ describe("Matching Engine", function () { const { bump, vaaHash, info } = auctionDataBefore; const auctionDataAfter = await engine.fetchAuction({ address: auction }); - expect(auctionDataAfter).to.eql( - new Auction( - bump, - vaaHash, - { completed: { slot: bigintToU64BN(BigInt(txDetails.slot)) } }, - info, - ), - ); const { bestOfferToken, initialOfferToken, securityDeposit, amountIn, offerPrice } = info!; @@ -2513,6 +2348,20 @@ describe("Matching Engine", function () { expect(penalty > 0n).is.true; expect(userReward > 0n).is.true; + expect(auctionDataAfter).to.eql( + new Auction( + bump, + vaaHash, + { + completed: { + slot: bigintToU64BN(BigInt(txDetails.slot)), + executePenalty: bigintToU64BN(penalty), + }, + }, + info, + ), + ); + let depositAndFee = BigInt(offerPrice.add(securityDeposit).toString()) - userReward; const executorToken = splToken.getAssociatedTokenAddressSync( @@ -2543,6 +2392,20 @@ describe("Matching Engine", function () { expect(penalty).equals(0n); expect(userReward).equals(0n); + expect(auctionDataAfter).to.eql( + new Auction( + bump, + vaaHash, + { + completed: { + slot: bigintToU64BN(BigInt(txDetails.slot)), + executePenalty: null, + }, + }, + info, + ), + ); + const depositAndFee = BigInt(offerPrice.add(securityDeposit).toString()); if (bestOfferToken.equals(initialOfferToken)) { @@ -2557,10 +2420,8 @@ describe("Matching Engine", function () { } } - const { amount: custodyTokenAfter } = await engine.fetchCctpMintRecipient(); - expect(custodyTokenAfter).equals( - custodyTokenBefore - BigInt(amountIn.add(securityDeposit).toString()), - ); + const custodyTokenAfter = await engine.fetchAuctionCustodyTokenBalance(auction); + expect(custodyTokenAfter).equals(0n); // Validate the core message. const message = await engine.getCoreMessage(executor); @@ -2615,7 +2476,7 @@ describe("Matching Engine", function () { // Concoct a Circle message. const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex")); const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } = - await craftCctpTokenBurnMessage(engine, sourceCctpDomain, cctpNonce, amountIn); + await craftCctpTokenBurnMessage(sourceCctpDomain, cctpNonce, amountIn); const fastMessage = new LiquidityLayerMessage({ fastMarketOrder: { @@ -2674,7 +2535,6 @@ describe("Matching Engine", function () { payer: payer.publicKey, fastVaa, finalizedVaa, - mint: USDC_MINT_ADDRESS, }, { encodedCctpMessage, @@ -2686,7 +2546,12 @@ describe("Matching Engine", function () { units: 300_000, }); - await expectIxErr(connection, [computeIx, ix], [payer], "Error Code: VaaMismatch"); + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); + await expectIxErr(connection, [computeIx, ix], [payer], "Error Code: VaaMismatch", { + addressLookupTableAccounts: [lookupTableAccount!], + }); }); it("Cannot Prepare Order Response with Emitter Address Mismatch", async function () { @@ -2699,7 +2564,7 @@ describe("Matching Engine", function () { // Concoct a Circle message. const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex")); const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } = - await craftCctpTokenBurnMessage(engine, sourceCctpDomain, cctpNonce, amountIn); + await craftCctpTokenBurnMessage(sourceCctpDomain, cctpNonce, amountIn); const fastMessage = new LiquidityLayerMessage({ fastMarketOrder: { @@ -2757,7 +2622,6 @@ describe("Matching Engine", function () { payer: payer.publicKey, fastVaa, finalizedVaa, - mint: USDC_MINT_ADDRESS, }, { encodedCctpMessage, @@ -2769,7 +2633,12 @@ describe("Matching Engine", function () { units: 300_000, }); - await expectIxErr(connection, [computeIx, ix], [payer], "Error Code: VaaMismatch"); + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); + await expectIxErr(connection, [computeIx, ix], [payer], "Error Code: VaaMismatch", { + addressLookupTableAccounts: [lookupTableAccount!], + }); }); it("Cannot Prepare Order Response with Emitter Sequence Mismatch", async function () { @@ -2782,7 +2651,7 @@ describe("Matching Engine", function () { // Concoct a Circle message. const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex")); const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } = - await craftCctpTokenBurnMessage(engine, sourceCctpDomain, cctpNonce, amountIn); + await craftCctpTokenBurnMessage(sourceCctpDomain, cctpNonce, amountIn); const fastMessage = new LiquidityLayerMessage({ fastMarketOrder: { @@ -2840,7 +2709,6 @@ describe("Matching Engine", function () { payer: payer.publicKey, fastVaa, finalizedVaa, - mint: USDC_MINT_ADDRESS, }, { encodedCctpMessage, @@ -2852,7 +2720,12 @@ describe("Matching Engine", function () { units: 300_000, }); - await expectIxErr(connection, [computeIx, ix], [payer], "Error Code: VaaMismatch"); + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); + await expectIxErr(connection, [computeIx, ix], [payer], "Error Code: VaaMismatch", { + addressLookupTableAccounts: [lookupTableAccount!], + }); }); // TODO: Test timestamp mismatch @@ -2868,7 +2741,7 @@ describe("Matching Engine", function () { // Concoct a Circle message. const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex")); const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } = - await craftCctpTokenBurnMessage(engine, sourceCctpDomain, cctpNonce, amountIn); + await craftCctpTokenBurnMessage(sourceCctpDomain, cctpNonce, amountIn); const fastMessage = new LiquidityLayerMessage({ fastMarketOrder: { @@ -2926,7 +2799,6 @@ describe("Matching Engine", function () { payer: payer.publicKey, fastVaa, finalizedVaa, - mint: USDC_MINT_ADDRESS, }, { encodedCctpMessage, @@ -2938,23 +2810,52 @@ describe("Matching Engine", function () { units: 300_000, }); - await expectIxOk(connection, [computeIx, ix], [payer]); + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); + await expectIxOk(connection, [computeIx, ix], [payer], { + addressLookupTableAccounts: [lookupTableAccount!], + }); // TODO: validate prepared slow order const fastVaaHash = await VaaAccount.fetch(connection, fastVaa).then((vaa) => vaa.digest(), ); - const preparedOrderResponse = engine.preparedOrderResponseAddress( - payer.publicKey, - fastVaaHash, + const preparedOrderResponse = engine.preparedOrderResponseAddress(fastVaaHash); + + const preparedOrderResponseData = await engine.fetchPreparedOrderResponse({ + address: preparedOrderResponse, + }); + const { bump } = preparedOrderResponseData; + + expect(preparedOrderResponseData).to.eql({ + bump, + preparedBy: payer.publicKey, + fastVaaHash: Array.from(fastVaaHash), + sourceChain: ethChain, + baseFee: bigintToU64BN( + finalizedMessage.deposit!.message.slowOrderResponse!.baseFee, + ), + }); + + const { amount: preparedCustodyBalance } = await splToken.getAccount( + connection, + engine.preparedCustodyTokenAddress(preparedOrderResponse), + ); + expect(preparedCustodyBalance).equals(fastMessage.fastMarketOrder!.amountIn); + + const { amount: cctpMintRecipientBalance } = await splToken.getAccount( + connection, + engine.cctpMintRecipientAddress(), ); + expect(cctpMintRecipientBalance).equals(0n); // Save for later. localVariables.set("ix", ix); localVariables.set("preparedOrderResponse", preparedOrderResponse); }); - it("Cannot Prepare Order Response for Same VAAs", async function () { + it("Prepare Order Response for Same VAAs is No-op", async function () { const ix = localVariables.get("ix") as TransactionInstruction; expect(localVariables.delete("ix")).is.true; @@ -2963,168 +2864,335 @@ describe("Matching Engine", function () { ) as PublicKey; expect(localVariables.delete("preparedOrderResponse")).is.true; - await expectIxErr( - connection, - [ix], - [payer], - `Allocate: account Address { address: ${preparedOrderResponse.toString()}, base: None } already in use`, - ); - }); - }); - - describe("Settle Auction", function () { - describe("Auction Complete", function () { - it("Cannot Settle Auction in Active Status", async function () { - const { prepareIx, preparedOrderResponse, auction } = - await prepareOrderResponse({ - initAuction: true, - executeOrder: false, - prepareOrderResponse: false, - }); - - const settleIx = await engine.settleAuctionCompleteIx({ - preparedOrderResponse, - auction, - preparedBy: payer.publicKey, - }); - - const { value: lookupTableAccount } = await connection.getAddressLookupTable( - lookupTableAddress, - ); - const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ - units: 500_000, - }); - await expectIxErr( - connection, - [computeIx, prepareIx!, settleIx], - [payer], - "Error Code: AuctionNotCompleted", - { - addressLookupTableAccounts: [lookupTableAccount!], - }, - ); + const preparedOrderResponseDataBefore = await engine.fetchPreparedOrderResponse({ + address: preparedOrderResponse, }); - it.skip("Prepare and Settle", async function () { - const { - fastVaa, - fastVaaAccount, - finalizedVaa, - prepareIx, - preparedOrderResponse, - auction, - } = await prepareOrderResponse({ - initAuction: true, - executeOrder: true, - prepareOrderResponse: false, - }); - - const settleIx = await engine.settleAuctionCompleteIx({ - preparedOrderResponse, - auction, - }); + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); + await expectIxOk(connection, [ix], [payer], { + addressLookupTableAccounts: [lookupTableAccount!], + }); - await expectIxOk(connection, [prepareIx!, settleIx], [payer]); + const preparedOrderResponseDataAfter = await engine.fetchPreparedOrderResponse({ + address: preparedOrderResponse, }); + expect(preparedOrderResponseDataAfter).to.eql(preparedOrderResponseDataBefore); }); - describe("Active Auction", function () { - it("Cannot Settle Executed Auction", async function () { - const { auction, fastVaa, fastVaaAccount, prepareIx } = - await prepareOrderResponse({ - executeOrder: true, - initAuction: true, - prepareOrderResponse: false, - }); - expect(prepareIx).is.not.null; + it("Prepare Order Response for Active Auction", async function () { + const redeemer = Keypair.generate(); - const liquidatorToken = await splToken.getAssociatedTokenAddress( - USDC_MINT_ADDRESS, - liquidator.publicKey, - ); + const sourceCctpDomain = 0; + const cctpNonce = testCctpNonce++; + const amountIn = 690000n; // 69 cents - const sourceCctpDomain = 0; - const cctpNonce = testCctpNonce++; - const amountIn = 690000n; // 69 cents - const { encodedCctpMessage } = await craftCctpTokenBurnMessage( - engine, - sourceCctpDomain, - cctpNonce, - amountIn, - ); + // Concoct a Circle message. + const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex")); + const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } = + await craftCctpTokenBurnMessage(sourceCctpDomain, cctpNonce, amountIn); + + const fastMarketOrder = { + amountIn, + minAmountOut: 0n, + targetChain: arbChain, + redeemer: Array.from(redeemer.publicKey.toBuffer()), + sender: new Array(32).fill(0), + refundAddress: new Array(32).fill(0), + maxFee: 42069n, + initAuctionFee: 2000n, + deadline: 0, + redeemerMessage: Buffer.from("Somebody set up us the bomb"), + }; - const settleIx = await engine.settleAuctionActiveCctpIx( + const finalizedMessage = new LiquidityLayerMessage({ + deposit: new LiquidityLayerDeposit( { - payer: payer.publicKey, - fastVaa, - fastVaaAccount, - executorToken: liquidatorToken, - auction, - encodedCctpMessage, + tokenAddress: burnMessage.burnTokenAddress, + amount: amountIn, + sourceCctpDomain, + destinationCctpDomain, + cctpNonce, + burnSource, + mintRecipient: Array.from(engine.cctpMintRecipientAddress().toBuffer()), }, - { targetChain: ethChain, remoteDomain: solanaChain }, - ); + { + slowOrderResponse: { + baseFee: 420n, + }, + }, + ), + }); - const { value: lookupTableAccount } = await connection.getAddressLookupTable( - lookupTableAddress, - ); - const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ - units: 500_000, + const finalizedVaa = await postLiquidityLayerVaa( + connection, + payer, + MOCK_GUARDIANS, + ethRouter, + wormholeSequence++, + finalizedMessage, + ); + + const { fastVaa, auction } = await placeInitialOfferForTest( + offerAuthorityOne, + fastMarketOrder, + ethRouter, + fastMarketOrder.maxFee, + ); + + const ix = await engine.prepareOrderResponseCctpIx( + { + payer: payer.publicKey, + fastVaa, + finalizedVaa, + }, + { + encodedCctpMessage, + cctpAttestation, + }, + true, // hasAuction + ); + + const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ + units: 300_000, + }); + + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); + await expectIxOk(connection, [computeIx, ix], [payer], { + addressLookupTableAccounts: [lookupTableAccount!], + }); + + // TODO: validate prepared slow order + const fastVaaHash = await VaaAccount.fetch(connection, fastVaa).then((vaa) => + vaa.digest(), + ); + const preparedOrderResponse = engine.preparedOrderResponseAddress(fastVaaHash); + + const preparedOrderResponseData = await engine.fetchPreparedOrderResponse({ + address: preparedOrderResponse, + }); + const { bump } = preparedOrderResponseData; + + expect(preparedOrderResponseData).to.eql({ + bump, + preparedBy: payer.publicKey, + fastVaaHash: Array.from(fastVaaHash), + sourceChain: ethChain, + baseFee: bigintToU64BN( + finalizedMessage.deposit!.message.slowOrderResponse!.baseFee, + ), + }); + + const { amount: preparedCustodyBalance } = await splToken.getAccount( + connection, + engine.preparedCustodyTokenAddress(preparedOrderResponse), + ); + expect(preparedCustodyBalance).equals(fastMarketOrder.amountIn); + + const { amount: cctpMintRecipientBalance } = await splToken.getAccount( + connection, + engine.cctpMintRecipientAddress(), + ); + expect(cctpMintRecipientBalance).equals(0n); + + const { info } = await engine.fetchAuction({ address: auction }); + expect(info!.endEarly).is.true; + }); + }); + + describe("Settle Auction", function () { + describe("Auction Complete", function () { + it("Cannot Settle Active Auction", async function () { + const { preparedOrderResponse, auction } = await prepareOrderResponse({ + initAuction: true, + executeOrder: false, + prepareOrderResponse: true, + }); + + const ix = await engine.settleAuctionCompleteIx({ + executor: payer.publicKey, + preparedOrderResponse, + auction, }); + + await expectIxErr(connection, [ix], [payer], "Error Code: AuctionNotCompleted"); + }); + + it("Cannot Settle Completed Auction with No Penalty (Executor != Best Offer)", async function () { + const { preparedOrderResponse, auction } = await prepareOrderResponse({ + initAuction: true, + executeOrder: true, + prepareOrderResponse: true, + }); + const { status: statusBefore } = await engine.fetchAuction({ + address: auction, + }); + expect(statusBefore.completed!.executePenalty).is.null; + + const ix = await engine.settleAuctionCompleteIx({ + executor: payer.publicKey, + preparedOrderResponse, + auction, + }); + await expectIxErr( connection, - [prepareIx!, settleIx, computeIx], + [ix], [payer], - "Error Code: AuctionNotActive", - { - addressLookupTableAccounts: [lookupTableAccount!], - }, + "Error Code: ExecutorTokenMismatch", ); }); - it("Settle", async function () { - const { auction, fastVaa, fastVaaAccount, prepareIx } = - await prepareOrderResponse({ - executeOrder: false, - initAuction: true, - prepareOrderResponse: false, - }); - expect(prepareIx).is.not.null; + it("Settle Completed without Penalty", async function () { + const { baseFee, preparedOrderResponse, auction } = await prepareOrderResponse({ + initAuction: true, + executeOrder: true, + prepareOrderResponse: true, + }); - const liquidatorToken = await splToken.getAssociatedTokenAddress( - USDC_MINT_ADDRESS, - liquidator.publicKey, + const preparedCustodyToken = + engine.preparedCustodyTokenAddress(preparedOrderResponse); + + const { amount: preparedCustodyBalanceBefore } = await splToken.getAccount( + connection, + preparedCustodyToken, ); - const sourceCctpDomain = 0; - const cctpNonce = testCctpNonce++; - const amountIn = 690000n; // 69 cents - const { encodedCctpMessage } = await craftCctpTokenBurnMessage( - engine, - sourceCctpDomain, - cctpNonce, - amountIn, + const { info, status: statusBefore } = await engine.fetchAuction({ + address: auction, + }); + expect(statusBefore.completed!.executePenalty).is.null; + + const { bestOfferToken } = info!; + const { owner: bestOfferAuthority, amount: bestOfferTokenBalanceBefore } = + await splToken.getAccount(connection, bestOfferToken); + + const authorityLamportsBefore = await connection.getBalance(bestOfferAuthority); + + const preparedOrderLamports = await connection + .getAccountInfo(preparedOrderResponse) + .then((info) => info!.lamports); + const preparedCustodyLamports = await connection + .getAccountInfo(preparedCustodyToken) + .then((info) => info!.lamports); + + const ix = await engine.settleAuctionCompleteIx({ + executor: bestOfferAuthority, + preparedOrderResponse, + }); + + await expectIxOk(connection, [ix], [payer]); + + const preparedCustodyInfo = await connection.getAccountInfo( + preparedCustodyToken, ); - const settleIx = await engine.settleAuctionActiveCctpIx( - { - payer: payer.publicKey, - fastVaa, - fastVaaAccount, - executorToken: liquidatorToken, - auction, - encodedCctpMessage, + expect(preparedCustodyInfo).is.null; + const preparedOrderResponseInfo = await connection.getAccountInfo( + preparedOrderResponse, + ); + expect(preparedOrderResponseInfo).is.null; + + const { amount: bestOfferTokenBalanceAfter } = await splToken.getAccount( + connection, + bestOfferToken, + ); + expect(bestOfferTokenBalanceAfter).equals( + preparedCustodyBalanceBefore + bestOfferTokenBalanceBefore, + ); + + const authorityLamportsAfter = await connection.getBalance(bestOfferAuthority); + expect(authorityLamportsAfter).equals( + authorityLamportsBefore + preparedOrderLamports + preparedCustodyLamports, + ); + + const { status: statusAfter } = await engine.fetchAuction({ + address: auction, + }); + expect(statusAfter).to.eql({ + settled: { + baseFee: bigintToU64BN(baseFee), + totalPenalty: null, }, - { targetChain: ethChain, remoteDomain: solanaChain }, + }); + }); + + it("Settle Completed with Penalty", async function () { + const { baseFee, preparedOrderResponse, auction } = await prepareOrderResponse({ + initAuction: true, + executeOrder: true, + prepareOrderResponse: true, + executeLate: true, + }); + + const preparedCustodyToken = + engine.preparedCustodyTokenAddress(preparedOrderResponse); + + const { amount: preparedCustodyBalanceBefore } = await splToken.getAccount( + connection, + preparedCustodyToken, ); - const { value: lookupTableAccount } = await connection.getAddressLookupTable( - lookupTableAddress, + const { info, status: statusBefore } = await engine.fetchAuction({ + address: auction, + }); + const executePenalty = statusBefore.completed!.executePenalty; + expect(executePenalty).is.not.null; + + const { bestOfferToken } = info!; + const { owner: bestOfferAuthority, amount: bestOfferTokenBalanceBefore } = + await splToken.getAccount(connection, bestOfferToken); + + const authorityLamportsBefore = await connection.getBalance(bestOfferAuthority); + + const preparedOrderLamports = await connection + .getAccountInfo(preparedOrderResponse) + .then((info) => info!.lamports); + const preparedCustodyLamports = await connection + .getAccountInfo(preparedCustodyToken) + .then((info) => info!.lamports); + + const ix = await engine.settleAuctionCompleteIx({ + executor: bestOfferAuthority, + preparedOrderResponse, + }); + + await expectIxOk(connection, [ix], [payer]); + + const preparedCustodyInfo = await connection.getAccountInfo( + preparedCustodyToken, ); - const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ - units: 500_000, + expect(preparedCustodyInfo).is.null; + const preparedOrderResponseInfo = await connection.getAccountInfo( + preparedOrderResponse, + ); + expect(preparedOrderResponseInfo).is.null; + + const { amount: bestOfferTokenBalanceAfter } = await splToken.getAccount( + connection, + bestOfferToken, + ); + expect(bestOfferTokenBalanceAfter).equals( + preparedCustodyBalanceBefore + bestOfferTokenBalanceBefore, + ); + + const authorityLamportsAfter = await connection.getBalance(bestOfferAuthority); + expect(authorityLamportsAfter).equals( + authorityLamportsBefore + preparedOrderLamports + preparedCustodyLamports, + ); + + const { status: statusAfter } = await engine.fetchAuction({ + address: auction, }); - await expectIxOk(connection, [prepareIx!, settleIx, computeIx], [payer], { - addressLookupTableAccounts: [lookupTableAccount!], + expect(statusAfter).to.eql({ + settled: { + baseFee: bigintToU64BN(baseFee), + totalPenalty: bigintToU64BN( + BigInt(executePenalty!.toString()) + baseFee, + ), + }, }); }); }); @@ -3154,7 +3222,12 @@ describe("Matching Engine", function () { connection, feeRecipientToken, ); - const { amount: custodyBalanceBefore } = await engine.fetchCctpMintRecipient(); + const preparedCustodyToken = + engine.preparedCustodyTokenAddress(preparedOrderResponse); + const { amount: custodyBalanceBefore } = await splToken.getAccount( + connection, + preparedCustodyToken, + ); await expectIxOk(connection, [computeIx, settleIx], [payer]); @@ -3168,13 +3241,23 @@ describe("Matching Engine", function () { ); expect(feeBalanceAfter).equals(feeBalanceBefore + baseFee); - const { amount } = deposit.header; - const { amount: custodyBalanceAfter } = await engine.fetchCctpMintRecipient(); - expect(custodyBalanceAfter).equals(custodyBalanceBefore - amount); + const { amount: custodyBalanceAfter } = await splToken.getAccount( + connection, + preparedCustodyToken, + ); + expect(custodyBalanceAfter).equals( + custodyBalanceBefore - deposit.header.amount, + ); + + const { amount: cctpMintRecipientBalance } = + await engine.fetchCctpMintRecipient(); + expect(cctpMintRecipientBalance).equals(0n); const fastVaaHash = fastVaaAccount.digest(); const auctionData = await engine.fetchAuction(fastVaaHash); - const { bump } = auctionData; + const { bump, info } = auctionData; + expect(info).is.null; + expect(auctionData).to.eql( new Auction( bump, @@ -3182,7 +3265,7 @@ describe("Matching Engine", function () { { settled: { baseFee: bigintToU64BN(baseFee), - penalty: null, + totalPenalty: null, }, }, null, @@ -3190,167 +3273,15 @@ describe("Matching Engine", function () { ); }); }); - - async function prepareOrderResponse(args: { - initAuction: boolean; - executeOrder: boolean; - prepareOrderResponse: boolean; - }) { - const { initAuction, executeOrder, prepareOrderResponse } = args; - - const redeemer = Keypair.generate(); - const sourceCctpDomain = 0; - const cctpNonce = testCctpNonce++; - const amountIn = 690000n; // 69 cents - - // Concoct a Circle message. - const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex")); - const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } = - await craftCctpTokenBurnMessage(engine, sourceCctpDomain, cctpNonce, amountIn); - - const maxFee = 42069n; - const currTime = await connection.getBlockTime(await connection.getSlot()); - const fastMessage = new LiquidityLayerMessage({ - fastMarketOrder: { - amountIn, - minAmountOut: 0n, - targetChain: arbChain, - redeemer: Array.from(redeemer.publicKey.toBuffer()), - sender: new Array(32).fill(0), - refundAddress: new Array(32).fill(0), - maxFee, - initAuctionFee: 2000n, - deadline: currTime! + 2, - redeemerMessage: Buffer.from("Somebody set up us the bomb"), - }, - }); - - const finalizedMessage = new LiquidityLayerMessage({ - deposit: new LiquidityLayerDeposit( - { - tokenAddress: burnMessage.burnTokenAddress, - amount: amountIn, - sourceCctpDomain, - destinationCctpDomain, - cctpNonce, - burnSource, - mintRecipient: Array.from(engine.cctpMintRecipientAddress().toBuffer()), - }, - { - slowOrderResponse: { - baseFee: 420n, - }, - }, - ), - }); - - const finalizedVaa = await postLiquidityLayerVaa( - connection, - payer, - MOCK_GUARDIANS, - ethRouter, - wormholeSequence++, - finalizedMessage, - ); - const finalizedVaaAccount = await VaaAccount.fetch(connection, finalizedVaa); - - const fastVaa = await postLiquidityLayerVaa( - connection, - payer, - MOCK_GUARDIANS, - ethRouter, - wormholeSequence++, - fastMessage, - ); - const fastVaaAccount = await VaaAccount.fetch(connection, fastVaa); - - const prepareIx = await engine.prepareOrderResponseCctpIx( - { - payer: payer.publicKey, - fastVaa, - finalizedVaa, - }, - { - encodedCctpMessage, - cctpAttestation, - }, - ); - - const fastVaaHash = fastVaaAccount.digest(); - const preparedBy = payer.publicKey; - const preparedOrderResponse = engine.preparedOrderResponseAddress( - preparedBy, - fastVaaHash, - ); - const auction = engine.auctionAddress(fastVaaHash); - - if (initAuction) { - const [approveIx, ix] = await engine.placeInitialOfferIx( - { - payer: offerAuthorityOne.publicKey, - fastVaa, - }, - maxFee, - ); - await expectIxOk(connection, [approveIx, ix], [offerAuthorityOne]); - - if (executeOrder) { - const { info } = await engine.fetchAuction({ address: auction }); - if (info === null) { - throw new Error("No auction info found"); - } - const { configId, bestOfferToken, initialOfferToken, startSlot } = info; - const auctionConfig = engine.auctionConfigAddress(configId); - const duration = (await engine.fetchAuctionConfig(configId)).parameters - .duration; - - await new Promise((f) => - setTimeout(f, startSlot.toNumber() + duration + 200), - ); - - const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ - units: 300_000, - }); - const ix = await engine.executeFastOrderCctpIx({ - payer: payer.publicKey, - fastVaa, - auction, - auctionConfig, - bestOfferToken, - initialOfferToken, - }); - await expectIxOk(connection, [computeIx, ix], [payer]); - } - } - - if (prepareOrderResponse) { - const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ - units: 300_000, - }); - await expectIxOk(connection, [computeIx, prepareIx], [payer]); - } - - return { - fastVaa, - fastVaaAccount, - finalizedVaa, - finalizedVaaAccount, - prepareIx: prepareOrderResponse ? null : prepareIx, - preparedOrderResponse, - auction, - preparedBy, - }; - } }); }); async function placeInitialOfferForTest( offerAuthority: Keypair, - sequence: bigint, fastMarketOrder: FastMarketOrder, emitter: number[], - feeOffer?: bigint, - chainName?: wormholeSdk.ChainName, + offerPrice?: bigint, + sourceChain?: wormholeSdk.ChainName, ): Promise<{ fastVaa: PublicKey; fastVaaAccount: VaaAccount; @@ -3358,35 +3289,95 @@ describe("Matching Engine", function () { auction: PublicKey; auctionDataBefore: Auction; }> { - const fastVaa = await postLiquidityLayerVaa( + const { + fast: { vaa: fastVaa, vaaAccount: fastVaaAccount }, + } = await observeCctpOrderVaas({ + sourceChain, + fastMarketOrder, + }); + offerPrice = offerPrice ?? fastMarketOrder.maxFee; + + const offerToken = splToken.getAssociatedTokenAddressSync( + USDC_MINT_ADDRESS, + offerAuthority.publicKey, + ); + + const { amount: offerTokenBalanceBefore } = await splToken.getAccount( connection, - offerAuthority, - MOCK_GUARDIANS, - emitter, - sequence, - new LiquidityLayerMessage({ fastMarketOrder }), - chainName, + offerToken, ); + const auction = engine.auctionAddress(fastVaaAccount.digest()); + const auctionCustodyBalanceBefore = await engine.fetchAuctionCustodyTokenBalance(auction); + // Place the initial offer. const [approveIx, ix] = await engine.placeInitialOfferIx( { payer: offerAuthority.publicKey, fastVaa, }, - feeOffer ?? fastMarketOrder.maxFee, + offerPrice, ); const txDetails = await expectIxOkDetails(connection, [approveIx, ix], [offerAuthority]); if (txDetails === null) { throw new Error("Transaction details is null"); } - - const fastVaaAccount = await VaaAccount.fetch(connection, fastVaa); - const auction = engine.auctionAddress(fastVaaAccount.digest()); const auctionDataBefore = await engine.fetchAuction({ address: auction }); - return { fastVaa, fastVaaAccount, txDetails, auction, auctionDataBefore }; + // Validate balance changes. + const { amount: offerTokenBalanceAfter } = await splToken.getAccount( + connection, + offerToken, + ); + + const auctionCustodyBalanceAfter = await engine.fetchAuctionCustodyTokenBalance(auction); + const balanceChange = fastMarketOrder.amountIn + fastMarketOrder.maxFee; + + expect(offerTokenBalanceAfter).equals(offerTokenBalanceBefore - balanceChange); + expect(auctionCustodyBalanceAfter).equals(auctionCustodyBalanceBefore + balanceChange); + + // Confirm the auction data. + const vaaHash = fastVaaAccount.digest(); + const auctionData = await engine.fetchAuction(vaaHash); + const { bump, info } = auctionData; + const { custodyTokenBump } = info!; + + const { auctionConfigId } = await engine.fetchCustodian(); + + expect(fastMarketOrder).is.not.undefined; + const { amountIn, maxFee } = fastMarketOrder!; + + const expectedAmountIn = bigintToU64BN(amountIn); + expect(auctionData).to.eql( + new Auction( + bump, + Array.from(vaaHash), + { active: {} }, + { + configId: auctionConfigId, + custodyTokenBump, + vaaSequence: bigintToU64BN(fastVaaAccount.emitterInfo().sequence), + sourceChain: ethChain, + bestOfferToken: offerToken, + initialOfferToken: offerToken, + startSlot: numberToU64BN(txDetails.slot), + amountIn: expectedAmountIn, + securityDeposit: bigintToU64BN(maxFee), + offerPrice: bigintToU64BN(offerPrice), + amountOut: expectedAmountIn, + endEarly: false, + }, + ), + ); + + return { + fastVaa, + fastVaaAccount, + txDetails, + auction, + auctionDataBefore, + }; } async function improveOfferForTest( @@ -3415,53 +3406,365 @@ describe("Matching Engine", function () { auctionDataBefore, }; } -}); -async function craftCctpTokenBurnMessage( - engine: MatchingEngineProgram, - sourceCctpDomain: number, - cctpNonce: bigint, - amount: bigint, - overrides: { destinationCctpDomain?: number } = {}, -) { - const { destinationCctpDomain: inputDestinationCctpDomain } = overrides; - - const messageTransmitterProgram = engine.messageTransmitterProgram(); - const { version, localDomain } = await messageTransmitterProgram.fetchMessageTransmitterConfig( - messageTransmitterProgram.messageTransmitterConfigAddress(), - ); - const destinationCctpDomain = inputDestinationCctpDomain ?? localDomain; + async function prepareOrderResponse(args: { + initAuction: boolean; + executeOrder: boolean; + prepareOrderResponse: boolean; + executeLate?: boolean; + }) { + const baseFee = 420n; + + const { + initAuction, + executeOrder, + prepareOrderResponse, + executeLate: inputExecuteLate, + } = args; + + const executeLate = inputExecuteLate ?? false; + + const redeemer = Keypair.generate(); + const sourceCctpDomain = 0; + const cctpNonce = testCctpNonce++; + const amountIn = 690000n; // 69 cents + + // Concoct a Circle message. + const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex")); + const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } = + await craftCctpTokenBurnMessage(sourceCctpDomain, cctpNonce, amountIn); + + const maxFee = 42069n; + const currTime = await connection.getBlockTime(await connection.getSlot()); + const fastMessage = new LiquidityLayerMessage({ + fastMarketOrder: { + amountIn, + minAmountOut: 0n, + targetChain: arbChain, + redeemer: Array.from(redeemer.publicKey.toBuffer()), + sender: new Array(32).fill(0), + refundAddress: new Array(32).fill(0), + maxFee, + initAuctionFee: 2000n, + deadline: currTime! + 2, + redeemerMessage: Buffer.from("Somebody set up us the bomb"), + }, + }); - const tokenMessengerMinterProgram = engine.tokenMessengerMinterProgram(); - const { tokenMessenger: sourceTokenMessenger } = - await tokenMessengerMinterProgram.fetchRemoteTokenMessenger( - tokenMessengerMinterProgram.remoteTokenMessengerAddress(sourceCctpDomain), + const finalizedMessage = new LiquidityLayerMessage({ + deposit: new LiquidityLayerDeposit( + { + tokenAddress: burnMessage.burnTokenAddress, + amount: amountIn, + sourceCctpDomain, + destinationCctpDomain, + cctpNonce, + burnSource, + mintRecipient: Array.from(engine.cctpMintRecipientAddress().toBuffer()), + }, + { + slowOrderResponse: { + baseFee, + }, + }, + ), + }); + + const finalizedVaa = await postLiquidityLayerVaa( + connection, + payer, + MOCK_GUARDIANS, + ethRouter, + wormholeSequence++, + finalizedMessage, ); + const finalizedVaaAccount = await VaaAccount.fetch(connection, finalizedVaa); - const burnMessage = new CctpTokenBurnMessage( - { - version, - sourceDomain: sourceCctpDomain, - destinationDomain: destinationCctpDomain, - nonce: cctpNonce, - sender: sourceTokenMessenger, - recipient: Array.from(tokenMessengerMinterProgram.ID.toBuffer()), // targetTokenMessenger - targetCaller: Array.from(engine.custodianAddress().toBuffer()), // targetCaller - }, - 0, - Array.from(wormholeSdk.tryNativeToUint8Array(ETHEREUM_USDC_ADDRESS, "ethereum")), // sourceTokenAddress - Array.from(engine.cctpMintRecipientAddress().toBuffer()), // mint recipient - amount, - new Array(32).fill(0), // burnSource - ); + const fastVaa = await postLiquidityLayerVaa( + connection, + payer, + MOCK_GUARDIANS, + ethRouter, + wormholeSequence++, + fastMessage, + ); + const fastVaaAccount = await VaaAccount.fetch(connection, fastVaa); - const encodedCctpMessage = burnMessage.encode(); - const cctpAttestation = new CircleAttester().createAttestation(encodedCctpMessage); + const prepareIx = await engine.prepareOrderResponseCctpIx( + { + payer: payer.publicKey, + fastVaa, + finalizedVaa, + }, + { + encodedCctpMessage, + cctpAttestation, + }, + ); - return { - destinationCctpDomain, - burnMessage, - encodedCctpMessage, - cctpAttestation, - }; -} + const fastVaaHash = fastVaaAccount.digest(); + const preparedBy = payer.publicKey; + const preparedOrderResponse = engine.preparedOrderResponseAddress(fastVaaHash); + const auction = engine.auctionAddress(fastVaaHash); + + if (initAuction) { + const [approveIx, ix] = await engine.placeInitialOfferIx( + { + payer: offerAuthorityOne.publicKey, + fastVaa, + }, + maxFee, + ); + await expectIxOk(connection, [approveIx, ix], [offerAuthorityOne]); + + if (executeOrder) { + const { info } = await engine.fetchAuction({ address: auction }); + if (info === null) { + throw new Error("No auction info found"); + } + const { configId, bestOfferToken, initialOfferToken, startSlot } = info; + const auctionConfig = engine.auctionConfigAddress(configId); + const { duration, gracePeriod, penaltyPeriod } = + await engine.fetchAuctionParameters(configId); + + const endSlot = (() => { + if (executeLate) { + return startSlot + .addn(duration + gracePeriod + penaltyPeriod - 1) + .toNumber(); + } else { + return startSlot.addn(duration + gracePeriod - 1).toNumber(); + } + })(); + + await waitUntilSlot(connection, endSlot); + + const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ + units: 300_000, + }); + const ix = await engine.executeFastOrderCctpIx({ + payer: payer.publicKey, + fastVaa, + auction, + auctionConfig, + bestOfferToken, + initialOfferToken, + }); + await expectIxOk(connection, [computeIx, ix], [payer]); + } + } + + if (prepareOrderResponse) { + const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ + units: 300_000, + }); + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); + await expectIxOk(connection, [computeIx, prepareIx], [payer], { + addressLookupTableAccounts: [lookupTableAccount!], + }); + } + + return { + baseFee, + fastMessage, + fastVaa, + fastVaaAccount, + finalizedVaa, + finalizedVaaAccount, + prepareIx: prepareOrderResponse ? null : prepareIx, + preparedOrderResponse, + auction, + preparedBy, + }; + } + + function newFastMarketOrder( + args: { + amountIn?: bigint; + minAmountOut?: bigint; + initAuctionFee?: bigint; + targetChain?: wormholeSdk.ChainName; + maxFee?: bigint; + deadline?: number; + redeemerMessage?: Buffer; + } = {}, + ): FastMarketOrder { + const { + amountIn, + targetChain, + minAmountOut, + maxFee, + initAuctionFee, + deadline, + redeemerMessage, + } = args; + + return { + amountIn: amountIn ?? 1_000_000_000n, + minAmountOut: minAmountOut ?? 0n, + targetChain: wormholeSdk.coalesceChainId(targetChain ?? "arbitrum"), + redeemer: new Array(32).fill(1), + sender: new Array(32).fill(2), + refundAddress: new Array(32).fill(3), + maxFee: maxFee ?? 42069n, + initAuctionFee: initAuctionFee ?? 1_250_000n, + deadline: deadline ?? 0, + redeemerMessage: redeemerMessage ?? Buffer.from("Somebody set up us the bomb"), + }; + } + + function newSlowOrderResponse(args: { baseFee?: bigint } = {}): SlowOrderResponse { + const { baseFee } = args; + + return { + baseFee: baseFee ?? 420n, + }; + } + + async function observeCctpOrderVaas( + args: { + sourceChain?: wormholeSdk.ChainName; + fastMarketOrder?: FastMarketOrder; + slowOrderResponse?: SlowOrderResponse; + finalized?: boolean; + } = {}, + ): Promise<{ + fast: { + vaa: PublicKey; + vaaAccount: VaaAccount; + }; + finalized?: { + vaa: PublicKey; + vaaAccount: VaaAccount; + encodedCctpMessage: Buffer; + cctpAttestation: Buffer; + }; + }> { + let { sourceChain, fastMarketOrder, slowOrderResponse, finalized } = args; + sourceChain ??= "ethereum"; + fastMarketOrder ??= newFastMarketOrder(); + slowOrderResponse ??= newSlowOrderResponse(); + finalized ??= false; + + const sourceCctpDomain = CHAIN_TO_DOMAIN[sourceChain]; + if (sourceCctpDomain === undefined) { + throw new Error(`Invalid source chain: ${sourceChain}`); + } + + // TODO: consider taking this out this condition when other test methods use this method to + // generate VAAs. + const finalizedSequence = finalized ? wormholeSequence++ : 0n; + const fastSequence = wormholeSequence++; + + const fastVaa = await postLiquidityLayerVaa( + connection, + payer, + MOCK_GUARDIANS, + ethRouter, + fastSequence, + new LiquidityLayerMessage({ + fastMarketOrder, + }), + ); + const fastVaaAccount = await VaaAccount.fetch(connection, fastVaa); + const fast = { vaa: fastVaa, vaaAccount: fastVaaAccount }; + + if (finalized) { + const { amountIn: amount } = fastMarketOrder; + const cctpNonce = testCctpNonce++; + + // Concoct a Circle message. + const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } = + await craftCctpTokenBurnMessage(sourceCctpDomain, cctpNonce, amount); + + const finalizedMessage = new LiquidityLayerMessage({ + deposit: new LiquidityLayerDeposit( + { + tokenAddress: burnMessage.burnTokenAddress, + amount, + sourceCctpDomain, + destinationCctpDomain, + cctpNonce, + burnSource: Array.from(Buffer.alloc(32, "beefdead", "hex")), + mintRecipient: Array.from(engine.cctpMintRecipientAddress().toBuffer()), + }, + { + slowOrderResponse, + }, + ), + }); + + const finalizedVaa = await postLiquidityLayerVaa( + connection, + payer, + MOCK_GUARDIANS, + ethRouter, + finalizedSequence, + finalizedMessage, + ); + const finalizedVaaAccount = await VaaAccount.fetch(connection, finalizedVaa); + return { + fast, + finalized: { + vaa: finalizedVaa, + vaaAccount: finalizedVaaAccount, + encodedCctpMessage, + cctpAttestation, + }, + }; + } else { + return { fast }; + } + } + + async function craftCctpTokenBurnMessage( + sourceCctpDomain: number, + cctpNonce: bigint, + amount: bigint, + overrides: { destinationCctpDomain?: number } = {}, + ) { + const { destinationCctpDomain: inputDestinationCctpDomain } = overrides; + + const messageTransmitterProgram = engine.messageTransmitterProgram(); + const { version, localDomain } = + await messageTransmitterProgram.fetchMessageTransmitterConfig( + messageTransmitterProgram.messageTransmitterConfigAddress(), + ); + const destinationCctpDomain = inputDestinationCctpDomain ?? localDomain; + + const tokenMessengerMinterProgram = engine.tokenMessengerMinterProgram(); + const { tokenMessenger: sourceTokenMessenger } = + await tokenMessengerMinterProgram.fetchRemoteTokenMessenger( + tokenMessengerMinterProgram.remoteTokenMessengerAddress(sourceCctpDomain), + ); + + const burnMessage = new CctpTokenBurnMessage( + { + version, + sourceDomain: sourceCctpDomain, + destinationDomain: destinationCctpDomain, + nonce: cctpNonce, + sender: sourceTokenMessenger, + recipient: Array.from(tokenMessengerMinterProgram.ID.toBuffer()), // targetTokenMessenger + targetCaller: Array.from(engine.custodianAddress().toBuffer()), // targetCaller + }, + 0, + Array.from(wormholeSdk.tryNativeToUint8Array(ETHEREUM_USDC_ADDRESS, "ethereum")), // sourceTokenAddress + Array.from(engine.cctpMintRecipientAddress().toBuffer()), // mint recipient + amount, + new Array(32).fill(0), // burnSource + ); + + const encodedCctpMessage = burnMessage.encode(); + const cctpAttestation = new CircleAttester().createAttestation(encodedCctpMessage); + + return { + destinationCctpDomain, + burnMessage, + encodedCctpMessage, + cctpAttestation, + }; + } +}); diff --git a/solana/ts/tests/04__interaction.ts b/solana/ts/tests/04__interaction.ts index b47594429..82156b0b2 100644 --- a/solana/ts/tests/04__interaction.ts +++ b/solana/ts/tests/04__interaction.ts @@ -34,6 +34,7 @@ import { bigintToU64BN, expectIxErr, expectIxOk, + expectIxOkDetails, postLiquidityLayerVaa, waitUntilSlot, } from "./helpers"; @@ -286,7 +287,7 @@ describe("Matching Engine <> Token Router", function () { let wormholeSequence = 4000n; - describe("Settle Auction", function () { + describe.skip("Settle Auction", function () { describe("Settle No Auction (Local)", function () { it("Settle", async function () { const { prepareIx, auction, fastVaa, finalizedVaa } = @@ -400,12 +401,9 @@ describe("Matching Engine <> Token Router", function () { "Error Code: AuctionPeriodNotExpired", ); }); + it("Execute after Auction Period has Expired", async function () { - const { - prepareIx, - auction: auctionAddress, - fastVaa, - } = await prepareOrderResponse({ + const { fastMarketOrder, auction, fastVaa } = await prepareOrderResponse({ initAuction: true, executeOrder: false, prepareOrderResponse: false, @@ -418,31 +416,43 @@ describe("Matching Engine <> Token Router", function () { liquidator.publicKey, ); - const auction = await matchingEngine.fetchAuction({ address: auctionAddress }); + const { info } = await matchingEngine.fetchAuction({ address: auction }); const { duration, gracePeriod } = await matchingEngine.fetchAuctionParameters(); await waitUntilSlot( connection, - auction.info!.startSlot.addn(duration + gracePeriod - 1).toNumber(), + info!.startSlot.addn(duration + gracePeriod - 1).toNumber(), ); - const settleIx = await matchingEngine.executeFastOrderLocalIx({ + const auctionCustodyTokenBalanceBefore = + await matchingEngine.fetchAuctionCustodyTokenBalance(auction); + const localCustodyTokenBalanceBefore = + await matchingEngine.fetchLocalCustodyTokenBalance(foreignChain); + expect(localCustodyTokenBalanceBefore).equals(0n); + + const ix = await matchingEngine.executeFastOrderLocalIx({ payer: payer.publicKey, fastVaa, - auction: auctionAddress, + auction, executorToken, }); - const { value: lookupTableAccount } = await connection.getAddressLookupTable( - lookupTableAddress, - ); + const txDetails = await expectIxOkDetails(connection, [ix], [payer]); - const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ - units: 400_000, - }); - await expectIxOk(connection, [prepareIx!, settleIx, computeIx], [payer], { - addressLookupTableAccounts: [lookupTableAccount!], - }); + const auctionCustodyTokenBalanceAfter = + await matchingEngine.fetchAuctionCustodyTokenBalance(auction); + expect(auctionCustodyTokenBalanceAfter).equals(0n); + const localCustodyTokenBalanceAfter = + await matchingEngine.fetchLocalCustodyTokenBalance(foreignChain); + + const { penalty, userReward } = await matchingEngine.computeDepositPenalty( + info!, + BigInt(txDetails!.slot), + info!.configId, + ); + const { amountIn, maxFee: offerPrice, initAuctionFee } = fastMarketOrder; + const userAmount = amountIn - offerPrice - initAuctionFee + userReward; + expect(localCustodyTokenBalanceAfter).equals(userAmount); }); before("Update Local Router Endpoint", async function () { @@ -754,10 +764,32 @@ describe("Matching Engine <> Token Router", function () { "solana", ); - const ix = await tokenRouter.redeemFastFillIx({ - payer: payer.publicKey, - vaa, - }); + const { + custodian, + preparedFill, + matchingEngineCustodian, + matchingEngineRedeemedFastFill, + matchingEngineRouterEndpoint, + matchingEngineLocalCustodyToken, + matchingEngineProgram, + } = await tokenRouter.redeemFastFillAccounts(vaa, foreignChain); + + const ix = await tokenRouter.program.methods + .redeemFastFill() + .accounts({ + payer: payer.publicKey, + custodian, + vaa, + preparedFill, + preparedCustodyToken: tokenRouter.preparedCustodyTokenAddress(preparedFill), + mint: tokenRouter.mint, + matchingEngineCustodian, + matchingEngineRedeemedFastFill, + matchingEngineRouterEndpoint, + matchingEngineLocalCustodyToken, + matchingEngineProgram, + }) + .instruction(); await expectIxErr(connection, [ix], [payer], "Error Code: InvalidVaa"); }); @@ -795,10 +827,33 @@ describe("Matching Engine <> Token Router", function () { message, "solana", ); - const ix = await tokenRouter.redeemFastFillIx({ - payer: payer.publicKey, - vaa, - }); + + const { + custodian, + preparedFill, + matchingEngineCustodian, + matchingEngineRedeemedFastFill, + matchingEngineRouterEndpoint, + matchingEngineLocalCustodyToken, + matchingEngineProgram, + } = await tokenRouter.redeemFastFillAccounts(vaa, foreignChain); + + const ix = await tokenRouter.program.methods + .redeemFastFill() + .accounts({ + payer: payer.publicKey, + custodian, + vaa, + preparedFill, + preparedCustodyToken: tokenRouter.preparedCustodyTokenAddress(preparedFill), + mint: tokenRouter.mint, + matchingEngineCustodian, + matchingEngineRedeemedFastFill, + matchingEngineRouterEndpoint, + matchingEngineLocalCustodyToken, + matchingEngineProgram, + }) + .instruction(); await expectIxErr(connection, [ix], [payer], "Error Code: InvalidPayloadId"); }); @@ -828,19 +883,20 @@ describe("Matching Engine <> Token Router", function () { const maxFee = 42069n; const currTime = await connection.getBlockTime(await connection.getSlot()); + const fastMarketOrder = { + amountIn, + minAmountOut: 0n, + targetChain: wormholeSdk.CHAINS.solana, + redeemer: Array.from(redeemer.publicKey.toBuffer()), + sender: new Array(32).fill(0), + refundAddress: new Array(32).fill(0), + maxFee, + initAuctionFee: 2000n, + deadline: currTime! + 2, + redeemerMessage: Buffer.from("Somebody set up us the bomb"), + }; const fastMessage = new LiquidityLayerMessage({ - fastMarketOrder: { - amountIn, - minAmountOut: 0n, - targetChain: wormholeSdk.CHAINS.solana, - redeemer: Array.from(redeemer.publicKey.toBuffer()), - sender: new Array(32).fill(0), - refundAddress: new Array(32).fill(0), - maxFee, - initAuctionFee: 2000n, - deadline: currTime! + 2, - redeemerMessage: Buffer.from("Somebody set up us the bomb"), - }, + fastMarketOrder, }); const finalizedMessage = new LiquidityLayerMessage({ @@ -898,10 +954,7 @@ describe("Matching Engine <> Token Router", function () { const fastVaaHash = fastVaaAccount.digest(); const preparedBy = payer.publicKey; - const preparedOrderResponse = matchingEngine.preparedOrderResponseAddress( - preparedBy, - fastVaaHash, - ); + const preparedOrderResponse = matchingEngine.preparedOrderResponseAddress(fastVaaHash); const auction = matchingEngine.auctionAddress(fastVaaHash); if (initAuction) { @@ -921,13 +974,18 @@ describe("Matching Engine <> Token Router", function () { } const { configId, bestOfferToken, initialOfferToken, startSlot } = info; const auctionConfig = matchingEngine.auctionConfigAddress(configId); - const duration = (await matchingEngine.fetchAuctionConfig(configId)).parameters - .duration; + const { duration, gracePeriod } = await matchingEngine.fetchAuctionParameters( + configId, + ); - await new Promise((f) => setTimeout(f, startSlot.toNumber() + duration + 200)); + await waitUntilSlot( + connection, + startSlot.toNumber() + duration + gracePeriod - 1, + ); + //await new Promise((f) => setTimeout(f, startSlot.toNumber() + duration + 200)); const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ - units: 400_000, + units: 300_000, }); const ix = await matchingEngine.executeFastOrderCctpIx({ payer: payer.publicKey, @@ -943,12 +1001,19 @@ describe("Matching Engine <> Token Router", function () { if (prepareOrderResponse) { const computeIx = ComputeBudgetProgram.setComputeUnitLimit({ - units: 400_000, + units: 300_000, + }); + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, + ); + await expectIxOk(connection, [computeIx, prepareIx], [payer], { + addressLookupTableAccounts: [lookupTableAccount!], }); - await expectIxOk(connection, [computeIx, prepareIx], [payer]); } return { + fastMessage, + fastMarketOrder, fastVaa, fastVaaAccount, finalizedVaa, diff --git a/solana/ts/tests/12__testnetFork.ts b/solana/ts/tests/12__testnetFork.ts index f2ed179e4..62716d9ed 100644 --- a/solana/ts/tests/12__testnetFork.ts +++ b/solana/ts/tests/12__testnetFork.ts @@ -33,7 +33,8 @@ const MATCHING_ENGINE_ARTIFACT_PATH = `${__dirname}/artifacts/testnet_matching_e const TOKEN_ROUTER_ARTIFACT_PATH = `${__dirname}/artifacts/testnet_token_router.so`; /// FOR NOW ONLY PERFORM THESE TESTS IF YOU HAVE THE MAGIC PRIVATE KEY. -if (process.env.MAGIC_PRIVATE_KEY !== undefined) { +if (false) { + //process.env.MAGIC_PRIVATE_KEY !== undefined) { const devnetOwner = Keypair.fromSecretKey( Buffer.from(process.env.MAGIC_PRIVATE_KEY!, "base64"), ); diff --git a/solana/ts/tests/helpers/consts.ts b/solana/ts/tests/helpers/consts.ts index d3da3f8b6..94e1ec613 100644 --- a/solana/ts/tests/helpers/consts.ts +++ b/solana/ts/tests/helpers/consts.ts @@ -1,5 +1,5 @@ import { PublicKey, Keypair } from "@solana/web3.js"; -import { CONTRACTS } from "@certusone/wormhole-sdk"; +import { CONTRACTS, type ChainName } from "@certusone/wormhole-sdk"; import { MockGuardians } from "@certusone/wormhole-sdk/lib/cjs/mock"; export const WORMHOLE_CONTRACTS = CONTRACTS.TESTNET; @@ -38,3 +38,23 @@ export const MOCK_GUARDIANS = new MockGuardians(0, [GUARDIAN_KEY]); export const USDC_MINT_ADDRESS = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); export const ETHEREUM_USDC_ADDRESS = "0x07865c6e87b9f70255377e024ace6630c1eaa37f"; + +export const CHAIN_TO_DOMAIN: Partial<{ [k in ChainName]: number }> = { + ethereum: 0, + avalanche: 1, + optimism: 2, + arbitrum: 3, + // noble: 4, + solana: 5, + base: 6, + polygon: 7, +}; + +export const REGISTERED_TOKEN_ROUTERS: Partial<{ [k in ChainName]: Array }> = { + ethereum: Array.from(Buffer.alloc(32, "f0", "hex")), + avalanche: Array.from(Buffer.alloc(32, "a1", "hex")), + optimism: Array.from(Buffer.alloc(32, "f2", "hex")), + arbitrum: Array.from(Buffer.alloc(32, "f3", "hex")), + base: Array.from(Buffer.alloc(32, "f6", "hex")), + polygon: Array.from(Buffer.alloc(32, "f7", "hex")), +}; diff --git a/solana/ts/tests/helpers/mock.ts b/solana/ts/tests/helpers/mock.ts index ddf7a7051..78a898e9c 100644 --- a/solana/ts/tests/helpers/mock.ts +++ b/solana/ts/tests/helpers/mock.ts @@ -15,19 +15,19 @@ export async function postLiquidityLayerVaa( foreignEmitterAddress: Array, sequence: bigint, message: LiquidityLayerMessage | Buffer, - chainName?: ChainName + sourceChain?: ChainName, ) { const foreignEmitter = new MockEmitter( Buffer.from(foreignEmitterAddress).toString("hex"), - coalesceChainId(chainName ?? "ethereum"), - Number(sequence - 1n) + coalesceChainId(sourceChain ?? "ethereum"), + Number(sequence - 1n), ); const published = foreignEmitter.publishMessage( 0, // nonce, Buffer.isBuffer(message) ? message : message.encode(), 0, // consistencyLevel - 12345678 // timestamp + 12345678, // timestamp ); const vaaBuf = guardians.addSignatures(published, [0]); diff --git a/solana/ts/tests/helpers/utils.ts b/solana/ts/tests/helpers/utils.ts index 0ee8bd9f8..0eac5f0d9 100644 --- a/solana/ts/tests/helpers/utils.ts +++ b/solana/ts/tests/helpers/utils.ts @@ -226,9 +226,8 @@ export async function waitUntilSlot(connection: Connection, targetSlot: number) } export async function getUsdcAtaBalance(connection: Connection, owner: PublicKey) { - const { amount } = await splToken.getAccount( - connection, - splToken.getAssociatedTokenAddressSync(USDC_MINT_ADDRESS, owner), - ); - return amount; + return splToken + .getAccount(connection, splToken.getAssociatedTokenAddressSync(USDC_MINT_ADDRESS, owner)) + .then((token) => token.amount) + .catch(() => 0n); }