From 8b77a73e831c783cb845224ab41df4642a443958 Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Wed, 3 Apr 2024 17:32:10 -0500 Subject: [PATCH 1/8] solana: fix ref --- .../src/processor/auction/execute_fast_order/cctp.rs | 5 +++-- .../src/processor/auction/settle/none/cctp.rs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) 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 36329c893..ad526b90e 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 @@ -92,6 +92,7 @@ pub fn handle_execute_fast_order_cctp( let auction_custody_token = &ctx.accounts.execute_order.active_auction.custody_token; let payer = &ctx.accounts.payer; + let system_program = &ctx.accounts.system_program; // Send the CCTP message to the destination chain. wormhole_cctp_solana::cpi::burn_and_publish( @@ -136,7 +137,7 @@ pub fn handle_execute_fast_order_cctp( .token_messenger_minter_program .to_account_info(), token_program: token_program.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), + system_program: system_program.to_account_info(), event_authority: ctx .accounts .cctp @@ -162,7 +163,7 @@ pub fn handle_execute_fast_order_cctp( 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(), + system_program: system_program.to_account_info(), clock: ctx.accounts.sysvars.clock.to_account_info(), rent: ctx.accounts.sysvars.rent.to_account_info(), }, 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 91c0af6e6..27560c1be 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 @@ -138,6 +138,7 @@ fn handle_settle_auction_none_cctp( } = ctx.accounts.fast_order_path.to_endpoint.as_ref(); let payer = &ctx.accounts.payer; + let system_program = &ctx.accounts.system_program; // This returns the CCTP nonce, but we do not need it. wormhole_cctp_solana::cpi::burn_and_publish( @@ -177,7 +178,7 @@ fn handle_settle_auction_none_cctp( .token_messenger_minter_program .to_account_info(), token_program: token_program.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), + system_program: system_program.to_account_info(), event_authority: ctx .accounts .cctp @@ -203,7 +204,7 @@ fn handle_settle_auction_none_cctp( 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(), + system_program: system_program.to_account_info(), clock: ctx.accounts.sysvars.clock.to_account_info(), rent: ctx.accounts.sysvars.rent.to_account_info(), }, From 2ce866ccd0b9426122d0bcd66bebed72859837cd Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Wed, 3 Apr 2024 14:51:19 -0500 Subject: [PATCH 2/8] solana: it works --- solana/modules/common/src/burn_and_publish.rs | 4 +- solana/modules/common/src/constants/mod.rs | 39 ---- solana/modules/common/src/lib.rs | 41 +++- solana/package-lock.json | 4 +- solana/package.json | 1 + .../src/cctp_mint_recipient.rs | 2 +- .../matching-engine/src/composite/mod.rs | 8 +- solana/programs/matching-engine/src/lib.rs | 3 +- .../src/processor/admin/initialize.rs | 4 +- .../admin/update/fee_recipient_token.rs | 2 +- .../auction/execute_fast_order/cctp.rs | 10 +- .../auction/execute_fast_order/local.rs | 2 +- .../src/processor/auction/offer/improve.rs | 5 +- .../processor/auction/offer/place_initial.rs | 4 +- .../src/processor/auction/settle/none/cctp.rs | 10 +- .../processor/auction/settle/none/local.rs | 2 +- .../matching-engine/src/utils/wormhole.rs | 4 +- .../token-router/src/cctp_mint_recipient.rs | 2 +- .../token-router/src/composite/mod.rs | 4 +- solana/programs/token-router/src/error.rs | 1 + solana/programs/token-router/src/lib.rs | 2 +- .../src/processor/admin/initialize.rs | 6 +- .../src/processor/market_order/place_cctp.rs | 99 ++++---- .../src/processor/market_order/prepare.rs | 112 +++++---- .../upgrade-manager/src/composite/mod.rs | 2 +- solana/programs/upgrade-manager/src/lib.rs | 4 +- solana/target/idl/token_router.json | 66 +++--- solana/target/types/token_router.ts | 132 ++++++----- solana/ts/src/matchingEngine/index.ts | 2 +- solana/ts/src/tokenRouter/index.ts | 206 ++++++++++------- .../ts/src/tokenRouter/state/PreparedOrder.ts | 2 + solana/ts/tests/02__tokenRouter.ts | 212 ++++++------------ 32 files changed, 506 insertions(+), 491 deletions(-) delete mode 100644 solana/modules/common/src/constants/mod.rs diff --git a/solana/modules/common/src/burn_and_publish.rs b/solana/modules/common/src/burn_and_publish.rs index 74ec46402..c260300de 100644 --- a/solana/modules/common/src/burn_and_publish.rs +++ b/solana/modules/common/src/burn_and_publish.rs @@ -62,7 +62,7 @@ pub fn burn_and_publish_fill( &[ Custodian::SIGNER_SEEDS, &[ - common::constants::CORE_MESSAGE_SEED_PREFIX, + common::CORE_MESSAGE_SEED_PREFIX, ctx.accounts.payer.key().as_ref(), ctx.accounts .payer_sequence @@ -79,7 +79,7 @@ pub fn burn_and_publish_fill( destination_cctp_domain: order.destination_cctp_domain(), amount: user_amount, mint_recipient: ctx.accounts.to_router_endpoint.mint_recipient, - wormhole_message_nonce: common::constants::WORMHOLE_MESSAGE_NONCE, + wormhole_message_nonce: common::WORMHOLE_MESSAGE_NONCE, payload: fill.to_vec_payload(), }, ) diff --git a/solana/modules/common/src/constants/mod.rs b/solana/modules/common/src/constants/mod.rs deleted file mode 100644 index 5717e5435..000000000 --- a/solana/modules/common/src/constants/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -pub const WORMHOLE_MESSAGE_NONCE: u32 = 0; - -/// Seed for custody token account. -pub const CUSTODY_TOKEN_SEED_PREFIX: &[u8] = b"custody"; - -pub const CORE_MESSAGE_SEED_PREFIX: &[u8] = b"core-msg"; -pub const CCTP_MESSAGE_SEED_PREFIX: &[u8] = b"cctp-msg"; - -pub use wormhole_solana_consts::USDC_MINT; - -use solana_program::{pubkey, pubkey::Pubkey}; - -cfg_if::cfg_if! { - if #[cfg(feature = "testnet")] { - pub const MATCHING_ENGINE_PROGRAM_ID: Pubkey = pubkey!("mPydpGUWxzERTNpyvTKdvS7v8kvw5sgwfiP8WQFrXVS"); - pub const TOKEN_ROUTER_PROGRAM_ID: Pubkey = pubkey!("tD8RmtdcV7bzBeuFgyrFc8wvayj988ChccEzRQzo6md"); - - pub const UPGRADE_MANAGER_PROGRAM_ID: Pubkey = pubkey!("ucdP9ktgrXgEUnn6roqD2SfdGMR2JSiWHUKv23oXwxt"); - pub const UPGRADE_MANAGER_AUTHORITY: Pubkey = pubkey!("2sxpm9pvWmNWFzhgWtmxkMsdWk2uSNT9MoKvww53po1M"); - } else if #[cfg(feature = "localnet")] { - pub const MATCHING_ENGINE_PROGRAM_ID: Pubkey = pubkey!("MatchingEngine11111111111111111111111111111"); - pub const TOKEN_ROUTER_PROGRAM_ID: Pubkey = pubkey!("TokenRouter11111111111111111111111111111111"); - - pub const UPGRADE_MANAGER_PROGRAM_ID: Pubkey = pubkey!("UpgradeManager11111111111111111111111111111"); - pub const UPGRADE_MANAGER_AUTHORITY: Pubkey = pubkey!("9Nu3k9HKFChDcAC8SeCrCeHvsRcdZzZfdQxGaEynFHZ7"); - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn upgrade_manager_authority() { - let (expected, _) = - Pubkey::find_program_address(&[b"upgrade"], &UPGRADE_MANAGER_PROGRAM_ID); - assert_eq!(UPGRADE_MANAGER_AUTHORITY, expected); - } -} diff --git a/solana/modules/common/src/lib.rs b/solana/modules/common/src/lib.rs index 161a94dbd..a6765022a 100644 --- a/solana/modules/common/src/lib.rs +++ b/solana/modules/common/src/lib.rs @@ -3,6 +3,43 @@ pub use wormhole_io; pub mod admin; -pub mod constants; - pub mod messages; + +pub const WORMHOLE_MESSAGE_NONCE: u32 = 0; + +pub const CORE_MESSAGE_SEED_PREFIX: &[u8] = b"core-msg"; +pub const CCTP_MESSAGE_SEED_PREFIX: &[u8] = b"cctp-msg"; + +pub const TRANSFER_AUTHORITY_SEED_PREFIX: &[u8] = b"transfer-authority"; + +pub use wormhole_solana_consts::USDC_MINT; + +use solana_program::{pubkey, pubkey::Pubkey}; + +cfg_if::cfg_if! { + if #[cfg(feature = "testnet")] { + pub const MATCHING_ENGINE_PROGRAM_ID: Pubkey = pubkey!("mPydpGUWxzERTNpyvTKdvS7v8kvw5sgwfiP8WQFrXVS"); + pub const TOKEN_ROUTER_PROGRAM_ID: Pubkey = pubkey!("tD8RmtdcV7bzBeuFgyrFc8wvayj988ChccEzRQzo6md"); + + pub const UPGRADE_MANAGER_PROGRAM_ID: Pubkey = pubkey!("ucdP9ktgrXgEUnn6roqD2SfdGMR2JSiWHUKv23oXwxt"); + pub const UPGRADE_MANAGER_AUTHORITY: Pubkey = pubkey!("2sxpm9pvWmNWFzhgWtmxkMsdWk2uSNT9MoKvww53po1M"); + } else if #[cfg(feature = "localnet")] { + pub const MATCHING_ENGINE_PROGRAM_ID: Pubkey = pubkey!("MatchingEngine11111111111111111111111111111"); + pub const TOKEN_ROUTER_PROGRAM_ID: Pubkey = pubkey!("TokenRouter11111111111111111111111111111111"); + + pub const UPGRADE_MANAGER_PROGRAM_ID: Pubkey = pubkey!("UpgradeManager11111111111111111111111111111"); + pub const UPGRADE_MANAGER_AUTHORITY: Pubkey = pubkey!("9Nu3k9HKFChDcAC8SeCrCeHvsRcdZzZfdQxGaEynFHZ7"); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn upgrade_manager_authority() { + let (expected, _) = + Pubkey::find_program_address(&[b"upgrade"], &UPGRADE_MANAGER_PROGRAM_ID); + assert_eq!(UPGRADE_MANAGER_AUTHORITY, expected); + } +} diff --git a/solana/package-lock.json b/solana/package-lock.json index af2143449..57aaee93d 100644 --- a/solana/package-lock.json +++ b/solana/package-lock.json @@ -11,6 +11,7 @@ "@solana/web3.js": "^1.87.6", "dotenv": "^16.4.1", "ethers": "^5.7.2", + "sha3": "^2.1.4", "yargs": "^17.7.2" }, "devDependencies": { @@ -4805,7 +4806,8 @@ }, "node_modules/sha3": { "version": "2.1.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", "dependencies": { "buffer": "6.0.3" } diff --git a/solana/package.json b/solana/package.json index 4e1a1e8ce..57ce22ffc 100644 --- a/solana/package.json +++ b/solana/package.json @@ -14,6 +14,7 @@ "@solana/web3.js": "^1.87.6", "dotenv": "^16.4.1", "ethers": "^5.7.2", + "sha3": "^2.1.4", "yargs": "^17.7.2" }, "devDependencies": { diff --git a/solana/programs/matching-engine/src/cctp_mint_recipient.rs b/solana/programs/matching-engine/src/cctp_mint_recipient.rs index 2887789a5..9d59ef1d2 100644 --- a/solana/programs/matching-engine/src/cctp_mint_recipient.rs +++ b/solana/programs/matching-engine/src/cctp_mint_recipient.rs @@ -21,7 +21,7 @@ mod test { super::id(), anchor_spl::associated_token::get_associated_token_address( &custodian, - &common::constants::USDC_MINT, + &common::USDC_MINT, ), "custody ata mismatch" ); diff --git a/solana/programs/matching-engine/src/composite/mod.rs b/solana/programs/matching-engine/src/composite/mod.rs index 546a29c17..2cad6f4f1 100644 --- a/solana/programs/matching-engine/src/composite/mod.rs +++ b/solana/programs/matching-engine/src/composite/mod.rs @@ -20,8 +20,8 @@ use common::{ #[derive(Accounts)] pub struct Usdc<'info> { - /// CHECK: This address must equal [USDC_MINT](common::constants::USDC_MINT). - #[account(address = common::constants::USDC_MINT)] + /// CHECK: This address must equal [USDC_MINT](common::USDC_MINT). + #[account(address = common::USDC_MINT)] pub mint: AccountInfo<'info>, } @@ -190,7 +190,7 @@ pub struct LocalTokenRouter<'info> { pub token_router_emitter: AccountInfo<'info>, #[account( - associated_token::mint = common::constants::USDC_MINT, + associated_token::mint = common::USDC_MINT, associated_token::authority = token_router_emitter, )] pub token_router_mint_recipient: Account<'info, token::TokenAccount>, @@ -370,7 +370,7 @@ pub struct ExecuteOrder<'info> { pub active_auction: ActiveAuction<'info>, - /// CHECK: Must be a token account, whose mint is [common::constants::USDC_MINT]. + /// CHECK: Must be a token account, whose mint is [common::USDC_MINT]. #[account(mut)] pub executor_token: AccountInfo<'info>, diff --git a/solana/programs/matching-engine/src/lib.rs b/solana/programs/matching-engine/src/lib.rs index 32e0a54b9..5f52fb422 100644 --- a/solana/programs/matching-engine/src/lib.rs +++ b/solana/programs/matching-engine/src/lib.rs @@ -19,7 +19,7 @@ pub use utils::admin::AddCctpRouterEndpointArgs; use anchor_lang::prelude::*; -declare_id!(common::constants::MATCHING_ENGINE_PROGRAM_ID); +declare_id!(common::MATCHING_ENGINE_PROGRAM_ID); cfg_if::cfg_if! { if #[cfg(feature = "testnet")] { @@ -32,7 +32,6 @@ 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"; -const TRANSFER_AUTHORITY_SEED_PREFIX: &[u8] = b"transfer-authority"; const FEE_PRECISION_MAX: u32 = 1_000_000; const VAA_AUCTION_EXPIRATION_TIME: i64 = 2 * 60 * 60; // 2 hours diff --git a/solana/programs/matching-engine/src/processor/admin/initialize.rs b/solana/programs/matching-engine/src/processor/admin/initialize.rs index e0db87851..fd8b4246f 100644 --- a/solana/programs/matching-engine/src/processor/admin/initialize.rs +++ b/solana/programs/matching-engine/src/processor/admin/initialize.rs @@ -89,13 +89,13 @@ pub struct Initialize<'info> { program_data: Account<'info, ProgramData>, /// CHECK: This program PDA will be the upgrade authority for the Token Router program. - #[account(address = common::constants::UPGRADE_MANAGER_AUTHORITY)] + #[account(address = common::UPGRADE_MANAGER_AUTHORITY)] upgrade_manager_authority: AccountInfo<'info>, /// CHECK: This program must exist. #[account( executable, - address = common::constants::UPGRADE_MANAGER_PROGRAM_ID, + address = common::UPGRADE_MANAGER_PROGRAM_ID, )] upgrade_manager_program: AccountInfo<'info>, 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 68a87a340..458dbe9dc 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 @@ -7,7 +7,7 @@ pub struct UpdateFeeRecipient<'info> { admin: AdminMut<'info>, #[account( - associated_token::mint = common::constants::USDC_MINT, + associated_token::mint = common::USDC_MINT, associated_token::authority = new_fee_recipient, )] new_fee_recipient_token: Account<'info, token::TokenAccount>, 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 ad526b90e..a263f3425 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 @@ -29,7 +29,7 @@ pub struct ExecuteFastOrderCctp<'info> { #[account( mut, seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, + common::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), &payer_sequence.value.to_be_bytes(), ], @@ -41,7 +41,7 @@ pub struct ExecuteFastOrderCctp<'info> { #[account( mut, seeds = [ - common::constants::CCTP_MESSAGE_SEED_PREFIX, + common::CCTP_MESSAGE_SEED_PREFIX, payer.key().as_ref(), &payer_sequence.value.to_be_bytes(), ], @@ -147,7 +147,7 @@ pub fn handle_execute_fast_order_cctp( &[ Custodian::SIGNER_SEEDS, &[ - common::constants::CCTP_MESSAGE_SEED_PREFIX, + common::CCTP_MESSAGE_SEED_PREFIX, payer.key().as_ref(), sequence_seed.as_ref(), &[ctx.bumps.cctp_message], @@ -170,7 +170,7 @@ pub fn handle_execute_fast_order_cctp( &[ Custodian::SIGNER_SEEDS, &[ - common::constants::CORE_MESSAGE_SEED_PREFIX, + common::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), sequence_seed.as_ref(), &[ctx.bumps.core_message], @@ -183,7 +183,7 @@ pub fn handle_execute_fast_order_cctp( destination_cctp_domain, amount, mint_recipient: ctx.accounts.to_router_endpoint.mint_recipient, - wormhole_message_nonce: common::constants::WORMHOLE_MESSAGE_NONCE, + wormhole_message_nonce: common::WORMHOLE_MESSAGE_NONCE, payload: fill.to_vec_payload(), }, )?; 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 9e9148531..34252b251 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 @@ -27,7 +27,7 @@ pub struct ExecuteFastOrderLocal<'info> { #[account( mut, seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, + common::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), &payer_sequence.value.to_be_bytes(), ], 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 4cbbbaf4c..e5dab5817 100644 --- a/solana/programs/matching-engine/src/processor/auction/offer/improve.rs +++ b/solana/programs/matching-engine/src/processor/auction/offer/improve.rs @@ -1,8 +1,7 @@ -use crate::{ - composite::*, error::MatchingEngineError, state::Auction, utils, TRANSFER_AUTHORITY_SEED_PREFIX, -}; +use crate::{composite::*, error::MatchingEngineError, state::Auction, utils}; use anchor_lang::prelude::*; use anchor_spl::token; +use common::TRANSFER_AUTHORITY_SEED_PREFIX; #[derive(Accounts)] #[instruction(offer_price: u64)] 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 1441008d5..306ac9a4e 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 @@ -2,11 +2,11 @@ use crate::{ composite::*, error::MatchingEngineError, state::{Auction, AuctionConfig, AuctionInfo, AuctionStatus}, - utils, TRANSFER_AUTHORITY_SEED_PREFIX, + utils, }; use anchor_lang::prelude::*; use anchor_spl::token; -use common::messages::raw::LiquidityLayerMessage; +use common::{messages::raw::LiquidityLayerMessage, TRANSFER_AUTHORITY_SEED_PREFIX}; #[derive(Accounts)] #[instruction(offer_price: u64)] 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 27560c1be..60adec21f 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 @@ -30,7 +30,7 @@ pub struct SettleAuctionNoneCctp<'info> { #[account( mut, seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, + common::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), &payer_sequence.value.to_be_bytes(), ], @@ -42,7 +42,7 @@ pub struct SettleAuctionNoneCctp<'info> { #[account( mut, seeds = [ - common::constants::CCTP_MESSAGE_SEED_PREFIX, + common::CCTP_MESSAGE_SEED_PREFIX, payer.key().as_ref(), &payer_sequence.value.to_be_bytes(), ], @@ -188,7 +188,7 @@ fn handle_settle_auction_none_cctp( &[ Custodian::SIGNER_SEEDS, &[ - common::constants::CCTP_MESSAGE_SEED_PREFIX, + common::CCTP_MESSAGE_SEED_PREFIX, payer.key().as_ref(), sequence_seed.as_ref(), &[ctx.bumps.cctp_message], @@ -211,7 +211,7 @@ fn handle_settle_auction_none_cctp( &[ Custodian::SIGNER_SEEDS, &[ - common::constants::CORE_MESSAGE_SEED_PREFIX, + common::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), sequence_seed.as_ref(), &[ctx.bumps.core_message], @@ -224,7 +224,7 @@ fn handle_settle_auction_none_cctp( destination_cctp_domain, amount, mint_recipient: *mint_recipient, - wormhole_message_nonce: common::constants::WORMHOLE_MESSAGE_NONCE, + wormhole_message_nonce: common::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 f4a42b962..3b564f599 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 @@ -28,7 +28,7 @@ pub struct SettleAuctionNoneLocal<'info> { #[account( mut, seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, + common::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), payer_sequence.value.to_be_bytes().as_ref(), ], diff --git a/solana/programs/matching-engine/src/utils/wormhole.rs b/solana/programs/matching-engine/src/utils/wormhole.rs index 7ef214a66..abee47b7f 100644 --- a/solana/programs/matching-engine/src/utils/wormhole.rs +++ b/solana/programs/matching-engine/src/utils/wormhole.rs @@ -55,7 +55,7 @@ where &[ Custodian::SIGNER_SEEDS, &[ - common::constants::CORE_MESSAGE_SEED_PREFIX, + common::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), sequence_seed, &[core_message_bump_seed], @@ -63,7 +63,7 @@ where ], ), core_bridge_program::cpi::PostMessageArgs { - nonce: common::constants::WORMHOLE_MESSAGE_NONCE, + nonce: common::WORMHOLE_MESSAGE_NONCE, payload: message.to_vec_payload(), commitment: core_bridge_program::Commitment::Finalized, }, diff --git a/solana/programs/token-router/src/cctp_mint_recipient.rs b/solana/programs/token-router/src/cctp_mint_recipient.rs index c1ba86c54..54a8ad2ba 100644 --- a/solana/programs/token-router/src/cctp_mint_recipient.rs +++ b/solana/programs/token-router/src/cctp_mint_recipient.rs @@ -21,7 +21,7 @@ mod test { super::id(), anchor_spl::associated_token::get_associated_token_address( &custodian, - &common::constants::USDC_MINT + &common::USDC_MINT ), "cctp mint recipient mismatch" ); diff --git a/solana/programs/token-router/src/composite/mod.rs b/solana/programs/token-router/src/composite/mod.rs index 5f4ec0ff0..16238122e 100644 --- a/solana/programs/token-router/src/composite/mod.rs +++ b/solana/programs/token-router/src/composite/mod.rs @@ -14,8 +14,8 @@ use common::{ #[derive(Accounts)] pub struct Usdc<'info> { - /// CHECK: This address must equal [USDC_MINT](common::constants::USDC_MINT). - #[account(address = common::constants::USDC_MINT)] + /// CHECK: This address must equal [USDC_MINT](common::USDC_MINT). + #[account(address = common::USDC_MINT)] pub mint: AccountInfo<'info>, } diff --git a/solana/programs/token-router/src/error.rs b/solana/programs/token-router/src/error.rs index 28c3347d0..435d24c92 100644 --- a/solana/programs/token-router/src/error.rs +++ b/solana/programs/token-router/src/error.rs @@ -9,6 +9,7 @@ pub enum TokenRouterError { InvalidPayloadId = 0x46, InvalidSourceRouter = 0x60, + InvalidTargetRouter = 0x62, EndpointDisabled = 0x64, InvalidCctpEndpoint = 0x66, diff --git a/solana/programs/token-router/src/lib.rs b/solana/programs/token-router/src/lib.rs index 71befd507..7349c4e87 100644 --- a/solana/programs/token-router/src/lib.rs +++ b/solana/programs/token-router/src/lib.rs @@ -14,7 +14,7 @@ pub mod state; use anchor_lang::prelude::*; -declare_id!(common::constants::TOKEN_ROUTER_PROGRAM_ID); +declare_id!(common::TOKEN_ROUTER_PROGRAM_ID); cfg_if::cfg_if! { if #[cfg(feature = "testnet")] { diff --git a/solana/programs/token-router/src/processor/admin/initialize.rs b/solana/programs/token-router/src/processor/admin/initialize.rs index af40a6fe0..9092ceaa8 100644 --- a/solana/programs/token-router/src/processor/admin/initialize.rs +++ b/solana/programs/token-router/src/processor/admin/initialize.rs @@ -40,7 +40,7 @@ pub struct Initialize<'info> { )] cctp_mint_recipient: Account<'info, token::TokenAccount>, - #[account(address = common::constants::USDC_MINT)] + #[account(address = common::USDC_MINT)] mint: Account<'info, token::Mint>, /// We use the program data to make sure this owner is the upgrade authority (the true owner, @@ -57,13 +57,13 @@ pub struct Initialize<'info> { program_data: Account<'info, ProgramData>, /// CHECK: This program PDA will be the upgrade authority for the Token Router program. - #[account(address = common::constants::UPGRADE_MANAGER_AUTHORITY)] + #[account(address = common::UPGRADE_MANAGER_AUTHORITY)] upgrade_manager_authority: AccountInfo<'info>, /// CHECK: This program must exist. #[account( executable, - address = common::constants::UPGRADE_MANAGER_PROGRAM_ID, + address = common::UPGRADE_MANAGER_PROGRAM_ID, )] upgrade_manager_program: AccountInfo<'info>, diff --git a/solana/programs/token-router/src/processor/market_order/place_cctp.rs b/solana/programs/token-router/src/processor/market_order/place_cctp.rs index 7f5b4c2a6..50ea72306 100644 --- a/solana/programs/token-router/src/processor/market_order/place_cctp.rs +++ b/solana/programs/token-router/src/processor/market_order/place_cctp.rs @@ -1,4 +1,5 @@ use crate::{ + composite::*, error::TokenRouterError, state::{Custodian, PayerSequence, PreparedOrder}, }; @@ -17,11 +18,15 @@ use common::{ #[derive(Accounts)] pub struct PlaceMarketOrderCctp<'info> { /// This account must be the same pubkey as the one who prepared the order. + #[account(mut)] + payer: Signer<'info>, + + /// CHECK: This account must equal the prepared order's `prepared_by` pubkey. #[account( mut, - address = prepared_order.prepared_by @ TokenRouterError::PayerNotPreparer, + address = prepared_order.prepared_by )] - payer: Signer<'info>, + prepared_by: AccountInfo<'info>, #[account( init_if_needed, @@ -33,27 +38,19 @@ pub struct PlaceMarketOrderCctp<'info> { ], bump, )] - payer_sequence: Account<'info, PayerSequence>, + payer_sequence: Box>, /// This program's Wormhole (Core Bridge) emitter authority. /// /// Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - constraint = !custodian.paused @ TokenRouterError::Paused, - )] - custodian: Box>, + #[account(constraint = !custodian.paused @ TokenRouterError::Paused)] + custodian: CheckedCustodian<'info>, #[account( mut, - close = payer, - has_one = order_sender @ TokenRouterError::OrderSenderMismatch, + close = prepared_by, )] - prepared_order: Account<'info, PreparedOrder>, - - /// Signer who must be the same one encoded in the prepared order. - order_sender: Signer<'info>, + prepared_order: Box>, /// Circle-supported mint. /// @@ -82,9 +79,6 @@ pub struct PlaceMarketOrderCctp<'info> { /// [RouterEndpoint::cctp_domain] is `Some(value)`. /// /// Seeds must be \["registered_emitter", target_chain.to_be_bytes()\]. - /// - /// NOTE: In the EVM implementation, if there is no router endpoint then "ErrUnsupportedChain" - /// error is thrown (whereas here the account would not exist). #[account( seeds = [ matching_engine::state::RouterEndpoint::SEED_PREFIX, @@ -92,8 +86,17 @@ pub struct PlaceMarketOrderCctp<'info> { ], bump = router_endpoint.bump, seeds::program = matching_engine::id(), + constraint = { + require_eq!( + router_endpoint.chain, + prepared_order.target_chain, + TokenRouterError::InvalidTargetRouter, + ); + + true + } )] - router_endpoint: Account<'info, matching_engine::state::RouterEndpoint>, + router_endpoint: Box>, /// CHECK: Seeds must be \["Bridge"\] (Wormhole Core Bridge program). #[account(mut)] @@ -103,7 +106,7 @@ pub struct PlaceMarketOrderCctp<'info> { #[account( mut, seeds = [ - common::constants::CORE_MESSAGE_SEED_PREFIX, + common::CORE_MESSAGE_SEED_PREFIX, payer.key().as_ref(), payer_sequence.value.to_be_bytes().as_ref(), ], @@ -115,7 +118,7 @@ pub struct PlaceMarketOrderCctp<'info> { #[account( mut, seeds = [ - common::constants::CCTP_MESSAGE_SEED_PREFIX, + common::CCTP_MESSAGE_SEED_PREFIX, payer.key().as_ref(), payer_sequence.value.to_be_bytes().as_ref(), ], @@ -192,6 +195,14 @@ fn handle_place_market_order_cctp( ) -> Result<()> { let redeemer_message = std::mem::take(&mut ctx.accounts.prepared_order.redeemer_message); + let custodian = &ctx.accounts.custodian; + let payer = &ctx.accounts.payer; + let prepared_custody_token = &ctx.accounts.prepared_custody_token; + let token_program = &ctx.accounts.token_program; + let system_program = &ctx.accounts.system_program; + let router_endpoint = &ctx.accounts.router_endpoint; + + let order_info = &ctx.accounts.prepared_order.info; let sequence_seed = ctx.accounts.payer_sequence.take_and_uptick().to_be_bytes(); // This returns the CCTP nonce, but we do not need it. @@ -201,13 +212,13 @@ fn handle_place_market_order_cctp( .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(), + burn_token_owner: custodian.to_account_info(), + payer: payer.to_account_info(), token_messenger_minter_sender_authority: ctx .accounts .token_messenger_minter_sender_authority .to_account_info(), - burn_token: ctx.accounts.prepared_custody_token.to_account_info(), + burn_token: prepared_custody_token.to_account_info(), message_transmitter_config: ctx .accounts .message_transmitter_config @@ -226,8 +237,8 @@ fn handle_place_market_order_cctp( .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(), + token_program: token_program.to_account_info(), + system_program: system_program.to_account_info(), event_authority: ctx .accounts .token_messenger_minter_event_authority @@ -236,8 +247,8 @@ fn handle_place_market_order_cctp( &[ Custodian::SIGNER_SEEDS, &[ - common::constants::CCTP_MESSAGE_SEED_PREFIX, - ctx.accounts.payer.key().as_ref(), + common::CCTP_MESSAGE_SEED_PREFIX, + payer.key().as_ref(), sequence_seed.as_ref(), &[ctx.bumps.cctp_message], ], @@ -246,37 +257,37 @@ fn handle_place_market_order_cctp( CpiContext::new_with_signer( ctx.accounts.core_bridge_program.to_account_info(), wormhole_cctp_solana::cpi::PostMessage { - payer: ctx.accounts.payer.to_account_info(), + payer: payer.to_account_info(), message: ctx.accounts.core_message.to_account_info(), - emitter: ctx.accounts.custodian.to_account_info(), + emitter: 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(), + system_program: 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(), + common::CORE_MESSAGE_SEED_PREFIX, + payer.key().as_ref(), sequence_seed.as_ref(), &[ctx.bumps.core_message], ], ], ), wormhole_cctp_solana::cpi::BurnAndPublishArgs { - burn_source: Some(ctx.accounts.prepared_order.src_token), - destination_caller: ctx.accounts.router_endpoint.address, + burn_source: Some(order_info.src_token), + destination_caller: router_endpoint.address, destination_cctp_domain, - amount: ctx.accounts.prepared_custody_token.amount, - mint_recipient: ctx.accounts.router_endpoint.mint_recipient, - wormhole_message_nonce: common::constants::WORMHOLE_MESSAGE_NONCE, + amount: prepared_custody_token.amount, + mint_recipient: router_endpoint.mint_recipient, + wormhole_message_nonce: common::WORMHOLE_MESSAGE_NONCE, payload: common::messages::Fill { source_chain: SOLANA_CHAIN, - order_sender: ctx.accounts.order_sender.key().to_bytes(), - redeemer: ctx.accounts.prepared_order.redeemer, + order_sender: order_info.order_sender.to_bytes(), + redeemer: order_info.redeemer, redeemer_message: redeemer_message.into(), } .to_vec_payload(), @@ -285,11 +296,11 @@ fn handle_place_market_order_cctp( // Finally close token account. token::close_account(CpiContext::new_with_signer( - ctx.accounts.token_program.to_account_info(), + token_program.to_account_info(), token::CloseAccount { - account: ctx.accounts.prepared_custody_token.to_account_info(), - destination: ctx.accounts.payer.to_account_info(), - authority: ctx.accounts.custodian.to_account_info(), + account: prepared_custody_token.to_account_info(), + destination: payer.to_account_info(), + authority: custodian.to_account_info(), }, &[Custodian::SIGNER_SEEDS], )) diff --git a/solana/programs/token-router/src/processor/market_order/prepare.rs b/solana/programs/token-router/src/processor/market_order/prepare.rs index a569f454e..5d78be39d 100644 --- a/solana/programs/token-router/src/processor/market_order/prepare.rs +++ b/solana/programs/token-router/src/processor/market_order/prepare.rs @@ -1,10 +1,12 @@ -use anchor_lang::prelude::*; -use anchor_spl::token; - use crate::{ + composite::*, error::TokenRouterError, - state::{Custodian, OrderType, PreparedOrder, PreparedOrderInfo}, + state::{OrderType, PreparedOrder, PreparedOrderInfo}, }; +use anchor_lang::prelude::*; +use anchor_spl::token; +use common::TRANSFER_AUTHORITY_SEED_PREFIX; +use solana_program::keccak; /// Accounts required for [prepare_market_order]. #[derive(Accounts)] @@ -13,23 +15,41 @@ pub struct PrepareMarketOrder<'info> { #[account(mut)] payer: Signer<'info>, - /// Custodian, but does not need to be deserialized. + custodian: CheckedCustodian<'info>, + + /// The auction participant needs to set approval to this PDA. /// - /// CHECK: Seeds must be \["emitter"\]. + /// CHECK: Seeds must be \["transfer-authority", prepared_order.key(), args.hash()\]. #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, + seeds = [ + TRANSFER_AUTHORITY_SEED_PREFIX, + prepared_order.key().as_ref(), + &args.hash().0, + ], + bump, )] - custodian: AccountInfo<'info>, - - /// This signer will be encoded in the prepared order. He will also need to be present when - /// invoking any of the place market order instructions. - order_sender: Signer<'info>, + transfer_authority: AccountInfo<'info>, #[account( init, payer = payer, - space = PreparedOrder::compute_size(args.redeemer_message.len()) + space = PreparedOrder::compute_size(args.redeemer_message.len()), + constraint = { + require!(args.amount_in > 0, TokenRouterError::InsufficientAmount); + + // Cannot send to zero address. + require!(args.redeemer != [0; 32], TokenRouterError::InvalidRedeemer); + + // If provided, validate min amount out. + if let Some(min_amount_out) = args.min_amount_out { + require!( + min_amount_out <= args.amount_in, + TokenRouterError::MinAmountOutTooHigh, + ); + } + + true + } )] prepared_order: Account<'info, PreparedOrder>, @@ -42,9 +62,13 @@ pub struct PrepareMarketOrder<'info> { /// NOTE: This token account must have delegated transfer authority to the custodian prior to /// invoking this instruction. #[account(mut)] - src_token: AccountInfo<'info>, + sender_token: Account<'info, token::TokenAccount>, - #[account(token::mint = mint)] + // TODO: Do we add a restriction that the refund token account must be the same owner as the + // sender token account? + #[account( + token::mint = usdc, + )] refund_token: Account<'info, token::TokenAccount>, /// Custody token account. This account will be closed at the end of this instruction. It just @@ -54,7 +78,7 @@ pub struct PrepareMarketOrder<'info> { #[account( init, payer = payer, - token::mint = mint, + token::mint = usdc, token::authority = custodian, seeds = [ crate::PREPARED_CUSTODY_TOKEN_SEED_PREFIX, @@ -64,9 +88,7 @@ pub struct PrepareMarketOrder<'info> { )] prepared_custody_token: Account<'info, token::TokenAccount>, - /// CHECK: This mint must be USDC. - #[account(address = common::constants::USDC_MINT)] - mint: AccountInfo<'info>, + usdc: Usdc<'info>, token_program: Program<'info, token::Token>, system_program: Program<'info, System>, @@ -93,10 +115,32 @@ pub struct PrepareMarketOrderArgs { pub redeemer_message: Vec, } +impl PrepareMarketOrderArgs { + pub fn hash(&self) -> keccak::Hash { + match self.min_amount_out { + Some(min_amount_out) => keccak::hashv(&[ + &self.amount_in.to_be_bytes(), + &min_amount_out.to_be_bytes(), + &self.target_chain.to_be_bytes(), + &self.redeemer, + &self.redeemer_message, + ]), + None => keccak::hashv(&[ + &self.amount_in.to_be_bytes(), + &self.target_chain.to_be_bytes(), + &self.redeemer, + &self.redeemer_message, + ]), + } + } +} + pub fn prepare_market_order( ctx: Context, args: PrepareMarketOrderArgs, ) -> Result<()> { + let hashed_args = args.hash(); + let PrepareMarketOrderArgs { amount_in, min_amount_out, @@ -105,26 +149,13 @@ pub fn prepare_market_order( redeemer_message, } = args; - require!(args.amount_in > 0, TokenRouterError::InsufficientAmount); - - // Cannot send to zero address. - require!(args.redeemer != [0; 32], TokenRouterError::InvalidRedeemer); - - // If provided, validate min amount out. - if let Some(min_amount_out) = min_amount_out { - require!( - min_amount_out <= amount_in, - TokenRouterError::MinAmountOutTooHigh, - ); - } - // Set the values in prepared order account. ctx.accounts.prepared_order.set_inner(PreparedOrder { info: PreparedOrderInfo { - order_sender: ctx.accounts.order_sender.key(), + order_sender: ctx.accounts.sender_token.owner, prepared_by: ctx.accounts.payer.key(), order_type: OrderType::Market { min_amount_out }, - src_token: ctx.accounts.src_token.key(), + src_token: ctx.accounts.sender_token.key(), refund_token: ctx.accounts.refund_token.key(), target_chain, redeemer, @@ -138,11 +169,16 @@ pub fn prepare_market_order( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), token::Transfer { - from: ctx.accounts.src_token.to_account_info(), + from: ctx.accounts.sender_token.to_account_info(), to: ctx.accounts.prepared_custody_token.to_account_info(), - authority: ctx.accounts.custodian.to_account_info(), + authority: ctx.accounts.transfer_authority.to_account_info(), }, - &[Custodian::SIGNER_SEEDS], + &[&[ + TRANSFER_AUTHORITY_SEED_PREFIX, + ctx.accounts.prepared_order.key().as_ref(), + &hashed_args.0, + &[ctx.bumps.transfer_authority], + ]], ), amount_in, ) diff --git a/solana/programs/upgrade-manager/src/composite/mod.rs b/solana/programs/upgrade-manager/src/composite/mod.rs index 75c4d5e18..ef070490d 100644 --- a/solana/programs/upgrade-manager/src/composite/mod.rs +++ b/solana/programs/upgrade-manager/src/composite/mod.rs @@ -14,7 +14,7 @@ pub struct ProgramOwnerOnly<'info> { /// CHECK: Upgrade authority for the liquidity layer program (either Token Router or Matching /// Engine). This address must equal the liquidity layer program data's upgrade authority. - #[account(address = common::constants::UPGRADE_MANAGER_AUTHORITY)] + #[account(address = common::UPGRADE_MANAGER_AUTHORITY)] pub upgrade_authority: AccountInfo<'info>, } diff --git a/solana/programs/upgrade-manager/src/lib.rs b/solana/programs/upgrade-manager/src/lib.rs index ceca44b59..0ef7fdf64 100644 --- a/solana/programs/upgrade-manager/src/lib.rs +++ b/solana/programs/upgrade-manager/src/lib.rs @@ -14,7 +14,7 @@ mod utils; use anchor_lang::prelude::*; -declare_id!(common::constants::UPGRADE_MANAGER_PROGRAM_ID); +declare_id!(common::UPGRADE_MANAGER_PROGRAM_ID); cfg_if::cfg_if! { if #[cfg(feature = "testnet")] { @@ -64,6 +64,6 @@ mod test { let (actual_addr, actual_bump_seed) = Pubkey::find_program_address(&[UPGRADE_AUTHORITY_SEED_PREFIX], &crate::id()); assert_eq!(actual_bump_seed, UPGRADE_AUTHORITY_BUMP); - assert_eq!(actual_addr, common::constants::UPGRADE_MANAGER_AUTHORITY); + assert_eq!(actual_addr, common::UPGRADE_MANAGER_AUTHORITY); } } diff --git a/solana/target/idl/token_router.json b/solana/target/idl/token_router.json index 5fecfcece..78ff46f03 100644 --- a/solana/target/idl/token_router.json +++ b/solana/target/idl/token_router.json @@ -306,20 +306,21 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "Custodian, but does not need to be deserialized.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { - "name": "orderSender", + "name": "transferAuthority", "isMut": false, - "isSigner": true, + "isSigner": false, "docs": [ - "This signer will be encoded in the prepared order. He will also need to be present when", - "invoking any of the place market order instructions." + "The auction participant needs to set approval to this PDA.", + "" ] }, { @@ -328,7 +329,7 @@ "isSigner": true }, { - "name": "srcToken", + "name": "senderToken", "isMut": true, "isSigner": false, "docs": [ @@ -357,9 +358,14 @@ ] }, { - "name": "mint", - "isMut": false, - "isSigner": false + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] }, { "name": "tokenProgram", @@ -464,6 +470,11 @@ "This account must be the same pubkey as the one who prepared the order." ] }, + { + "name": "preparedBy", + "isMut": true, + "isSigner": false + }, { "name": "payerSequence", "isMut": true, @@ -471,12 +482,12 @@ }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "", - "Seeds must be \\[\"emitter\"\\]." + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -484,14 +495,6 @@ "isMut": true, "isSigner": false }, - { - "name": "orderSender", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer who must be the same one encoded in the prepared order." - ] - }, { "name": "mint", "isMut": true, @@ -522,10 +525,7 @@ "CCTP domain encoded if this route is CCTP-enabled. For this instruction, it is required that", "[RouterEndpoint::cctp_domain] is `Some(value)`.", "", - "Seeds must be \\[\"registered_emitter\", target_chain.to_be_bytes()\\].", - "", - "NOTE: In the EVM implementation, if there is no router endpoint then \"ErrUnsupportedChain\"", - "error is thrown (whereas here the account would not exist)." + "Seeds must be \\[\"registered_emitter\", target_chain.to_be_bytes()\\]." ] }, { @@ -1362,6 +1362,10 @@ "code": 6096, "name": "InvalidSourceRouter" }, + { + "code": 6098, + "name": "InvalidTargetRouter" + }, { "code": 6100, "name": "EndpointDisabled" diff --git a/solana/target/types/token_router.ts b/solana/target/types/token_router.ts index 489f83fc0..c9c12ca9f 100644 --- a/solana/target/types/token_router.ts +++ b/solana/target/types/token_router.ts @@ -306,20 +306,21 @@ export type TokenRouter = { }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "Custodian, but does not need to be deserialized.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { - "name": "orderSender", + "name": "transferAuthority", "isMut": false, - "isSigner": true, + "isSigner": false, "docs": [ - "This signer will be encoded in the prepared order. He will also need to be present when", - "invoking any of the place market order instructions." + "The auction participant needs to set approval to this PDA.", + "" ] }, { @@ -328,7 +329,7 @@ export type TokenRouter = { "isSigner": true }, { - "name": "srcToken", + "name": "senderToken", "isMut": true, "isSigner": false, "docs": [ @@ -357,9 +358,14 @@ export type TokenRouter = { ] }, { - "name": "mint", - "isMut": false, - "isSigner": false + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] }, { "name": "tokenProgram", @@ -464,6 +470,11 @@ export type TokenRouter = { "This account must be the same pubkey as the one who prepared the order." ] }, + { + "name": "preparedBy", + "isMut": true, + "isSigner": false + }, { "name": "payerSequence", "isMut": true, @@ -471,12 +482,12 @@ export type TokenRouter = { }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "", - "Seeds must be \\[\"emitter\"\\]." + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -484,14 +495,6 @@ export type TokenRouter = { "isMut": true, "isSigner": false }, - { - "name": "orderSender", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer who must be the same one encoded in the prepared order." - ] - }, { "name": "mint", "isMut": true, @@ -522,10 +525,7 @@ export type TokenRouter = { "CCTP domain encoded if this route is CCTP-enabled. For this instruction, it is required that", "[RouterEndpoint::cctp_domain] is `Some(value)`.", "", - "Seeds must be \\[\"registered_emitter\", target_chain.to_be_bytes()\\].", - "", - "NOTE: In the EVM implementation, if there is no router endpoint then \"ErrUnsupportedChain\"", - "error is thrown (whereas here the account would not exist)." + "Seeds must be \\[\"registered_emitter\", target_chain.to_be_bytes()\\]." ] }, { @@ -1362,6 +1362,10 @@ export type TokenRouter = { "code": 6096, "name": "InvalidSourceRouter" }, + { + "code": 6098, + "name": "InvalidTargetRouter" + }, { "code": 6100, "name": "EndpointDisabled" @@ -1741,20 +1745,21 @@ export const IDL: TokenRouter = { }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "Custodian, but does not need to be deserialized.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { - "name": "orderSender", + "name": "transferAuthority", "isMut": false, - "isSigner": true, + "isSigner": false, "docs": [ - "This signer will be encoded in the prepared order. He will also need to be present when", - "invoking any of the place market order instructions." + "The auction participant needs to set approval to this PDA.", + "" ] }, { @@ -1763,7 +1768,7 @@ export const IDL: TokenRouter = { "isSigner": true }, { - "name": "srcToken", + "name": "senderToken", "isMut": true, "isSigner": false, "docs": [ @@ -1792,9 +1797,14 @@ export const IDL: TokenRouter = { ] }, { - "name": "mint", - "isMut": false, - "isSigner": false + "name": "usdc", + "accounts": [ + { + "name": "mint", + "isMut": false, + "isSigner": false + } + ] }, { "name": "tokenProgram", @@ -1899,6 +1909,11 @@ export const IDL: TokenRouter = { "This account must be the same pubkey as the one who prepared the order." ] }, + { + "name": "preparedBy", + "isMut": true, + "isSigner": false + }, { "name": "payerSequence", "isMut": true, @@ -1906,12 +1921,12 @@ export const IDL: TokenRouter = { }, { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "This program's Wormhole (Core Bridge) emitter authority.", - "", - "Seeds must be \\[\"emitter\"\\]." + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -1919,14 +1934,6 @@ export const IDL: TokenRouter = { "isMut": true, "isSigner": false }, - { - "name": "orderSender", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer who must be the same one encoded in the prepared order." - ] - }, { "name": "mint", "isMut": true, @@ -1957,10 +1964,7 @@ export const IDL: TokenRouter = { "CCTP domain encoded if this route is CCTP-enabled. For this instruction, it is required that", "[RouterEndpoint::cctp_domain] is `Some(value)`.", "", - "Seeds must be \\[\"registered_emitter\", target_chain.to_be_bytes()\\].", - "", - "NOTE: In the EVM implementation, if there is no router endpoint then \"ErrUnsupportedChain\"", - "error is thrown (whereas here the account would not exist)." + "Seeds must be \\[\"registered_emitter\", target_chain.to_be_bytes()\\]." ] }, { @@ -2797,6 +2801,10 @@ export const IDL: TokenRouter = { "code": 6096, "name": "InvalidSourceRouter" }, + { + "code": 6098, + "name": "InvalidTargetRouter" + }, { "code": 6100, "name": "EndpointDisabled" diff --git a/solana/ts/src/matchingEngine/index.ts b/solana/ts/src/matchingEngine/index.ts index f2f72e0db..386beca55 100644 --- a/solana/ts/src/matchingEngine/index.ts +++ b/solana/ts/src/matchingEngine/index.ts @@ -418,7 +418,7 @@ export class MatchingEngineProgram { return { transferAuthority, ix: splToken.createApproveInstruction( - splToken.getAssociatedTokenAddressSync(USDC_MINT_ADDRESS, owner), + splToken.getAssociatedTokenAddressSync(this.mint, owner), transferAuthority, owner, totalDeposit, diff --git a/solana/ts/src/tokenRouter/index.ts b/solana/ts/src/tokenRouter/index.ts index 669cd09fa..e1e3f14bd 100644 --- a/solana/ts/src/tokenRouter/index.ts +++ b/solana/ts/src/tokenRouter/index.ts @@ -10,6 +10,7 @@ import { SystemProgram, TransactionInstruction, } from "@solana/web3.js"; +import { Keccak } from "sha3"; import { IDL, TokenRouter } from "../../../target/types/token_router"; import { CctpTokenBurnMessage, @@ -17,11 +18,12 @@ import { TokenMessengerMinterProgram, } from "../cctp"; import { - Uint64, PayerSequence, + Uint64, cctpMessageAddress, coreMessageAddress, reclaimCctpMessageIx, + uint64ToBN, } from "../common"; import * as matchingEngineSdk from "../matchingEngine"; import { UpgradeManagerProgram } from "../upgradeManager"; @@ -202,6 +204,29 @@ export class TokenRouterProgram { return this.program.account.preparedFill.fetch(addr); } + transferAuthorityAddress(preparedOrder: PublicKey, args: PrepareMarketOrderArgs): PublicKey { + const { amountIn, minAmountOut, targetChain, redeemer, redeemerMessage } = args; + const hasher = new Keccak(256); + hasher.update(uint64ToBN(amountIn).toBuffer("be", 8)); + if (minAmountOut !== null) { + hasher.update(uint64ToBN(minAmountOut).toBuffer("be", 8)); + } + hasher.update( + (() => { + const buf = Buffer.alloc(2); + buf.writeUInt16BE(targetChain); + return buf; + })(), + ); + hasher.update(Buffer.from(redeemer)); + hasher.update(redeemerMessage); + + return PublicKey.findProgramAddressSync( + [Buffer.from("transfer-authority"), preparedOrder.toBuffer(), hasher.digest()], + this.ID, + )[0]; + } + async commonAccounts(): Promise { const custodian = this.custodianAddress(); const { coreBridgeConfig, coreEmitterSequence, coreFeeCollector, coreBridgeProgram } = @@ -305,37 +330,72 @@ export class TokenRouterProgram { }; } + async approveTransferAuthorityIx( + accounts: { + preparedOrder: PublicKey; + senderToken: PublicKey; + sender?: PublicKey; + }, + args: PrepareMarketOrderArgs, + ): Promise<{ transferAuthority: PublicKey; ix: TransactionInstruction }> { + const { preparedOrder, senderToken } = accounts; + const { amountIn } = args; + + let { sender } = accounts; + sender ??= await (async () => { + const tokenAccount = await splToken.getAccount( + this.program.provider.connection, + senderToken, + ); + return tokenAccount.owner; + })(); + + const transferAuthority = this.transferAuthorityAddress(preparedOrder, args); + + return { + transferAuthority, + ix: splToken.createApproveInstruction(senderToken, transferAuthority, sender, amountIn), + }; + } + async prepareMarketOrderIx( accounts: { payer: PublicKey; - orderSender: PublicKey; preparedOrder: PublicKey; - srcToken: PublicKey; - refundToken: PublicKey; + senderToken: PublicKey; + refundToken?: PublicKey; + sender?: PublicKey; }, args: PrepareMarketOrderArgs, - ): Promise { - const { payer, orderSender, preparedOrder, srcToken, refundToken } = accounts; - const { amountIn, minAmountOut, ...remainingArgs } = args; + ): Promise<[approveIx: TransactionInstruction, prepareIx: TransactionInstruction]> { + const { payer, preparedOrder, senderToken, sender } = accounts; + let { refundToken } = accounts; + refundToken ??= senderToken; + + const { transferAuthority, ix: approveIx } = await this.approveTransferAuthorityIx( + { preparedOrder, senderToken, sender }, + args, + ); - return this.program.methods + const prepareIx = await this.program.methods .prepareMarketOrder({ - amountIn: new BN(amountIn.toString()), - minAmountOut: minAmountOut === null ? null : new BN(minAmountOut.toString()), - ...remainingArgs, + ...args, + amountIn: uint64ToBN(args.amountIn), + minAmountOut: args.minAmountOut === null ? null : uint64ToBN(args.minAmountOut), }) .accounts({ payer, - custodian: this.custodianAddress(), - orderSender, + custodian: this.checkedCustodianComposite(), + transferAuthority, preparedOrder, - srcToken, + senderToken, refundToken, preparedCustodyToken: this.preparedCustodyTokenAddress(preparedOrder), - mint: this.mint, - tokenProgram: splToken.TOKEN_PROGRAM_ID, + usdc: this.usdcComposite(), }) .instruction(); + + return [approveIx, prepareIx]; } async closePreparedOrderIx(accounts: { @@ -344,36 +404,16 @@ export class TokenRouterProgram { orderSender?: PublicKey; refundToken?: PublicKey; }): Promise { - const { - preparedOrder, - preparedBy: inputPreparedBy, - orderSender: inputOrderSender, - refundToken: inputRefundToken, - } = accounts; - - const { preparedBy, orderSender, refundToken } = await (async () => { - if ( - inputPreparedBy === undefined || - inputOrderSender === undefined || - inputRefundToken === undefined - ) { - const { - info: { preparedBy, orderSender, refundToken }, - } = await this.fetchPreparedOrder(preparedOrder); - - return { - preparedBy: inputPreparedBy ?? preparedBy, - orderSender: inputOrderSender ?? orderSender, - refundToken: inputRefundToken ?? refundToken, - }; - } else { - return { - preparedBy: inputPreparedBy, - orderSender: inputOrderSender, - refundToken: inputRefundToken, - }; - } - })(); + const { preparedOrder } = accounts; + let { preparedBy, orderSender, refundToken } = accounts; + + if (preparedBy === undefined || orderSender === undefined || refundToken === undefined) { + const { info } = await this.fetchPreparedOrder(preparedOrder); + + preparedBy ??= info.preparedBy; + orderSender ??= info.orderSender; + refundToken ??= info.refundToken; + } return this.program.methods .closePreparedOrder() @@ -384,7 +424,6 @@ export class TokenRouterProgram { preparedOrder, refundToken, preparedCustodyToken: this.preparedCustodyTokenAddress(preparedOrder), - tokenProgram: splToken.TOKEN_PROGRAM_ID, }) .instruction(); } @@ -413,33 +452,29 @@ export class TokenRouterProgram { accounts: { payer: PublicKey; preparedOrder: PublicKey; - orderSender?: PublicKey; + preparedBy?: PublicKey; routerEndpoint?: PublicKey; }, - args?: { - targetChain: number; - }, + args: { + targetChain?: number; + destinationDomain?: number; + } = {}, ): Promise { - const { - payer, - preparedOrder, - orderSender: inputOrderSender, - routerEndpoint: inputRouterEndpoint, - } = accounts; - const { orderSender, targetChain } = await (async () => { - if (inputOrderSender === undefined || args === undefined) { - const { - info: { orderSender, targetChain }, - } = await this.fetchPreparedOrder(preparedOrder).catch((_) => { - throw new Error( - "Cannot find prepared order. If it doesn't exist, please provide orderSender and targetChain.", - ); - }); - return { orderSender, targetChain }; - } else { - return { orderSender: inputOrderSender, targetChain: args.targetChain }; - } - })(); + const { payer, preparedOrder } = accounts; + let { preparedBy, routerEndpoint } = accounts; + let { targetChain, destinationDomain } = args; + + if (preparedBy === undefined || targetChain === undefined) { + const { info } = await this.fetchPreparedOrder(preparedOrder).catch((_) => { + throw new Error("Cannot find prepared order"); + }); + + preparedBy ??= info.preparedBy; + targetChain ??= info.targetChain; + } + + const matchingEngine = this.matchingEngineProgram(); + routerEndpoint ??= matchingEngine.routerEndpointAddress(targetChain); const payerSequence = this.payerSequenceAddress(payer); const { coreMessage, cctpMessage } = await this.fetchPayerSequenceValue({ @@ -451,16 +486,15 @@ export class TokenRouterProgram { }; }); - const matchingEngine = this.matchingEngineProgram(); - const routerEndpoint = matchingEngine.routerEndpointAddress(targetChain); - - const { protocol } = await matchingEngine.fetchRouterEndpoint({ - address: routerEndpoint, - }); - if (protocol.cctp === undefined) { - throw new Error("invalid router endpoint"); + if (destinationDomain === undefined) { + const { protocol } = await matchingEngine.fetchRouterEndpoint({ + address: routerEndpoint, + }); + if (protocol.cctp === undefined) { + throw new Error("invalid router endpoint"); + } + destinationDomain = protocol.cctp.domain; } - const mint = this.mint; const { senderAuthority: tokenMessengerMinterSenderAuthority, @@ -473,8 +507,8 @@ export class TokenRouterProgram { messageTransmitterProgram, tokenMessengerMinterProgram, } = this.tokenMessengerMinterProgram().depositForBurnWithCallerAccounts( - mint, - protocol.cctp.domain, + this.mint, + destinationDomain, ); const custodian = this.custodianAddress(); @@ -485,13 +519,13 @@ export class TokenRouterProgram { .placeMarketOrderCctp() .accounts({ payer, + preparedBy, payerSequence, - custodian, + custodian: this.checkedCustodianComposite(), preparedOrder, - orderSender: inputOrderSender ?? orderSender, - mint, + mint: this.mint, preparedCustodyToken: this.preparedCustodyTokenAddress(preparedOrder), - routerEndpoint: inputRouterEndpoint ?? routerEndpoint, + routerEndpoint, coreBridgeConfig, coreMessage, cctpMessage, diff --git a/solana/ts/src/tokenRouter/state/PreparedOrder.ts b/solana/ts/src/tokenRouter/state/PreparedOrder.ts index f1036c4a9..eee4880fc 100644 --- a/solana/ts/src/tokenRouter/state/PreparedOrder.ts +++ b/solana/ts/src/tokenRouter/state/PreparedOrder.ts @@ -1,5 +1,7 @@ import { BN } from "@coral-xyz/anchor"; import { PublicKey } from "@solana/web3.js"; +import { Keccak } from "sha3"; +import { Uint64, uint64ToBN } from "../../common"; export type OrderType = { market?: { diff --git a/solana/ts/tests/02__tokenRouter.ts b/solana/ts/tests/02__tokenRouter.ts index 7fde2ea57..e6d813c8f 100644 --- a/solana/ts/tests/02__tokenRouter.ts +++ b/solana/ts/tests/02__tokenRouter.ts @@ -388,7 +388,6 @@ describe("Token Router", function () { const localVariables = new Map(); it("Cannot Prepare Market Order with Insufficient Amount", async function () { - const orderSender = Keypair.generate(); const preparedOrder = Keypair.generate(); const amountIn = 0n; @@ -396,13 +395,11 @@ describe("Token Router", function () { const targetChain = foreignChain; const redeemer = Array.from(Buffer.alloc(32, "deadbeef", "hex")); const redeemerMessage = Buffer.from("All your base are belong to us"); - const ix = await tokenRouter.prepareMarketOrderIx( + const ixs = await tokenRouter.prepareMarketOrderIx( { payer: payer.publicKey, - orderSender: orderSender.publicKey, preparedOrder: preparedOrder.publicKey, - srcToken: payerToken, - refundToken: payerToken, + senderToken: payerToken, }, { amountIn, @@ -413,23 +410,15 @@ describe("Token Router", function () { }, ); - const approveIx = splToken.createApproveInstruction( - payerToken, - tokenRouter.custodianAddress(), - payer.publicKey, - amountIn, - ); - await expectIxErr( connection, - [approveIx, ix], - [payer, orderSender, preparedOrder], + ixs, + [payer, preparedOrder], "Error Code: InsufficientAmount", ); }); it("Cannot Prepare Market Order with Invalid Redeemer", async function () { - const orderSender = Keypair.generate(); const preparedOrder = Keypair.generate(); const amountIn = 69n; @@ -437,13 +426,11 @@ describe("Token Router", function () { const targetChain = foreignChain; const redeemer = Array.from(Buffer.alloc(32, 0, "hex")); const redeemerMessage = Buffer.from("All your base are belong to us"); - const ix = await tokenRouter.prepareMarketOrderIx( + const ixs = await tokenRouter.prepareMarketOrderIx( { payer: payer.publicKey, - orderSender: orderSender.publicKey, preparedOrder: preparedOrder.publicKey, - srcToken: payerToken, - refundToken: payerToken, + senderToken: payerToken, }, { amountIn, @@ -454,23 +441,15 @@ describe("Token Router", function () { }, ); - const approveIx = splToken.createApproveInstruction( - payerToken, - tokenRouter.custodianAddress(), - payer.publicKey, - amountIn, - ); - await expectIxErr( connection, - [approveIx, ix], - [payer, orderSender, preparedOrder], + ixs, + [payer, preparedOrder], "Error Code: InvalidRedeemer", ); }); it("Cannot Prepare Market Order with Min Amount Too High", async function () { - const orderSender = Keypair.generate(); const preparedOrder = Keypair.generate(); const amountIn = 1n; @@ -478,13 +457,11 @@ describe("Token Router", function () { const targetChain = foreignChain; const redeemer = Array.from(Buffer.alloc(32, "deadbeef", "hex")); const redeemerMessage = Buffer.from("All your base are belong to us"); - const ix = await tokenRouter.prepareMarketOrderIx( + const ixs = await tokenRouter.prepareMarketOrderIx( { payer: payer.publicKey, - orderSender: orderSender.publicKey, preparedOrder: preparedOrder.publicKey, - srcToken: payerToken, - refundToken: payerToken, + senderToken: payerToken, }, { amountIn, @@ -495,23 +472,15 @@ describe("Token Router", function () { }, ); - const approveIx = splToken.createApproveInstruction( - payerToken, - tokenRouter.custodianAddress(), - payer.publicKey, - amountIn, - ); - await expectIxErr( connection, - [approveIx, ix], - [payer, orderSender, preparedOrder], + ixs, + [payer, preparedOrder], "Error Code: MinAmountOutTooHigh", ); }); it("Cannot Prepare Market Order without Delegating Authority to Custodian", async function () { - const orderSender = Keypair.generate(); const preparedOrder = Keypair.generate(); const amountIn = 69n; @@ -519,13 +488,11 @@ describe("Token Router", function () { const targetChain = foreignChain; const redeemer = Array.from(Buffer.alloc(32, "deadbeef", "hex")); const redeemerMessage = Buffer.from("All your base are belong to us"); - const ix = await tokenRouter.prepareMarketOrderIx( + const [, ix] = await tokenRouter.prepareMarketOrderIx( { payer: payer.publicKey, - orderSender: orderSender.publicKey, preparedOrder: preparedOrder.publicKey, - srcToken: payerToken, - refundToken: payerToken, + senderToken: payerToken, }, { amountIn, @@ -539,13 +506,12 @@ describe("Token Router", function () { await expectIxErr( connection, [ix], - [payer, orderSender, preparedOrder], + [payer, preparedOrder], "Error: owner does not match", ); }); it("Prepare Market Order with Some Min Amount Out", async function () { - const orderSender = Keypair.generate(); const preparedOrder = Keypair.generate(); const amountIn = 69n; @@ -553,13 +519,11 @@ describe("Token Router", function () { const targetChain = foreignChain; const redeemer = Array.from(Buffer.alloc(32, "deadbeef", "hex")); const redeemerMessage = Buffer.from("All your base are belong to us"); - const ix = await tokenRouter.prepareMarketOrderIx( + const ixs = await tokenRouter.prepareMarketOrderIx( { payer: payer.publicKey, - orderSender: orderSender.publicKey, preparedOrder: preparedOrder.publicKey, - srcToken: payerToken, - refundToken: payerToken, + senderToken: payerToken, }, { amountIn, @@ -570,16 +534,9 @@ describe("Token Router", function () { }, ); - const approveIx = splToken.createApproveInstruction( - payerToken, - tokenRouter.custodianAddress(), - payer.publicKey, - amountIn, - ); - const { amount: balanceBefore } = await splToken.getAccount(connection, payerToken); - await expectIxOk(connection, [approveIx, ix], [payer, orderSender, preparedOrder]); + await expectIxOk(connection, ixs, [payer, preparedOrder]); const { amount: balanceAfter } = await splToken.getAccount(connection, payerToken); expect(balanceAfter).equals(balanceBefore - amountIn); @@ -593,7 +550,7 @@ describe("Token Router", function () { expect(preparedOrderData).to.eql( new PreparedOrder( { - orderSender: orderSender.publicKey, + orderSender: payer.publicKey, preparedBy: payer.publicKey, orderType: { market: { @@ -618,17 +575,14 @@ describe("Token Router", function () { }); it("Prepare Market Order without Min Amount Out", async function () { - const orderSender = Keypair.generate(); const preparedOrder = Keypair.generate(); const amountIn = 69n; - const ix = await tokenRouter.prepareMarketOrderIx( + const ixs = await tokenRouter.prepareMarketOrderIx( { payer: payer.publicKey, - orderSender: orderSender.publicKey, preparedOrder: preparedOrder.publicKey, - srcToken: payerToken, - refundToken: payerToken, + senderToken: payerToken, }, { amountIn, @@ -639,16 +593,9 @@ describe("Token Router", function () { }, ); - const approveIx = splToken.createApproveInstruction( - payerToken, - tokenRouter.custodianAddress(), - payer.publicKey, - amountIn, - ); - const { amount: balanceBefore } = await splToken.getAccount(connection, payerToken); - await expectIxOk(connection, [approveIx, ix], [payer, orderSender, preparedOrder]); + await expectIxOk(connection, ixs, [payer, preparedOrder]); const { amount: balanceAfter } = await splToken.getAccount(connection, payerToken); expect(balanceAfter).equals(balanceBefore - amountIn); @@ -661,20 +608,16 @@ describe("Token Router", function () { // We've checked other fields in a previous test. Just make sure the min amount out // is null. - const { - info: { orderType }, - } = await tokenRouter.fetchPreparedOrder(preparedOrder.publicKey); - expect(orderType).to.eql({ market: { minAmountOut: null } }); + const { info } = await tokenRouter.fetchPreparedOrder(preparedOrder.publicKey); + expect(info.orderType).to.eql({ market: { minAmountOut: null } }); // Save for later. localVariables.set("preparedOrder", preparedOrder.publicKey); - localVariables.set("orderSender", orderSender); localVariables.set("amountIn", amountIn); }); it("Cannot Close Prepared Order without Original Payer", async function () { const preparedOrder = localVariables.get("preparedOrder") as PublicKey; - const orderSender = localVariables.get("orderSender") as Keypair; const ix = await tokenRouter.closePreparedOrderIx({ preparedOrder, @@ -684,7 +627,7 @@ describe("Token Router", function () { await expectIxErr( connection, [ix], - [ownerAssistant, orderSender], + [ownerAssistant, payer], "Error Code: PreparedByMismatch", ); }); @@ -707,7 +650,6 @@ describe("Token Router", function () { it("Cannot Close Prepared Order without Correct Refund Token", async function () { const preparedOrder = localVariables.get("preparedOrder") as PublicKey; - const orderSender = localVariables.get("orderSender") as Keypair; const refundToken = Keypair.generate().publicKey; @@ -716,19 +658,12 @@ describe("Token Router", function () { refundToken, }); - await expectIxErr( - connection, - [ix], - [payer, orderSender], - "Error Code: RefundTokenMismatch", - ); + await expectIxErr(connection, [ix], [payer], "Error Code: RefundTokenMismatch"); }); it("Close Prepared Order", async function () { const preparedOrder = localVariables.get("preparedOrder") as PublicKey; expect(localVariables.delete("preparedOrder")).is.true; - const orderSender = localVariables.get("orderSender") as Keypair; - expect(localVariables.delete("orderSender")).is.true; const amountIn = localVariables.get("amountIn") as bigint; expect(localVariables.delete("amountIn")).is.true; @@ -738,7 +673,7 @@ describe("Token Router", function () { const { amount: balanceBefore } = await splToken.getAccount(connection, payerToken); - await expectIxOk(connection, [ix], [payer, orderSender]); + await expectIxOk(connection, [ix], [payer]); const { amount: balanceAfter } = await splToken.getAccount(connection, payerToken); expect(balanceAfter).equals(balanceBefore + amountIn); @@ -758,7 +693,6 @@ describe("Token Router", function () { USDC_MINT_ADDRESS, payer.publicKey, ); - const orderSender = Keypair.generate(); const redeemer = Array.from(Buffer.alloc(32, "deadbeef", "hex")); const redeemerMessage = Buffer.from("All your base are belong to us"); @@ -773,11 +707,7 @@ describe("Token Router", function () { const amountIn = 69n; const { preparedOrder, approveIx, prepareIx } = await prepareOrder(amountIn); - await expectIxOk( - connection, - [approveIx, prepareIx], - [payer, orderSender, preparedOrder], - ); + await expectIxOk(connection, [approveIx, prepareIx], [payer, preparedOrder]); // Save for later. localVariables.set("preparedOrder", preparedOrder.publicKey); @@ -790,11 +720,14 @@ describe("Token Router", function () { const unregisteredEndpoint = tokenRouter .matchingEngineProgram() .routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA); - const ix = await tokenRouter.placeMarketOrderCctpIx({ - payer: payer.publicKey, - preparedOrder, - routerEndpoint: unregisteredEndpoint, - }); + const ix = await tokenRouter.placeMarketOrderCctpIx( + { + payer: payer.publicKey, + preparedOrder, + routerEndpoint: unregisteredEndpoint, + }, + { destinationDomain: 3 }, + ); const { value: lookupTableAccount } = await connection.getAddressLookupTable( lookupTableAddress, @@ -802,48 +735,48 @@ describe("Token Router", function () { await expectIxErr( connection, [ix], - [payer, orderSender], - "Error Code: AccountNotInitialized", + [payer], + "router_endpoint. Error Code: AccountNotInitialized", { addressLookupTableAccounts: [lookupTableAccount!], }, ); }); - it("Cannot Place Market Order without Original Payer", async function () { + it("Cannot Place Market Order with Invalid Target Router", async function () { const preparedOrder = localVariables.get("preparedOrder") as PublicKey; - const newPayer = Keypair.generate(); - const ix = await tokenRouter.placeMarketOrderCctpIx({ - payer: newPayer.publicKey, - preparedOrder, - }); + const ix = await tokenRouter.placeMarketOrderCctpIx( + { + payer: payer.publicKey, + preparedOrder, + }, + { targetChain: 23 }, + ); - await expectIxErr( - connection, - [ix], - [newPayer], - "Transaction signature verification failure", + const { value: lookupTableAccount } = await connection.getAddressLookupTable( + lookupTableAddress, ); + await expectIxErr(connection, [ix], [payer], "Error Code: InvalidTargetRouter", { + addressLookupTableAccounts: [lookupTableAccount!], + }); }); - it("Cannot Place Market Order without Order Sender", async function () { + it("Cannot Place Market Order without Original Preparer", async function () { const preparedOrder = localVariables.get("preparedOrder") as PublicKey; const someoneElse = Keypair.generate(); - const ix = await tokenRouter.placeMarketOrderCctpIx({ payer: payer.publicKey, preparedOrder, - orderSender: someoneElse.publicKey, + preparedBy: someoneElse.publicKey, }); - // NOTE: This error comes from the SPL Token program. await expectIxErr( connection, [ix], - [payer, someoneElse], - "Error Code: OrderSenderMismatch", + [payer], + "prepared_by. Error Code: ConstraintAddress", ); }); @@ -861,7 +794,7 @@ describe("Token Router", function () { const { value: lookupTableAccount } = await connection.getAddressLookupTable( lookupTableAddress, ); - await expectIxOk(connection, [ix], [payer, orderSender], { + await expectIxOk(connection, [ix], [payer], { addressLookupTableAccounts: [lookupTableAccount!], }); @@ -929,7 +862,7 @@ describe("Token Router", function () { await expectIxOk( connection, [approveIx, approveIx, prepareIx], - [payer, orderSender, preparedOrder], + [payer, preparedOrder], ); // Save for later. @@ -945,7 +878,7 @@ describe("Token Router", function () { preparedOrder, }); - await expectIxErr(connection, [ix], [payer, orderSender], "Error Code: Paused"); + await expectIxErr(connection, [ix], [payer], "Error Code: Paused"); }); it("Unpause", async function () { @@ -973,7 +906,7 @@ describe("Token Router", function () { const { value: lookupTableAccount } = await connection.getAddressLookupTable( lookupTableAddress, ); - await expectIxOk(connection, [ix], [payer, orderSender], { + await expectIxOk(connection, [ix], [payer], { addressLookupTableAccounts: [lookupTableAccount!], }); @@ -988,7 +921,7 @@ describe("Token Router", function () { { payer: payer.publicKey, preparedOrder: preparedOrder.publicKey, - orderSender: orderSender.publicKey, + preparedBy: payer.publicKey, }, { targetChain: foreignChain, @@ -1000,14 +933,9 @@ describe("Token Router", function () { const { value: lookupTableAccount } = await connection.getAddressLookupTable( lookupTableAddress, ); - await expectIxOk( - connection, - [approveIx, prepareIx, ix], - [payer, orderSender, preparedOrder], - { - addressLookupTableAccounts: [lookupTableAccount!], - }, - ); + await expectIxOk(connection, [approveIx, prepareIx, ix], [payer, preparedOrder], { + addressLookupTableAccounts: [lookupTableAccount!], + }); const { amount: balanceAfter } = await splToken.getAccount(connection, payerToken); expect(balanceAfter).equals(balanceBefore - amountIn); @@ -1021,12 +949,11 @@ describe("Token Router", function () { async function prepareOrder(amountIn: bigint) { const preparedOrder = Keypair.generate(); - const prepareIx = await tokenRouter.prepareMarketOrderIx( + const [approveIx, prepareIx] = await tokenRouter.prepareMarketOrderIx( { payer: payer.publicKey, - orderSender: orderSender.publicKey, preparedOrder: preparedOrder.publicKey, - srcToken: payerToken, + senderToken: payerToken, refundToken: payerToken, }, { @@ -1038,13 +965,6 @@ describe("Token Router", function () { }, ); - const approveIx = splToken.createApproveInstruction( - payerToken, - tokenRouter.custodianAddress(), - payer.publicKey, - amountIn, - ); - return { preparedOrder, approveIx, prepareIx }; } @@ -1099,7 +1019,7 @@ describe("Token Router", function () { { fill: { sourceChain: wormholeSdk.CHAIN_ID_SOLANA as number, - orderSender: Array.from(orderSender.publicKey.toBuffer()), + orderSender: Array.from(payer.publicKey.toBuffer()), redeemer, redeemerMessage, }, From 695f49cf68074845d94054251f76d3840ffdb4dc Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Thu, 4 Apr 2024 08:00:00 -0500 Subject: [PATCH 3/8] solana: remove errs; rename acct --- solana/programs/token-router/src/error.rs | 5 -- .../src/processor/close_prepared_order.rs | 31 ++++---- .../src/processor/consume_prepared_fill.rs | 10 +-- solana/target/idl/token_router.json | 37 +++------- solana/target/types/token_router.ts | 74 +++++-------------- solana/ts/src/tokenRouter/index.ts | 8 +- solana/ts/tests/02__tokenRouter.ts | 11 ++- solana/ts/tests/04__interaction.ts | 8 +- 8 files changed, 62 insertions(+), 122 deletions(-) diff --git a/solana/programs/token-router/src/error.rs b/solana/programs/token-router/src/error.rs index 435d24c92..6ef4b96bb 100644 --- a/solana/programs/token-router/src/error.rs +++ b/solana/programs/token-router/src/error.rs @@ -26,9 +26,4 @@ pub enum TokenRouterError { InsufficientAmount = 0x400, MinAmountOutTooHigh = 0x402, InvalidRedeemer = 0x404, - RedeemerMismatch = 0x406, - PreparedByMismatch = 0x408, - OrderSenderMismatch = 0x40a, - RefundTokenMismatch = 0x40c, - PayerNotPreparer = 0x40e, } diff --git a/solana/programs/token-router/src/processor/close_prepared_order.rs b/solana/programs/token-router/src/processor/close_prepared_order.rs index c7043c6cb..bc5a4ccab 100644 --- a/solana/programs/token-router/src/processor/close_prepared_order.rs +++ b/solana/programs/token-router/src/processor/close_prepared_order.rs @@ -1,5 +1,5 @@ use crate::{ - error::TokenRouterError, + composite::*, state::{Custodian, PreparedOrder}, }; use anchor_lang::prelude::*; @@ -8,33 +8,30 @@ use anchor_spl::token; /// Accounts required for [close_prepared_order]. #[derive(Accounts)] pub struct ClosePreparedOrder<'info> { - /// Custodian, but does not need to be deserialized. - /// - /// CHECK: Seeds must be \["emitter"\]. - #[account( - seeds = [Custodian::SEED_PREFIX], - bump = Custodian::BUMP, - )] - custodian: AccountInfo<'info>, + custodian: CheckedCustodian<'info>, /// This signer must be the same one encoded in the prepared order. + #[account(address = prepared_order.order_sender)] order_sender: Signer<'info>, - /// CHECK: This payer must be the same one encoded in the prepared order. - #[account(mut)] - prepared_by: AccountInfo<'info>, - #[account( mut, close = prepared_by, - has_one = prepared_by @ TokenRouterError::PreparedByMismatch, - has_one = order_sender @ TokenRouterError::OrderSenderMismatch, - has_one = refund_token @ TokenRouterError::RefundTokenMismatch, )] prepared_order: Account<'info, PreparedOrder>, + /// CHECK: This payer must be the same one encoded in the prepared order. + #[account( + mut, + address = prepared_order.prepared_by, + )] + prepared_by: AccountInfo<'info>, + /// CHECK: This account must be the same one encoded in the prepared order. - #[account(mut)] + #[account( + mut, + address = prepared_order.refund_token, + )] refund_token: AccountInfo<'info>, /// Custody token account. This account will be closed at the end of this instruction. It just diff --git a/solana/programs/token-router/src/processor/consume_prepared_fill.rs b/solana/programs/token-router/src/processor/consume_prepared_fill.rs index 81dd3e318..d2a2cc54f 100644 --- a/solana/programs/token-router/src/processor/consume_prepared_fill.rs +++ b/solana/programs/token-router/src/processor/consume_prepared_fill.rs @@ -1,4 +1,4 @@ -use crate::{error::TokenRouterError, state::PreparedFill}; +use crate::state::PreparedFill; use anchor_lang::prelude::*; use anchor_spl::token; @@ -6,6 +6,7 @@ use anchor_spl::token; #[derive(Accounts)] pub struct ConsumePreparedFill<'info> { /// This signer must be the same one encoded in the prepared fill. + #[account(address = prepared_fill.redeemer)] redeemer: Signer<'info>, /// CHECK: This recipient may not necessarily be the same one encoded in the prepared fill (as @@ -13,12 +14,11 @@ pub struct ConsumePreparedFill<'info> { /// intention of consuming it, he will be out of luck. We will reward the redeemer with the /// closed account funds with a payer of his choosing. #[account(mut)] - rent_recipient: AccountInfo<'info>, + beneficiary: AccountInfo<'info>, #[account( mut, - close = rent_recipient, - has_one = redeemer @ TokenRouterError::RedeemerMismatch, + close = beneficiary, )] prepared_fill: Account<'info, PreparedFill>, @@ -77,7 +77,7 @@ pub fn consume_prepared_fill(ctx: Context) -> Result<()> { token_program.to_account_info(), token::CloseAccount { account: custody_token.to_account_info(), - destination: ctx.accounts.rent_recipient.to_account_info(), + destination: ctx.accounts.beneficiary.to_account_info(), authority: prepared_fill.to_account_info(), }, &[prepared_fill_signer_seeds], diff --git a/solana/target/idl/token_router.json b/solana/target/idl/token_router.json index 78ff46f03..2e5e81b13 100644 --- a/solana/target/idl/token_router.json +++ b/solana/target/idl/token_router.json @@ -401,11 +401,12 @@ "accounts": [ { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "Custodian, but does not need to be deserialized.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -417,12 +418,12 @@ ] }, { - "name": "preparedBy", + "name": "preparedOrder", "isMut": true, "isSigner": false }, { - "name": "preparedOrder", + "name": "preparedBy", "isMut": true, "isSigner": false }, @@ -983,7 +984,7 @@ ] }, { - "name": "rentRecipient", + "name": "beneficiary", "isMut": true, "isSigner": false, "docs": [ @@ -1413,26 +1414,6 @@ { "code": 7028, "name": "InvalidRedeemer" - }, - { - "code": 7030, - "name": "RedeemerMismatch" - }, - { - "code": 7032, - "name": "PreparedByMismatch" - }, - { - "code": 7034, - "name": "OrderSenderMismatch" - }, - { - "code": 7036, - "name": "RefundTokenMismatch" - }, - { - "code": 7038, - "name": "PayerNotPreparer" } ], "metadata": { diff --git a/solana/target/types/token_router.ts b/solana/target/types/token_router.ts index c9c12ca9f..a421064fd 100644 --- a/solana/target/types/token_router.ts +++ b/solana/target/types/token_router.ts @@ -401,11 +401,12 @@ export type TokenRouter = { "accounts": [ { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "Custodian, but does not need to be deserialized.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -417,12 +418,12 @@ export type TokenRouter = { ] }, { - "name": "preparedBy", + "name": "preparedOrder", "isMut": true, "isSigner": false }, { - "name": "preparedOrder", + "name": "preparedBy", "isMut": true, "isSigner": false }, @@ -983,7 +984,7 @@ export type TokenRouter = { ] }, { - "name": "rentRecipient", + "name": "beneficiary", "isMut": true, "isSigner": false, "docs": [ @@ -1413,26 +1414,6 @@ export type TokenRouter = { { "code": 7028, "name": "InvalidRedeemer" - }, - { - "code": 7030, - "name": "RedeemerMismatch" - }, - { - "code": 7032, - "name": "PreparedByMismatch" - }, - { - "code": 7034, - "name": "OrderSenderMismatch" - }, - { - "code": 7036, - "name": "RefundTokenMismatch" - }, - { - "code": 7038, - "name": "PayerNotPreparer" } ] }; @@ -1840,11 +1821,12 @@ export const IDL: TokenRouter = { "accounts": [ { "name": "custodian", - "isMut": false, - "isSigner": false, - "docs": [ - "Custodian, but does not need to be deserialized.", - "" + "accounts": [ + { + "name": "custodian", + "isMut": false, + "isSigner": false + } ] }, { @@ -1856,12 +1838,12 @@ export const IDL: TokenRouter = { ] }, { - "name": "preparedBy", + "name": "preparedOrder", "isMut": true, "isSigner": false }, { - "name": "preparedOrder", + "name": "preparedBy", "isMut": true, "isSigner": false }, @@ -2422,7 +2404,7 @@ export const IDL: TokenRouter = { ] }, { - "name": "rentRecipient", + "name": "beneficiary", "isMut": true, "isSigner": false, "docs": [ @@ -2852,26 +2834,6 @@ export const IDL: TokenRouter = { { "code": 7028, "name": "InvalidRedeemer" - }, - { - "code": 7030, - "name": "RedeemerMismatch" - }, - { - "code": 7032, - "name": "PreparedByMismatch" - }, - { - "code": 7034, - "name": "OrderSenderMismatch" - }, - { - "code": 7036, - "name": "RefundTokenMismatch" - }, - { - "code": 7038, - "name": "PayerNotPreparer" } ] }; diff --git a/solana/ts/src/tokenRouter/index.ts b/solana/ts/src/tokenRouter/index.ts index e1e3f14bd..fd328e3a1 100644 --- a/solana/ts/src/tokenRouter/index.ts +++ b/solana/ts/src/tokenRouter/index.ts @@ -419,7 +419,7 @@ export class TokenRouterProgram { .closePreparedOrder() .accounts({ preparedBy, - custodian: this.custodianAddress(), + custodian: this.checkedCustodianComposite(), orderSender, preparedOrder, refundToken, @@ -432,15 +432,15 @@ export class TokenRouterProgram { preparedFill: PublicKey; redeemer: PublicKey; dstToken: PublicKey; - rentRecipient: PublicKey; + beneficiary: PublicKey; }): Promise { - const { preparedFill, redeemer, dstToken, rentRecipient } = accounts; + const { preparedFill, redeemer, dstToken, beneficiary } = accounts; return this.program.methods .consumePreparedFill() .accounts({ redeemer, - rentRecipient, + beneficiary, preparedFill, dstToken, preparedCustodyToken: this.preparedCustodyTokenAddress(preparedFill), diff --git a/solana/ts/tests/02__tokenRouter.ts b/solana/ts/tests/02__tokenRouter.ts index e6d813c8f..732e17494 100644 --- a/solana/ts/tests/02__tokenRouter.ts +++ b/solana/ts/tests/02__tokenRouter.ts @@ -628,7 +628,7 @@ describe("Token Router", function () { connection, [ix], [ownerAssistant, payer], - "Error Code: PreparedByMismatch", + "prepared_by. Error Code: ConstraintAddress", ); }); @@ -644,7 +644,7 @@ describe("Token Router", function () { connection, [ix], [payer, ownerAssistant], - "Error Code: OrderSenderMismatch", + "order_sender. Error Code: ConstraintAddress", ); }); @@ -658,7 +658,12 @@ describe("Token Router", function () { refundToken, }); - await expectIxErr(connection, [ix], [payer], "Error Code: RefundTokenMismatch"); + await expectIxErr( + connection, + [ix], + [payer], + "refund_token. Error Code: ConstraintAddress", + ); }); it("Close Prepared Order", async function () { diff --git a/solana/ts/tests/04__interaction.ts b/solana/ts/tests/04__interaction.ts index 85e0ecafb..1b85ae4f8 100644 --- a/solana/ts/tests/04__interaction.ts +++ b/solana/ts/tests/04__interaction.ts @@ -589,16 +589,16 @@ describe("Matching Engine <> Token Router", function () { const redeemerMessage = localVariables.get("redeemerMessage") as Buffer; expect(localVariables.delete("redeemerMessage")).is.true; - const rentRecipient = Keypair.generate().publicKey; + const beneficiary = Keypair.generate().publicKey; const ix = await tokenRouter.consumePreparedFillIx({ preparedFill, redeemer: redeemer.publicKey, dstToken: payerToken, - rentRecipient, + beneficiary, }); const { amount: balanceBefore } = await splToken.getAccount(connection, payerToken); - const solBalanceBefore = await connection.getBalance(rentRecipient); + const solBalanceBefore = await connection.getBalance(beneficiary); await expectIxOk(connection, [ix], [payer, redeemer]); @@ -606,7 +606,7 @@ describe("Matching Engine <> Token Router", function () { const { amount: balanceAfter } = await splToken.getAccount(connection, payerToken); expect(balanceAfter).equals(balanceBefore + amount); - const solBalanceAfter = await connection.getBalance(rentRecipient); + const solBalanceAfter = await connection.getBalance(beneficiary); const preparedFillRent = await connection.getMinimumBalanceForRentExemption( 152 + redeemerMessage.length, ); From 4ee154b71af47429053a07415c9c16a6f682a055 Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Thu, 4 Apr 2024 08:32:50 -0500 Subject: [PATCH 4/8] solana: sdk refactor --- solana/ts/src/common/index.ts | 28 + solana/ts/src/common/messages/deposit.ts | 13 +- solana/ts/src/common/messages/index.ts | 4 +- solana/ts/src/common/state/index.ts | 4 +- solana/ts/src/matchingEngine/index.ts | 926 +++++++++--------- .../ts/src/matchingEngine/state/Proposal.ts | 17 +- solana/ts/src/tokenRouter/index.ts | 9 +- solana/ts/src/wormhole/index.ts | 10 +- solana/ts/tests/02__tokenRouter.ts | 2 +- 9 files changed, 509 insertions(+), 504 deletions(-) diff --git a/solana/ts/src/common/index.ts b/solana/ts/src/common/index.ts index 2576b14ff..bf4e29a4d 100644 --- a/solana/ts/src/common/index.ts +++ b/solana/ts/src/common/index.ts @@ -37,6 +37,34 @@ export function uint64ToBN(value: Uint64): BN { return new BN(buf); } +export type VaaHash = Array | Buffer | Uint8Array; + +export function vaaHashToUint8Array(vaaHash: VaaHash): Uint8Array { + if (Array.isArray(vaaHash)) { + return Uint8Array.from(vaaHash); + } else if (Buffer.isBuffer(vaaHash)) { + return Uint8Array.from(vaaHash); + } else { + return vaaHash; + } +} + +export function vaaHashToBuffer(vaaHash: VaaHash): Buffer { + if (Buffer.isBuffer(vaaHash)) { + return vaaHash; + } else { + return Buffer.from(vaaHashToUint8Array(vaaHash)); + } +} + +export function vaaHashToArray(vaaHash: VaaHash): Array { + if (Array.isArray(vaaHash)) { + return vaaHash; + } else { + return Array.from(vaaHashToUint8Array(vaaHash)); + } +} + export async function reclaimCctpMessageIx( messageTransmitter: MessageTransmitterProgram, accounts: { diff --git a/solana/ts/src/common/messages/deposit.ts b/solana/ts/src/common/messages/deposit.ts index b829ae8a6..2ac58e532 100644 --- a/solana/ts/src/common/messages/deposit.ts +++ b/solana/ts/src/common/messages/deposit.ts @@ -1,4 +1,4 @@ -import { BN } from "@coral-xyz/anchor"; +import * as wormholeSdk from "@certusone/wormhole-sdk"; import { ethers } from "ethers"; export const ID_DEPOSIT = 1; @@ -17,7 +17,7 @@ export type DepositHeader = { }; export type Fill = { - sourceChain: number; + sourceChain: wormholeSdk.ChainId; orderSender: Array; redeemer: Array; redeemerMessage: Buffer; @@ -52,7 +52,7 @@ export class LiquidityLayerDeposit { const tokenAddress = Array.from(buf.subarray(offset, (offset += 32))); const amount = BigInt( - ethers.BigNumber.from(buf.subarray(offset, (offset += 32))).toString() + ethers.BigNumber.from(buf.subarray(offset, (offset += 32))).toString(), ); const sourceCctpDomain = buf.readUInt32BE(offset); offset += 4; @@ -74,6 +74,9 @@ export class LiquidityLayerDeposit { switch (depositPayloadId) { case ID_DEPOSIT_FILL: { const sourceChain = payload.readUInt16BE(offset); + if (!wormholeSdk.isChain(sourceChain)) { + throw new Error("Invalid source chain"); + } offset += 2; const orderSender = Array.from(payload.subarray(offset, (offset += 32))); const redeemer = Array.from(payload.subarray(offset, (offset += 32))); @@ -81,7 +84,7 @@ export class LiquidityLayerDeposit { offset += 4; const redeemerMessage = payload.subarray( offset, - (offset += redeemerMessageLen) + (offset += redeemerMessageLen), ); return { fill: { sourceChain, orderSender, redeemer, redeemerMessage }, @@ -109,7 +112,7 @@ export class LiquidityLayerDeposit { burnSource, mintRecipient, }, - message + message, ); } diff --git a/solana/ts/src/common/messages/index.ts b/solana/ts/src/common/messages/index.ts index 5fb7f4526..9bf167e57 100644 --- a/solana/ts/src/common/messages/index.ts +++ b/solana/ts/src/common/messages/index.ts @@ -1,4 +1,4 @@ -import { BN } from "@coral-xyz/anchor"; +import * as wormholeSdk from "@certusone/wormhole-sdk"; import { Fill, ID_DEPOSIT, LiquidityLayerDeposit } from "./deposit"; export * from "./deposit"; @@ -17,7 +17,7 @@ export type FastMarketOrder = { amountIn: bigint; // u64 minAmountOut: bigint; - targetChain: number; + targetChain: wormholeSdk.ChainId; redeemer: Array; sender: Array; refundAddress: Array; diff --git a/solana/ts/src/common/state/index.ts b/solana/ts/src/common/state/index.ts index ddc4a7b66..79c0352e1 100644 --- a/solana/ts/src/common/state/index.ts +++ b/solana/ts/src/common/state/index.ts @@ -1,5 +1,5 @@ -import { BN } from "@coral-xyz/anchor"; import { PublicKey } from "@solana/web3.js"; +import { Uint64, writeUint64BE } from ".."; export * from "./PayerSequence"; @@ -30,7 +30,7 @@ function messageAddress( prefix: string, ): PublicKey { const encodedPayerSequenceValue = Buffer.alloc(8); - encodedPayerSequenceValue.writeBigUInt64BE(BigInt(payerSequenceValue.toString())); + writeUint64BE(encodedPayerSequenceValue, payerSequenceValue); return PublicKey.findProgramAddressSync( [Buffer.from(prefix), payer.toBuffer(), encodedPayerSequenceValue], programId, diff --git a/solana/ts/src/matchingEngine/index.ts b/solana/ts/src/matchingEngine/index.ts index 386beca55..123dbd9d8 100644 --- a/solana/ts/src/matchingEngine/index.ts +++ b/solana/ts/src/matchingEngine/index.ts @@ -10,23 +10,25 @@ import { SYSVAR_CLOCK_PUBKEY, SYSVAR_EPOCH_SCHEDULE_PUBKEY, SYSVAR_RENT_PUBKEY, - SystemProgram, Signer, + SystemProgram, TransactionInstruction, } from "@solana/web3.js"; +import { PreparedTransaction, PreparedTransactionOptions } from ".."; import { IDL, MatchingEngine } from "../../../target/types/matching_engine"; -import { USDC_MINT_ADDRESS } from "../../tests/helpers"; import { MessageTransmitterProgram, TokenMessengerMinterProgram } from "../cctp"; -import { PreparedTransaction, PreparedTransactionOptions } from ".."; import { - Uint64, + FastMarketOrder, LiquidityLayerMessage, PayerSequence, + Uint64, + VaaHash, cctpMessageAddress, coreMessageAddress, reclaimCctpMessageIx, uint64ToBN, uint64ToBigInt, + writeUint64BE, } from "../common"; import { UpgradeManagerProgram } from "../upgradeManager"; import { BPF_LOADER_UPGRADEABLE_PROGRAM_ID, programDataAddress } from "../utils"; @@ -54,10 +56,8 @@ export const FEE_PRECISION_MAX = 1_000_000n; export type ProgramId = (typeof PROGRAM_IDS)[number]; -export type VaaHash = Array | Buffer | Uint8Array; - export type AddCctpRouterEndpointArgs = { - chain: number; + chain: wormholeSdk.ChainId; cctpDomain: number; address: Array; mintRecipient: Array | null; @@ -216,12 +216,17 @@ export class MatchingEngineProgram { return splToken.getAssociatedTokenAddressSync(this.mint, this.custodianAddress(), true); } - routerEndpointAddress(chain: number): PublicKey { + routerEndpointAddress(chain: wormholeSdk.ChainId): PublicKey { return RouterEndpoint.address(this.ID, chain); } - async fetchRouterEndpoint(input: number | { address: PublicKey }): Promise { - const addr = typeof input === "number" ? this.routerEndpointAddress(input) : input.address; + async fetchRouterEndpoint( + input: wormholeSdk.ChainId | { address: PublicKey }, + ): Promise { + const addr = + typeof input == "object" && "address" in input + ? input.address + : this.routerEndpointAddress(input); return this.program.account.routerEndpoint.fetch(addr); } @@ -250,7 +255,7 @@ export class MatchingEngineProgram { .catch((_) => 0n); } - async proposalAddress(proposalId?: BN): Promise { + async proposalAddress(proposalId?: Uint64): Promise { if (proposalId === undefined) { const { nextProposalId } = await this.fetchCustodian(); proposalId = nextProposalId; @@ -344,9 +349,9 @@ export class MatchingEngineProgram { .catch((_) => 0n); } - transferAuthorityAddress(auction: PublicKey, offerPrice: bigint | number): PublicKey { + transferAuthorityAddress(auction: PublicKey, offerPrice: Uint64): PublicKey { const encodedOfferPrice = Buffer.alloc(8); - encodedOfferPrice.writeBigUInt64BE(BigInt(offerPrice)); + writeUint64BE(encodedOfferPrice, offerPrice); return PublicKey.findProgramAddressSync( [Buffer.from("transfer-authority"), auction.toBuffer(), encodedOfferPrice], this.ID, @@ -406,8 +411,8 @@ export class MatchingEngineProgram { owner: PublicKey; }, amounts: { - offerPrice: bigint | number; - totalDeposit: bigint | number; + offerPrice: Uint64; + totalDeposit: Uint64; }, ): Promise<{ transferAuthority: PublicKey; ix: TransactionInstruction }> { const { auction, owner } = accounts; @@ -421,7 +426,7 @@ export class MatchingEngineProgram { splToken.getAssociatedTokenAddressSync(this.mint, owner), transferAuthority, owner, - totalDeposit, + uint64ToBigInt(totalDeposit), ), }; } @@ -542,35 +547,26 @@ export class MatchingEngineProgram { bestOfferToken?: PublicKey; }, cached: { - info?: AuctionInfo | null; + auctionInfo?: AuctionInfo; } = {}, ) { - 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 }; + const { auction } = accounts; + + let { config, bestOfferToken } = accounts; + let { auctionInfo } = cached; + + if (config === undefined || bestOfferToken === undefined) { + if (auctionInfo === undefined) { + const { info } = await this.fetchAuction({ address: auction }); 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, - }; + auctionInfo = info; } - })(); + + config ??= this.auctionConfigAddress(auctionInfo.configId); + bestOfferToken ??= auctionInfo.bestOfferToken; + } return { custodyToken: this.auctionCustodyTokenAddress(auction), @@ -641,7 +637,7 @@ export class MatchingEngineProgram { }, auctionParams: AuctionParameters, ): Promise { - const { owner, ownerAssistant, feeRecipient, mint: inputMint } = accounts; + const { owner, ownerAssistant, feeRecipient, mint } = accounts; const upgradeManager = this.upgradeManagerProgram(); return this.program.methods @@ -654,7 +650,7 @@ export class MatchingEngineProgram { feeRecipient, feeRecipientToken: splToken.getAssociatedTokenAddressSync(this.mint, feeRecipient), cctpMintRecipient: this.cctpMintRecipientAddress(), - usdc: this.usdcComposite(inputMint), + usdc: this.usdcComposite(mint), programData: programDataAddress(this.ID), upgradeManagerAuthority: upgradeManager.upgradeAuthorityAddress(), upgradeManagerProgram: upgradeManager.ID, @@ -670,11 +666,11 @@ export class MatchingEngineProgram { }, paused: boolean, ): Promise { - const { ownerOrAssistant, custodian: inputCustodian } = accounts; + const { ownerOrAssistant, custodian } = accounts; return this.program.methods .setPause(paused) .accounts({ - admin: this.adminMutComposite(ownerOrAssistant, inputCustodian), + admin: this.adminMutComposite(ownerOrAssistant, custodian), }) .instruction(); } @@ -684,11 +680,11 @@ export class MatchingEngineProgram { newOwner: PublicKey; custodian?: PublicKey; }): Promise { - const { owner, newOwner, custodian: inputCustodian } = accounts; + const { owner, newOwner, custodian } = accounts; return this.program.methods .submitOwnershipTransferRequest() .accounts({ - admin: this.ownerOnlyMutComposite(owner, inputCustodian), + admin: this.ownerOnlyMutComposite(owner, custodian), newOwner, }) .instruction(); @@ -698,13 +694,12 @@ export class MatchingEngineProgram { pendingOwner: PublicKey; custodian?: PublicKey; }): Promise { - const { pendingOwner, custodian: inputCustodian } = accounts; + const { pendingOwner } = accounts; + let { custodian } = accounts; + custodian ??= this.custodianAddress(); return this.program.methods .confirmOwnershipTransferRequest() - .accounts({ - pendingOwner, - custodian: inputCustodian ?? this.custodianAddress(), - }) + .accounts({ pendingOwner, custodian }) .instruction(); } @@ -712,11 +707,11 @@ export class MatchingEngineProgram { owner: PublicKey; custodian?: PublicKey; }): Promise { - const { owner, custodian: inputCustodian } = accounts; + const { owner, custodian } = accounts; return this.program.methods .cancelOwnershipTransferRequest() .accounts({ - admin: this.ownerOnlyMutComposite(owner, inputCustodian), + admin: this.ownerOnlyMutComposite(owner, custodian), }) .instruction(); } @@ -726,11 +721,11 @@ export class MatchingEngineProgram { newOwnerAssistant: PublicKey; custodian?: PublicKey; }) { - const { owner, newOwnerAssistant, custodian: inputCustodian } = accounts; + const { owner, newOwnerAssistant, custodian } = accounts; return this.program.methods .updateOwnerAssistant() .accounts({ - admin: this.ownerOnlyMutComposite(owner, inputCustodian), + admin: this.ownerOnlyMutComposite(owner, custodian), newOwnerAssistant, }) .instruction(); @@ -746,25 +741,23 @@ export class MatchingEngineProgram { }, args: AddCctpRouterEndpointArgs, ): Promise { - const { - ownerOrAssistant, - payer: inputPayer, - custodian: inputCustodian, - routerEndpoint: inputRouterEndpoint, - remoteTokenMessenger: inputRemoteTokenMessenger, - } = accounts; + const { ownerOrAssistant, custodian } = accounts; const { chain, cctpDomain } = args; - const derivedRemoteTokenMessenger = + + let { payer, routerEndpoint, remoteTokenMessenger } = accounts; + payer ??= ownerOrAssistant; + routerEndpoint ??= this.routerEndpointAddress(chain); + remoteTokenMessenger ??= this.tokenMessengerMinterProgram().remoteTokenMessengerAddress(cctpDomain); return this.program.methods .addCctpRouterEndpoint(args) .accounts({ - payer: inputPayer ?? ownerOrAssistant, - admin: this.adminComposite(ownerOrAssistant, inputCustodian), - routerEndpoint: inputRouterEndpoint ?? this.routerEndpointAddress(chain), + payer, + admin: this.adminComposite(ownerOrAssistant, custodian), + routerEndpoint, localCustodyToken: this.localCustodyTokenAddress(chain), - remoteTokenMessenger: inputRemoteTokenMessenger ?? derivedRemoteTokenMessenger, + remoteTokenMessenger, usdc: this.usdcComposite(), }) .instruction(); @@ -779,24 +772,20 @@ export class MatchingEngineProgram { }, args: AddCctpRouterEndpointArgs, ): Promise { - const { - owner, - custodian: inputCustodian, - routerEndpoint: inputRouterEndpoint, - remoteTokenMessenger: inputRemoteTokenMessenger, - } = accounts; + const { owner, custodian } = accounts; const { chain, cctpDomain } = args; - const derivedRemoteTokenMessenger = + + let { routerEndpoint, remoteTokenMessenger } = accounts; + routerEndpoint ??= this.routerEndpointAddress(chain); + remoteTokenMessenger ??= this.tokenMessengerMinterProgram().remoteTokenMessengerAddress(cctpDomain); return this.program.methods .updateCctpRouterEndpoint(args) .accounts({ - admin: this.ownerOnlyComposite(owner, inputCustodian), - routerEndpoint: this.routerEndpointComposite( - inputRouterEndpoint ?? this.routerEndpointAddress(chain), - ), - remoteTokenMessenger: inputRemoteTokenMessenger ?? derivedRemoteTokenMessenger, + admin: this.ownerOnlyComposite(owner, custodian), + routerEndpoint: this.routerEndpointComposite(routerEndpoint), + remoteTokenMessenger, }) .instruction(); } @@ -809,35 +798,44 @@ export class MatchingEngineProgram { proposal?: PublicKey; }, parameters: AuctionParameters, + opts: { + proposalId?: Uint64; + } = {}, ): Promise { - const { - ownerOrAssistant, - payer: inputPayer, - custodian: inputCustodian, - proposal: inputProposal, - } = accounts; + const { ownerOrAssistant, custodian } = accounts; + + let { payer, proposal } = accounts; + payer ??= ownerOrAssistant; + proposal ??= await this.proposalAddress(opts.proposalId); return this.program.methods .proposeAuctionParameters(parameters) .accounts({ - payer: inputPayer ?? ownerOrAssistant, + payer, admin: { ownerOrAssistant, - custodian: this.checkedCustodianComposite(inputCustodian), + custodian: this.checkedCustodianComposite(custodian), }, - proposal: inputProposal ?? (await this.proposalAddress()), + proposal, epochSchedule: SYSVAR_EPOCH_SCHEDULE_PUBKEY, }) .instruction(); } - async closeProposalIx(accounts: { - ownerOrAssistant: PublicKey; - proposal?: PublicKey; - }): Promise { - const { ownerOrAssistant, proposal: inputProposal } = accounts; + async closeProposalIx( + accounts: { + ownerOrAssistant: PublicKey; + proposal?: PublicKey; + }, + opts: { + proposalId?: Uint64; + } = {}, + ): Promise { + const { ownerOrAssistant } = accounts; + + let { proposal } = accounts; + proposal ??= await this.proposalAddress(opts.proposalId); - const proposal = inputProposal ?? (await this.proposalAddress()); const { by: proposedBy } = await this.fetchProposal({ address: proposal }); return this.program.methods @@ -850,37 +848,36 @@ export class MatchingEngineProgram { .instruction(); } - async updateAuctionParametersIx(accounts: { - owner: PublicKey; - payer?: PublicKey; - custodian?: PublicKey; - proposal?: PublicKey; - auctionConfig?: PublicKey; - }): Promise { - const { - owner, - payer: inputPayer, - custodian: inputCustodian, - proposal: inputProposal, - auctionConfig: inputAuctionConfig, - } = accounts; - - // Add 1 to the current auction config ID to get the next one. - const auctionConfig = await (async () => { - if (inputAuctionConfig === undefined) { - const { auctionConfigId } = await this.fetchCustodian(); - return this.auctionConfigAddress(auctionConfigId + 1); - } else { - return inputAuctionConfig; - } - })(); + async updateAuctionParametersIx( + accounts: { + owner: PublicKey; + payer?: PublicKey; + custodian?: PublicKey; + proposal?: PublicKey; + auctionConfig?: PublicKey; + }, + opts: { + proposalId?: Uint64; + } = {}, + ): Promise { + const { owner, custodian } = accounts; + + let { payer, proposal, auctionConfig } = accounts; + payer ??= owner; + proposal ??= await this.proposalAddress(opts.proposalId); + + if (auctionConfig === undefined) { + const { auctionConfigId } = await this.fetchCustodian(); + // Add 1 to the current auction config ID to get the next one. + auctionConfig = this.auctionConfigAddress(auctionConfigId + 1); + } return this.program.methods .updateAuctionParameters() .accounts({ - payer: inputPayer ?? owner, - admin: this.ownerOnlyMutComposite(owner, inputCustodian), - proposal: inputProposal ?? (await this.proposalAddress()), + payer, + admin: this.ownerOnlyMutComposite(owner, custodian), + proposal, auctionConfig, }) .instruction(); @@ -893,21 +890,18 @@ export class MatchingEngineProgram { custodian?: PublicKey; routerEndpoint?: PublicKey; }): Promise { - const { - ownerOrAssistant, - tokenRouterProgram, - payer: inputPayer, - custodian: inputCustodian, - routerEndpoint: inputRouterEndpoint, - } = accounts; + const { ownerOrAssistant, tokenRouterProgram, custodian } = accounts; + + let { payer, routerEndpoint } = accounts; + payer ??= ownerOrAssistant; + routerEndpoint ??= this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA); return this.program.methods .addLocalRouterEndpoint() .accounts({ - payer: inputPayer ?? ownerOrAssistant, - admin: this.adminComposite(ownerOrAssistant, inputCustodian), - routerEndpoint: - inputRouterEndpoint ?? this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), + payer, + admin: this.adminComposite(ownerOrAssistant, custodian), + routerEndpoint, local: this.localTokenRouterComposite(tokenRouterProgram), }) .instruction(); @@ -919,23 +913,16 @@ export class MatchingEngineProgram { custodian?: PublicKey; routerEndpoint?: PublicKey; }): Promise { - const { - owner, - tokenRouterProgram, - custodian: inputCustodian, - routerEndpoint: inputRouterEndpoint, - } = accounts; + const { owner, tokenRouterProgram, custodian } = accounts; + + let { routerEndpoint } = accounts; + routerEndpoint ??= this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA); return this.program.methods .updateLocalRouterEndpoint() .accounts({ - admin: { - owner, - custodian: this.checkedCustodianComposite(inputCustodian), - }, - routerEndpoint: this.routerEndpointComposite( - inputRouterEndpoint ?? this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), - ), + admin: this.ownerOnlyComposite(owner, custodian), + routerEndpoint: this.routerEndpointComposite(routerEndpoint), local: this.localTokenRouterComposite(tokenRouterProgram), }) .instruction(); @@ -949,14 +936,16 @@ export class MatchingEngineProgram { }, chain: wormholeSdk.ChainId, ): Promise { - const { owner, custodian: inputCustodian, routerEndpoint: inputRouterEndpoint } = accounts; + const { owner, custodian } = accounts; + + let { routerEndpoint } = accounts; + routerEndpoint ??= this.routerEndpointAddress(chain); + return this.program.methods .disableRouterEndpoint() .accounts({ - admin: this.ownerOnlyComposite(owner, inputCustodian), - routerEndpoint: this.routerEndpointComposite( - inputRouterEndpoint ?? this.routerEndpointAddress(chain), - ), + admin: this.ownerOnlyComposite(owner, custodian), + routerEndpoint: this.routerEndpointComposite(routerEndpoint), }) .instruction(); } @@ -966,15 +955,12 @@ export class MatchingEngineProgram { newFeeRecipient: PublicKey; custodian?: PublicKey; }): Promise { - const { ownerOrAssistant, newFeeRecipient, custodian: inputCustodian } = accounts; + const { ownerOrAssistant, newFeeRecipient, custodian } = accounts; return this.program.methods .updateFeeRecipient() .accounts({ - admin: { - ownerOrAssistant, - custodian: inputCustodian ?? this.custodianAddress(), - }, + admin: this.adminMutComposite(ownerOrAssistant, custodian), newFeeRecipient, newFeeRecipientToken: splToken.getAssociatedTokenAddressSync( this.mint, @@ -984,17 +970,13 @@ export class MatchingEngineProgram { .instruction(); } - async getCoreMessage(payer: PublicKey, payerSequenceValue?: bigint): Promise { - const value = await (async () => { - if (payerSequenceValue === undefined) { - // Fetch the latest. - const { value } = await this.fetchPayerSequence(payer); - return BigInt(value.subn(1).toString()); - } else { - return payerSequenceValue; - } - })(); - return this.coreMessageAddress(payer, value); + async getCoreMessage(payer: PublicKey, payerSequenceValue?: Uint64): Promise { + if (payerSequenceValue === undefined) { + // Fetch the latest. + const { value } = await this.fetchPayerSequence(payer); + payerSequenceValue = uint64ToBigInt(value) - 1n; + } + return this.coreMessageAddress(payer, payerSequenceValue); } async fetchCctpMintRecipient(): Promise { @@ -1013,8 +995,8 @@ export class MatchingEngineProgram { auction?: PublicKey; }, args: { - offerPrice: bigint; - totalDeposit?: bigint; + offerPrice: Uint64; + totalDeposit?: Uint64; }, signers: Signer[], opts: PreparedTransactionOptions, @@ -1055,91 +1037,54 @@ export class MatchingEngineProgram { toRouterEndpoint?: PublicKey; }, args: { - offerPrice: bigint; - totalDeposit?: bigint; + offerPrice: Uint64; + totalDeposit?: Uint64; }, ): Promise<[approveIx: TransactionInstruction, placeInitialOfferIx: TransactionInstruction]> { - const { - payer, - fastVaa, - offerToken: inputOfferToken, - auction: inputAuction, - auctionConfig: inputAuctionConfig, - fromRouterEndpoint: inputFromRouterEndpoint, - toRouterEndpoint: inputToRouterEndpoint, - } = accounts; - - const { offerPrice, totalDeposit: inputTotalDeposit } = args; - - const offerToken = await (async () => { - if (inputOfferToken !== undefined) { - return inputOfferToken; - } else { - return splToken.getAssociatedTokenAddressSync(this.mint, payer); + const { payer, fastVaa } = accounts; + + const { offerPrice } = args; + + let { auction, auctionConfig, offerToken, fromRouterEndpoint, toRouterEndpoint } = accounts; + let { totalDeposit } = args; + + offerToken ??= await splToken.getAssociatedTokenAddress(this.mint, payer); + + let fetchedConfigId: Uint64 | null = null; + if ( + auction === undefined || + fromRouterEndpoint === undefined || + toRouterEndpoint === undefined || + totalDeposit === undefined + ) { + const vaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); + auction ??= this.auctionAddress(vaaAccount.digest()); + fromRouterEndpoint ??= this.routerEndpointAddress(vaaAccount.emitterInfo().chain); + + const { fastMarketOrder } = LiquidityLayerMessage.decode(vaaAccount.payload()); + if (fastMarketOrder === undefined) { + throw new Error("Message not FastMarketOrder"); } - })(); + toRouterEndpoint ??= this.routerEndpointAddress(fastMarketOrder.targetChain); - const { fetchedConfigId, auction, fromRouterEndpoint, toRouterEndpoint, totalDeposit } = - await (async () => { - if ( - inputAuction === undefined || - inputFromRouterEndpoint === undefined || - inputToRouterEndpoint === undefined || - inputTotalDeposit === undefined - ) { - const vaaAccount = await VaaAccount.fetch( - this.program.provider.connection, - fastVaa, - ); - const { fastMarketOrder } = LiquidityLayerMessage.decode(vaaAccount.payload()); - if (fastMarketOrder === undefined) { - throw new Error("Message not FastMarketOrder"); - } - - const { auctionConfigId } = await this.fetchCustodian(); - const notionalDeposit = await this.computeNotionalSecurityDeposit( - fastMarketOrder.amountIn, - auctionConfigId, - ); - - return { - fetchedConfigId: auctionConfigId, - auction: inputAuction ?? this.auctionAddress(vaaAccount.digest()), - fromRouterEndpoint: - inputFromRouterEndpoint ?? - this.routerEndpointAddress(vaaAccount.emitterInfo().chain), - toRouterEndpoint: - inputToRouterEndpoint ?? - this.routerEndpointAddress(fastMarketOrder.targetChain), - totalDeposit: - fastMarketOrder.amountIn + fastMarketOrder.maxFee + notionalDeposit, - }; - } else { - return { - fetchedConfigId: null, - auction: inputAuction, - fromRouterEndpoint: inputFromRouterEndpoint, - toRouterEndpoint: inputToRouterEndpoint, - totalDeposit: inputTotalDeposit, - }; - } - })(); - - const auctionConfig = await (async () => { - if (inputAuctionConfig === undefined) { - const auctionConfigId = await (async () => { - if (fetchedConfigId === null) { - const { auctionConfigId } = await this.fetchCustodian(); - return auctionConfigId; - } else { - return fetchedConfigId; - } - })(); - return this.auctionConfigAddress(auctionConfigId); - } else { - return inputAuctionConfig; + const custodianData = await this.fetchCustodian(); + fetchedConfigId = custodianData.auctionConfigId; + + const notionalDeposit = await this.computeNotionalSecurityDeposit( + fastMarketOrder.amountIn, + fetchedConfigId, + ); + + totalDeposit ??= fastMarketOrder.amountIn + fastMarketOrder.maxFee + notionalDeposit; + } + + if (auctionConfig === undefined) { + if (fetchedConfigId === null) { + const custodianData = await this.fetchCustodian(); + fetchedConfigId = custodianData.auctionConfigId; } - })(); + auctionConfig = this.auctionConfigAddress(fetchedConfigId); + } const auctionCustodyToken = this.auctionCustodyTokenAddress(auction); @@ -1151,7 +1096,7 @@ export class MatchingEngineProgram { }, ); const placeInitialOfferIx = await this.program.methods - .placeInitialOffer(new BN(offerPrice.toString())) + .placeInitialOffer(uint64ToBN(offerPrice)) .accounts({ payer, transferAuthority, @@ -1179,7 +1124,7 @@ export class MatchingEngineProgram { auctionConfig?: PublicKey; bestOfferToken?: PublicKey; }, - offerPrice: bigint, + offerPrice: Uint64, signers: Signer[], opts: PreparedTransactionOptions, confirmOptions?: ConfirmOptions, @@ -1215,36 +1160,33 @@ export class MatchingEngineProgram { auctionConfig?: PublicKey; bestOfferToken?: PublicKey; }, - offerPrice: bigint, + offerPrice: Uint64, ): Promise<[approveIx: TransactionInstruction, improveOfferIx: TransactionInstruction]> { - const { - participant, - auction, - auctionConfig: inputAuctionConfig, - bestOfferToken: inputBestOfferToken, - } = accounts; + const { participant, auction, auctionConfig, bestOfferToken } = accounts; // TODO: add cached args above - const { info } = await this.fetchAuction({ address: auction }); - if (info === null) { + const { info: auctionInfo } = await this.fetchAuction({ address: auction }); + if (auctionInfo === null) { throw new Error("no auction info found"); } const { transferAuthority, ix: approveIx } = await this.approveTransferAuthorityIx( { auction, owner: participant }, { - totalDeposit: BigInt(info.amountIn.add(info.securityDeposit).toString()), + totalDeposit: BigInt( + auctionInfo.amountIn.add(auctionInfo.securityDeposit).toString(), + ), offerPrice, }, ); const improveOfferIx = await this.program.methods - .improveOffer(new BN(offerPrice.toString())) + .improveOffer(uint64ToBN(offerPrice)) .accounts({ transferAuthority, activeAuction: await this.activeAuctionComposite( - { auction, config: inputAuctionConfig, bestOfferToken: inputBestOfferToken }, - { info }, + { auction, config: auctionConfig, bestOfferToken: bestOfferToken }, + { auctionInfo }, ), offerToken: splToken.getAssociatedTokenAddressSync(this.mint, participant), }) @@ -1365,44 +1307,26 @@ export class MatchingEngineProgram { auction?: PublicKey; bestOfferToken?: PublicKey; }) { - const { - executor, - preparedOrderResponse, - auction: inputAuction, - bestOfferToken: inputBestOfferToken, - } = accounts; - - const { auction } = await (async () => { - if (inputAuction !== undefined) { - return { - auction: inputAuction, - }; - } else { - const { fastVaaHash } = await this.fetchPreparedOrderResponse({ - address: preparedOrderResponse, - }); - return { - auction: inputAuction ?? this.auctionAddress(fastVaaHash), - }; - } - })(); + const { executor, preparedOrderResponse } = accounts; - const { bestOfferToken } = await (async () => { - if (inputBestOfferToken === undefined) { - const { info } = await this.fetchAuction({ address: auction }); - if (info === null) { - throw new Error("no auction info found"); - } + let { auction, bestOfferToken } = accounts; - return { - bestOfferToken: inputBestOfferToken ?? info.bestOfferToken, - }; - } else { - return { - bestOfferToken: inputBestOfferToken, - }; + if (auction === undefined) { + const { fastVaaHash } = await this.fetchPreparedOrderResponse({ + address: preparedOrderResponse, + }); + + auction = this.auctionAddress(fastVaaHash); + } + + if (bestOfferToken === undefined) { + const { info } = await this.fetchAuction({ address: auction }); + if (info === null) { + throw new Error("no auction info found"); } - })(); + + bestOfferToken = info.bestOfferToken; + } return this.program.methods .settleAuctionComplete() @@ -1452,12 +1376,14 @@ export class MatchingEngineProgram { auction, }); } else { - return this.settleAuctionNoneCctpIx({ - payer: executor, - fastVaa, - preparedOrderResponse, - toRouterEndpoint: this.routerEndpointAddress(fastMarketOrder.targetChain), - }); + return this.settleAuctionNoneCctpIx( + { + payer: executor, + fastVaa, + preparedOrderResponse, + }, + { targetChain: fastMarketOrder.targetChain }, + ); } })(); @@ -1475,37 +1401,33 @@ export class MatchingEngineProgram { return preparedTx; } - async settleAuctionNoneLocalIx(accounts: { - payer: PublicKey; - preparedOrderResponse?: PublicKey; - auction?: PublicKey; - fastVaa: PublicKey; - }) { - const { - payer, - preparedOrderResponse: inputPreparedOrderResponse, - auction, - fastVaa, - } = accounts; - const fastVaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); - - const preparedOrderResponse = - inputPreparedOrderResponse ?? - this.preparedOrderResponseAddress(fastVaaAccount.digest()); + async settleAuctionNoneLocalIx( + accounts: { + payer: PublicKey; + fastVaa: PublicKey; + preparedOrderResponse?: PublicKey; + auction?: PublicKey; + }, + opts: { + sourceChain?: wormholeSdk.ChainId; + } = {}, + ) { + const { payer, fastVaa } = accounts; - const { targetChain, toRouterEndpoint } = await (async () => { - const message = LiquidityLayerMessage.decode(fastVaaAccount.payload()); - if (message.fastMarketOrder == undefined) { - throw new Error("Message not FastMarketOrder"); - } + let { auction, preparedOrderResponse } = accounts; + let { sourceChain } = opts; - const targetChain = message.fastMarketOrder.targetChain; - const toRouterEndpoint = this.routerEndpointAddress( - message.fastMarketOrder.targetChain, - ); + let fastVaaAccount: VaaAccount | undefined; + if (auction === undefined || preparedOrderResponse === undefined) { + fastVaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); + auction ??= this.auctionAddress(fastVaaAccount.digest()); + preparedOrderResponse ??= this.preparedOrderResponseAddress(fastVaaAccount.digest()); + } - return { targetChain, toRouterEndpoint }; - })(); + if (sourceChain === undefined) { + fastVaaAccount ??= await VaaAccount.fetch(this.program.provider.connection, fastVaa); + sourceChain ??= fastVaaAccount.emitterInfo().chain; + } const payerSequence = this.payerSequenceAddress(payer); const payerSequenceValue = await this.fetchPayerSequenceValue({ @@ -1536,8 +1458,8 @@ export class MatchingEngineProgram { }), fastOrderPath: this.fastOrderPathComposite({ fastVaa, - fromEndpoint: this.routerEndpointAddress(fastVaaAccount.emitterInfo().chain), - toEndpoint: toRouterEndpoint, + fromEndpoint: this.routerEndpointAddress(sourceChain), + toEndpoint: this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), }), auction, wormhole: { @@ -1550,22 +1472,41 @@ export class MatchingEngineProgram { .instruction(); } - async settleAuctionNoneCctpIx(accounts: { - payer: PublicKey; - fastVaa: PublicKey; - preparedOrderResponse: PublicKey; - toRouterEndpoint?: PublicKey; - }) { - const { payer, fastVaa, preparedOrderResponse } = accounts; + async settleAuctionNoneCctpIx( + accounts: { + payer: PublicKey; + fastVaa: PublicKey; + preparedOrderResponse?: PublicKey; + auction?: PublicKey; + }, + opts: { + sourceChain?: wormholeSdk.ChainId; + targetChain?: wormholeSdk.ChainId; + } = {}, + ) { + const { payer, fastVaa } = accounts; - const fastVaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); - const { fastMarketOrder } = LiquidityLayerMessage.decode(fastVaaAccount.payload()); - if (fastMarketOrder === undefined) { - throw new Error("Message not FastMarketOrder"); + let { auction, preparedOrderResponse } = accounts; + let { sourceChain, targetChain } = opts; + + let fastVaaAccount: VaaAccount | undefined; + if (auction === undefined || preparedOrderResponse === undefined) { + fastVaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); + auction ??= this.auctionAddress(fastVaaAccount.digest()); + preparedOrderResponse ??= this.preparedOrderResponseAddress(fastVaaAccount.digest()); } - const { targetChain } = fastMarketOrder; + if (sourceChain === undefined || targetChain === undefined) { + fastVaaAccount ??= await VaaAccount.fetch(this.program.provider.connection, fastVaa); + sourceChain ??= fastVaaAccount.emitterInfo().chain; + + const message = LiquidityLayerMessage.decode(fastVaaAccount.payload()); + if (message.fastMarketOrder == undefined) { + throw new Error("Message not FastMarketOrder"); + } + targetChain ??= message.fastMarketOrder.targetChain; + } const { custodian, payerSequence, @@ -1604,10 +1545,10 @@ export class MatchingEngineProgram { }), fastOrderPath: this.fastOrderPathComposite({ fastVaa, - fromEndpoint: this.routerEndpointAddress(fastVaaAccount.emitterInfo().chain), + fromEndpoint: this.routerEndpointAddress(sourceChain), toEndpoint: toRouterEndpoint, }), - auction: this.auctionAddress(fastVaaAccount.digest()), + auction, wormhole: { config: coreBridgeConfig, emitterSequence: coreEmitterSequence, @@ -1641,36 +1582,53 @@ export class MatchingEngineProgram { opts: PreparedTransactionOptions, confirmOptions?: ConfirmOptions, ): Promise { - const { - payer, - fastVaa, - auction: inputAuction, - executorToken: inputExecutorToken, - } = accounts; + const { payer, fastVaa, executorToken } = accounts; - const fastVaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); - const { fastMarketOrder } = LiquidityLayerMessage.decode(fastVaaAccount.payload()); - if (fastMarketOrder === undefined) { - throw new Error("Message not FastMarketOrder"); + let { auction } = accounts; + + let fastVaaAccount: VaaAccount | undefined; + let targetChain: wormholeSdk.ChainId | undefined; + if (auction === undefined) { + fastVaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); + auction = this.auctionAddress(fastVaaAccount.digest()); + const { fastMarketOrder } = LiquidityLayerMessage.decode(fastVaaAccount.payload()); + if (fastMarketOrder === undefined) { + throw new Error("Message not FastMarketOrder"); + } + + targetChain = fastMarketOrder.targetChain; + } + + // TODO: Make this smarter. Consider adding a target chain argument. + if (targetChain === undefined) { + fastVaaAccount ??= await VaaAccount.fetch(this.program.provider.connection, fastVaa); + + const { fastMarketOrder } = LiquidityLayerMessage.decode(fastVaaAccount.payload()); + if (fastMarketOrder === undefined) { + throw new Error("Message not FastMarketOrder"); + } + + targetChain = fastMarketOrder.targetChain; } - const auction = inputAuction ?? this.auctionAddress(fastVaaAccount.digest()); - const { targetChain } = fastMarketOrder; const executeOrderIx = await (async () => { if (targetChain === wormholeSdk.CHAIN_ID_SOLANA) { return this.executeFastOrderLocalIx({ payer, fastVaa, auction, - executorToken: inputExecutorToken, + executorToken, }); } else { - return this.executeFastOrderCctpIx({ - payer, - fastVaa, - auction, - executorToken: inputExecutorToken, - }); + return this.executeFastOrderCctpIx( + { + payer, + fastVaa, + auction, + executorToken, + }, + { targetChain }, + ); } })(); @@ -1686,51 +1644,52 @@ export class MatchingEngineProgram { }; } - async executeFastOrderCctpIx(accounts: { - payer: PublicKey; - fastVaa: PublicKey; - executorToken?: PublicKey; - auction?: PublicKey; - auctionConfig?: PublicKey; - bestOfferToken?: PublicKey; - initialOfferToken?: PublicKey; - }) { - const { - payer, - fastVaa, - executorToken: inputExecutorToken, - auction: inputAuction, - auctionConfig: inputAuctionConfig, - bestOfferToken: inputBestOfferToken, - initialOfferToken: inputInitialOfferToken, - } = accounts; - - // TODO: Think of a way to not have to do this fetch. - const vaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); - const { fastMarketOrder } = LiquidityLayerMessage.decode(vaaAccount.payload()); - if (fastMarketOrder === undefined) { - throw new Error("Message not FastMarketOrder"); + async executeFastOrderCctpIx( + accounts: { + payer: PublicKey; + fastVaa: PublicKey; + executorToken?: PublicKey; + auction?: PublicKey; + auctionConfig?: PublicKey; + bestOfferToken?: PublicKey; + initialOfferToken?: PublicKey; + }, + opts: { + targetChain?: wormholeSdk.ChainId; + } = {}, + ) { + const { payer, fastVaa, auctionConfig, bestOfferToken } = accounts; + + let { auction, executorToken, initialOfferToken } = accounts; + let { targetChain } = opts; + + executorToken ??= splToken.getAssociatedTokenAddressSync(this.mint, payer); + + let fastVaaAccount: VaaAccount | undefined; + if (auction === undefined) { + fastVaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); + auction = this.auctionAddress(fastVaaAccount.digest()); } - const auction = inputAuction ?? this.auctionAddress(vaaAccount.digest()); + if (targetChain === undefined) { + fastVaaAccount ??= await VaaAccount.fetch(this.program.provider.connection, fastVaa); - 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"); - } - return { - info, - initialOfferToken: inputInitialOfferToken ?? info.initialOfferToken, - }; - } else { - return { - info: null, - initialOfferToken: inputInitialOfferToken, - }; + const { fastMarketOrder } = LiquidityLayerMessage.decode(fastVaaAccount.payload()); + if (fastMarketOrder === undefined) { + throw new Error("Message not FastMarketOrder"); } - })(); + targetChain ??= fastMarketOrder.targetChain; + } + + let auctionInfo: AuctionInfo | undefined; + if (initialOfferToken === undefined) { + const { info } = await this.fetchAuction({ address: auction }); + if (info === null) { + throw new Error("no auction info found"); + } + auctionInfo = info; + initialOfferToken = info.initialOfferToken; + } const { custodian, @@ -1751,7 +1710,7 @@ export class MatchingEngineProgram { tokenMessengerMinterEventAuthority, messageTransmitterProgram, tokenMessengerMinterProgram, - } = await this.burnAndPublishAccounts({ payer }, fastMarketOrder); + } = await this.burnAndPublishAccounts({ payer }, { targetChain }); const mint = this.mint; return this.program.methods @@ -1766,13 +1725,12 @@ export class MatchingEngineProgram { activeAuction: await this.activeAuctionComposite( { auction, - config: inputAuctionConfig, - bestOfferToken: inputBestOfferToken, + config: auctionConfig, + bestOfferToken, }, - { info }, + { auctionInfo }, ), - executorToken: - inputExecutorToken ?? splToken.getAssociatedTokenAddressSync(mint, payer), + executorToken, initialOfferToken, }, toRouterEndpoint: this.routerEndpointComposite(toRouterEndpoint), @@ -1799,37 +1757,48 @@ export class MatchingEngineProgram { .instruction(); } - async executeFastOrderLocalIx(accounts: { - payer: PublicKey; - fastVaa: PublicKey; - executorToken?: PublicKey; - auction?: PublicKey; - auctionConfig?: PublicKey; - bestOfferToken?: PublicKey; - initialOfferToken?: PublicKey; - toRouterEndpoint?: PublicKey; - }) { - const { - payer, - fastVaa, - executorToken: inputExecutorToken, - auction: inputAuction, - auctionConfig: inputAuctionConfig, - bestOfferToken: inputBestOfferToken, - initialOfferToken: inputInitialOfferToken, - toRouterEndpoint: inputToRouterEndpoint, - } = accounts; - - const vaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); - const auction = inputAuction ?? this.auctionAddress(vaaAccount.digest()); - - // 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"); + async executeFastOrderLocalIx( + accounts: { + payer: PublicKey; + fastVaa: PublicKey; + executorToken?: PublicKey; + auction?: PublicKey; + auctionConfig?: PublicKey; + bestOfferToken?: PublicKey; + initialOfferToken?: PublicKey; + toRouterEndpoint?: PublicKey; + }, + opts: { + sourceChain?: wormholeSdk.ChainId; + } = {}, + ) { + const { payer, fastVaa, auctionConfig, bestOfferToken } = accounts; + + let { auction, executorToken, toRouterEndpoint, initialOfferToken } = accounts; + let { sourceChain } = opts; + executorToken ??= splToken.getAssociatedTokenAddressSync(this.mint, payer); + toRouterEndpoint ??= this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA); + + if (auction === undefined) { + const vaaAccount = await VaaAccount.fetch(this.program.provider.connection, fastVaa); + auction = this.auctionAddress(vaaAccount.digest()); + } + + let auctionInfo: AuctionInfo | undefined; + if (initialOfferToken === undefined || sourceChain === undefined) { + const { info } = await this.fetchAuction({ address: auction }); + if (info === null) { + throw new Error("no auction info found"); + } + auctionInfo = info; + + // Shouldn't be a problem. + if (!wormholeSdk.isChain(auctionInfo.sourceChain)) { + throw new Error("invalid source chain"); + } + sourceChain ??= auctionInfo.sourceChain; + initialOfferToken ??= auctionInfo.initialOfferToken; } - const sourceChain = info.sourceChain; - const initialOfferToken = inputInitialOfferToken ?? info.initialOfferToken; const payerSequence = this.payerSequenceAddress(payer); const payerSequenceValue = await this.fetchPayerSequenceValue({ @@ -1856,20 +1825,15 @@ export class MatchingEngineProgram { activeAuction: await this.activeAuctionComposite( { auction, - config: inputAuctionConfig, - bestOfferToken: inputBestOfferToken, + config: auctionConfig, + bestOfferToken, }, - { info }, + { auctionInfo }, ), - executorToken: - inputExecutorToken ?? - splToken.getAssociatedTokenAddressSync(this.mint, payer), + executorToken, initialOfferToken, }, - toRouterEndpoint: this.routerEndpointComposite( - inputToRouterEndpoint ?? - this.routerEndpointAddress(wormholeSdk.CHAIN_ID_SOLANA), - ), + toRouterEndpoint: this.routerEndpointComposite(toRouterEndpoint), wormhole: { config: coreBridgeConfig, emitterSequence: coreEmitterSequence, @@ -1883,18 +1847,18 @@ export class MatchingEngineProgram { async redeemFastFillAccounts( vaa: PublicKey, - sourceChain?: number, + sourceChain?: wormholeSdk.ChainId, ): Promise<{ vaaAccount: VaaAccount; accounts: RedeemFastFillAccounts }> { const vaaAccount = await VaaAccount.fetch(this.program.provider.connection, vaa); - sourceChain ??= (() => { + if (sourceChain === undefined) { const { fastFill } = LiquidityLayerMessage.decode(vaaAccount.payload()); if (fastFill === undefined) { throw new Error("Message not FastFill"); } - return fastFill.fill.sourceChain; - })(); + sourceChain = fastFill.fill.sourceChain; + } const localCustodyToken = this.localCustodyTokenAddress(sourceChain); @@ -1945,26 +1909,24 @@ export class MatchingEngineProgram { mint?: PublicKey; }, args: { - targetChain: number; + targetChain: wormholeSdk.ChainId; destinationCctpDomain?: number; }, ): Promise { - const { payer, mint: inputMint } = base; - const { targetChain, destinationCctpDomain: inputDestinationCctpDomain } = args; - - const destinationCctpDomain = await (async () => { - if (inputDestinationCctpDomain === undefined) { - const { - protocol: { cctp }, - } = await this.fetchRouterEndpoint(targetChain); - if (cctp === undefined) { - throw new Error("not CCTP endpoint"); - } - return cctp.domain; - } else { - return inputDestinationCctpDomain; + const { payer } = base; + const { targetChain } = args; + + let { mint } = base; + let { destinationCctpDomain } = args; + mint ??= this.mint; + + if (destinationCctpDomain === undefined) { + const { protocol } = await this.fetchRouterEndpoint(targetChain); + if (protocol.cctp === undefined) { + throw new Error("not CCTP endpoint"); } - })(); + destinationCctpDomain = protocol.cctp.domain; + } const { senderAuthority: tokenMessengerMinterSenderAuthority, @@ -1977,7 +1939,7 @@ export class MatchingEngineProgram { messageTransmitterProgram, tokenMessengerMinterProgram, } = this.tokenMessengerMinterProgram().depositForBurnWithCallerAccounts( - inputMint ?? this.mint, + mint, destinationCctpDomain, ); @@ -2092,19 +2054,21 @@ export class MatchingEngineProgram { async computeDepositPenalty( auctionInfo: AuctionInfo, - currentSlot: bigint, + currentSlot: Uint64, configId?: number, ): Promise<{ penalty: bigint; userReward: bigint }> { const auctionParams = await this.fetchAuctionParameters(configId); const gracePeriod = BigInt(auctionParams.gracePeriod); const slotsElapsed = - currentSlot - BigInt(auctionInfo.startSlot.toString()) - BigInt(auctionParams.duration); + uint64ToBigInt(currentSlot) - + uint64ToBigInt(auctionInfo.startSlot) - + BigInt(auctionParams.duration); if (slotsElapsed <= gracePeriod) { return { penalty: 0n, userReward: 0n }; } - const amount = BigInt(auctionInfo.securityDeposit.toString()); + const amount = uint64ToBigInt(auctionInfo.securityDeposit); const penaltyPeriod = slotsElapsed - gracePeriod; const auctionPenaltySlots = BigInt(auctionParams.penaltyPeriod); @@ -2124,18 +2088,18 @@ export class MatchingEngineProgram { } } - async computeMinOfferDelta(offerPrice: bigint): Promise { + async computeMinOfferDelta(offerPrice: Uint64): Promise { const { minOfferDeltaBps } = await this.fetchAuctionParameters(); - return (offerPrice * BigInt(minOfferDeltaBps)) / FEE_PRECISION_MAX; + return (uint64ToBigInt(offerPrice) * BigInt(minOfferDeltaBps)) / FEE_PRECISION_MAX; } - async computeNotionalSecurityDeposit(amountIn: bigint, configId?: number) { + async computeNotionalSecurityDeposit(amountIn: Uint64, configId?: number) { const { securityDepositBase, securityDepositBps } = await this.fetchAuctionParameters( configId, ); return ( uint64ToBigInt(securityDepositBase) + - (amountIn * BigInt(securityDepositBps)) / FEE_PRECISION_MAX + (uint64ToBigInt(amountIn) * BigInt(securityDepositBps)) / FEE_PRECISION_MAX ); } } diff --git a/solana/ts/src/matchingEngine/state/Proposal.ts b/solana/ts/src/matchingEngine/state/Proposal.ts index 05b1367b5..0ca8fa795 100644 --- a/solana/ts/src/matchingEngine/state/Proposal.ts +++ b/solana/ts/src/matchingEngine/state/Proposal.ts @@ -1,6 +1,7 @@ import { BN } from "@coral-xyz/anchor"; import { PublicKey } from "@solana/web3.js"; import { AuctionParameters } from "./AuctionConfig"; +import { Uint64, uint64ToBN, writeUint64BE } from "../../common"; export type ProposalAction = { none?: {}; @@ -25,23 +26,23 @@ export class Proposal { action: ProposalAction, by: PublicKey, owner: PublicKey, - slotProposedAt: BN, - slotEnactDelay: BN, - slotEnactedAt: BN | null, + slotProposedAt: Uint64, + slotEnactDelay: Uint64, + slotEnactedAt: Uint64 | null, ) { this.id = id; this.bump = bump; this.action = action; this.by = by; this.owner = owner; - this.slotProposedAt = slotProposedAt; - this.slotEnactDelay = slotEnactDelay; - this.slotEnactedAt = slotEnactedAt; + this.slotProposedAt = uint64ToBN(slotProposedAt); + this.slotEnactDelay = uint64ToBN(slotEnactDelay); + this.slotEnactedAt = slotEnactedAt === null ? null : uint64ToBN(slotEnactedAt); } - static address(programId: PublicKey, nextProposalId: BN) { + static address(programId: PublicKey, nextProposalId: Uint64) { const encodedProposalId = Buffer.alloc(8); - encodedProposalId.writeBigUInt64BE(BigInt(nextProposalId.toString())); + writeUint64BE(encodedProposalId, nextProposalId); return PublicKey.findProgramAddressSync( [Buffer.from("proposal"), encodedProposalId], diff --git a/solana/ts/src/tokenRouter/index.ts b/solana/ts/src/tokenRouter/index.ts index fd328e3a1..8ff5acde8 100644 --- a/solana/ts/src/tokenRouter/index.ts +++ b/solana/ts/src/tokenRouter/index.ts @@ -30,6 +30,7 @@ import { UpgradeManagerProgram } from "../upgradeManager"; import { BPF_LOADER_UPGRADEABLE_PROGRAM_ID, programDataAddress } from "../utils"; import { VaaAccount } from "../wormhole"; import { Custodian, PreparedFill, PreparedOrder } from "./state"; +import * as wormholeSdk from "@certusone/wormhole-sdk"; export const PROGRAM_IDS = [ "TokenRouter11111111111111111111111111111111", @@ -456,7 +457,7 @@ export class TokenRouterProgram { routerEndpoint?: PublicKey; }, args: { - targetChain?: number; + targetChain?: wormholeSdk.ChainId; destinationDomain?: number; } = {}, ): Promise { @@ -470,6 +471,10 @@ export class TokenRouterProgram { }); preparedBy ??= info.preparedBy; + + if (!wormholeSdk.isChain(info.targetChain)) { + throw new Error("Invalid chain found in prepared order"); + } targetChain ??= info.targetChain; } @@ -662,7 +667,7 @@ export class TokenRouterProgram { async redeemFastFillAccounts( vaa: PublicKey, - sourceChain?: number, + sourceChain?: wormholeSdk.ChainId, ): Promise { const { vaaAccount, diff --git a/solana/ts/src/wormhole/index.ts b/solana/ts/src/wormhole/index.ts index 4c3355c9f..5b6ef3426 100644 --- a/solana/ts/src/wormhole/index.ts +++ b/solana/ts/src/wormhole/index.ts @@ -1,3 +1,4 @@ +import * as wormholeSdk from "@certusone/wormhole-sdk"; import { parseVaa } from "@certusone/wormhole-sdk"; import { Connection, PublicKey } from "@solana/web3.js"; import { ethers } from "ethers"; @@ -16,13 +17,13 @@ export type PostedVaaV1 = { guardianSetIndex: number; nonce: number; sequence: bigint; - emitterChain: number; + emitterChain: wormholeSdk.ChainId; emitterAddress: Array; payload: Buffer; }; export type EmitterInfo = { - chain: number; + chain: wormholeSdk.ChainId; address: Array; sequence: bigint; }; @@ -64,6 +65,9 @@ export class VaaAccount { const sequence = data.readBigUInt64LE(offset); offset += 8; const emitterChain = data.readUInt16LE(offset); + if (!wormholeSdk.isChain(emitterChain)) { + throw new Error("invalid emitter chain"); + } offset += 2; const emitterAddress = Array.from(data.subarray(offset, (offset += 32))); const payloadLen = data.readUInt32LE(offset); @@ -92,7 +96,7 @@ export class VaaAccount { if (this._encodedVaa !== undefined) { const parsed = parseVaa(this._encodedVaa.buf); return { - chain: parsed.emitterChain, + chain: parsed.emitterChain as wormholeSdk.ChainId, address: Array.from(parsed.emitterAddress), sequence: parsed.sequence, }; diff --git a/solana/ts/tests/02__tokenRouter.ts b/solana/ts/tests/02__tokenRouter.ts index 732e17494..ac1c7c524 100644 --- a/solana/ts/tests/02__tokenRouter.ts +++ b/solana/ts/tests/02__tokenRouter.ts @@ -1023,7 +1023,7 @@ describe("Token Router", function () { }, { fill: { - sourceChain: wormholeSdk.CHAIN_ID_SOLANA as number, + sourceChain: wormholeSdk.CHAIN_ID_SOLANA, orderSender: Array.from(payer.publicKey.toBuffer()), redeemer, redeemerMessage, From 0797b84df28759af427e5deb9cc26d216a6aa21c Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Thu, 4 Apr 2024 10:58:50 -0500 Subject: [PATCH 5/8] solana: fix settle auction complete test --- solana/ts/tests/01__matchingEngine.ts | 59 ++++++++++++++++++++------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/solana/ts/tests/01__matchingEngine.ts b/solana/ts/tests/01__matchingEngine.ts index a774a93e4..9d15f8ecf 100644 --- a/solana/ts/tests/01__matchingEngine.ts +++ b/solana/ts/tests/01__matchingEngine.ts @@ -3170,8 +3170,15 @@ describe("Matching Engine", function () { ); }); - it.skip("Settle Completed with Penalty (Executor != Best Offer)", async function () { - // TODO + it("Settle Completed with Penalty (Executor != Best Offer)", async function () { + await settleAuctionCompleteForTest( + { + executor: playerTwo.publicKey, + }, + { + executeWithinGracePeriod: false, + }, + ); }); it("Settle Completed (Tx)", async function () { @@ -4249,17 +4256,25 @@ describe("Matching Engine", function () { }); const { bestOfferToken } = info!; + const executorToken = splToken.getAssociatedTokenAddressSync( + USDC_MINT_ADDRESS, + accounts.executor, + ); const { owner: bestOfferAuthority, amount: bestOfferTokenBalanceBefore } = await splToken.getAccount(connection, bestOfferToken); - const withinGracePeriod = opts.executeWithinGracePeriod ?? true; - if (withinGracePeriod) { + let executorTokenBalanceBefore: bigint | null = null; + if ( + (opts.executeWithinGracePeriod ?? true) || + accounts.executor.equals(bestOfferAuthority) + ) { expect(accounts.executor).to.eql(bestOfferAuthority); } else { - // Do anything here? + const { amount } = await splToken.getAccount(connection, executorToken); + executorTokenBalanceBefore = amount; } - const authorityLamportsBefore = await connection.getBalance(bestOfferAuthority); + const authorityLamportsBefore = await connection.getBalance(accounts.executor); const preparedCustodyToken = engine.preparedCustodyTokenAddress(preparedOrderResponse); const { amount: preparedCustodyBalanceBefore } = await splToken.getAccount( @@ -4289,11 +4304,27 @@ describe("Matching Engine", function () { connection, bestOfferToken, ); - expect(bestOfferTokenBalanceAfter).equals( - preparedCustodyBalanceBefore + bestOfferTokenBalanceBefore, - ); + const finalizedVaaAccount = await VaaAccount.fetch(connection, finalizedVaa); + const { deposit } = LiquidityLayerMessage.decode(finalizedVaaAccount.payload()); + const baseFee = deposit!.message.slowOrderResponse!.baseFee; - const authorityLamportsAfter = await connection.getBalance(bestOfferAuthority); + if (executorTokenBalanceBefore == null) { + expect(bestOfferTokenBalanceAfter).equals( + bestOfferTokenBalanceBefore + preparedCustodyBalanceBefore, + ); + } else { + expect(bestOfferTokenBalanceAfter).equals( + bestOfferTokenBalanceBefore + preparedCustodyBalanceBefore - baseFee, + ); + + const { amount: executorTokenBalanceAfter } = await splToken.getAccount( + connection, + executorToken, + ); + expect(executorTokenBalanceAfter).equals(executorTokenBalanceBefore + baseFee); + } + + const authorityLamportsAfter = await connection.getBalance(accounts.executor); expect(authorityLamportsAfter).equals( authorityLamportsBefore + preparedOrderLamports + preparedCustodyLamports, ); @@ -4302,16 +4333,14 @@ describe("Matching Engine", function () { address: auction, }); - const finalizedVaaAccount = await VaaAccount.fetch(connection, finalizedVaa); - const { deposit } = LiquidityLayerMessage.decode(finalizedVaaAccount.payload()); - const baseFee = uint64ToBN(deposit!.message.slowOrderResponse!.baseFee); - const executePenalty = statusBefore.completed!.executePenalty; expect(statusAfter).to.eql({ settled: { baseFee: uint64ToBN(baseFee), totalPenalty: - executePenalty !== null ? uint64ToBN(executePenalty.add(baseFee)) : null, + executePenalty !== null + ? uint64ToBN(executePenalty.add(uint64ToBN(baseFee))) + : null, }, }); From 4027d7030426b1e2908b3c1e249b13ccc7be7322 Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Thu, 4 Apr 2024 12:44:22 -0500 Subject: [PATCH 6/8] solana: clean up imports --- solana/ts/tests/01__matchingEngine.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/solana/ts/tests/01__matchingEngine.ts b/solana/ts/tests/01__matchingEngine.ts index 9d15f8ecf..2332dfe7e 100644 --- a/solana/ts/tests/01__matchingEngine.ts +++ b/solana/ts/tests/01__matchingEngine.ts @@ -8,11 +8,10 @@ import { Connection, Keypair, PublicKey, + Signer, SystemProgram, TransactionInstruction, VersionedTransactionResponse, - Signer, - AddressLookupTableAccount, } from "@solana/web3.js"; import { use as chaiUse, expect } from "chai"; import chaiAsPromised from "chai-as-promised"; @@ -59,8 +58,6 @@ import { waitUntilSlot, waitUntilTimestamp, } from "./helpers"; -import { join } from "path"; -import { Cctp } from "../src/cctp/messages"; chaiUse(chaiAsPromised); From 97df0a98bd0168b06a613ab11dd5ffe10e7b9cdb Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Thu, 4 Apr 2024 12:44:29 -0500 Subject: [PATCH 7/8] solana: consume fill tests --- solana/ts/src/tokenRouter/index.ts | 10 +- solana/ts/tests/02__tokenRouter.ts | 143 +++++++++++++++-------------- 2 files changed, 78 insertions(+), 75 deletions(-) diff --git a/solana/ts/src/tokenRouter/index.ts b/solana/ts/src/tokenRouter/index.ts index 8ff5acde8..f0337fefc 100644 --- a/solana/ts/src/tokenRouter/index.ts +++ b/solana/ts/src/tokenRouter/index.ts @@ -152,9 +152,9 @@ export class TokenRouterProgram { return splToken.getAssociatedTokenAddressSync(this.mint, this.custodianAddress(), true); } - preparedCustodyTokenAddress(preparedOrder: PublicKey): PublicKey { + preparedCustodyTokenAddress(preparedAccount: PublicKey): PublicKey { return PublicKey.findProgramAddressSync( - [Buffer.from("prepared-custody"), preparedOrder.toBuffer()], + [Buffer.from("prepared-custody"), preparedAccount.toBuffer()], this.ID, )[0]; } @@ -557,9 +557,9 @@ export class TokenRouterProgram { const msg = CctpTokenBurnMessage.from(cctpMessage); const cctpMintRecipient = this.cctpMintRecipientAddress(); - const vaaAcct = await VaaAccount.fetch(this.program.provider.connection, vaa); - const { chain } = vaaAcct.emitterInfo(); - const preparedFill = this.preparedFillAddress(vaaAcct.digest()); + const vaaAccount = await VaaAccount.fetch(this.program.provider.connection, vaa); + const { chain } = vaaAccount.emitterInfo(); + const preparedFill = this.preparedFillAddress(vaaAccount.digest()); const { authority: messageTransmitterAuthority, diff --git a/solana/ts/tests/02__tokenRouter.ts b/solana/ts/tests/02__tokenRouter.ts index ac1c7c524..02ba8360d 100644 --- a/solana/ts/tests/02__tokenRouter.ts +++ b/solana/ts/tests/02__tokenRouter.ts @@ -8,6 +8,7 @@ import { Keypair, PublicKey, SystemProgram, + TransactionInstruction, } from "@solana/web3.js"; import { use as chaiUse, expect } from "chai"; import chaiAsPromised from "chai-as-promised"; @@ -27,6 +28,8 @@ import { expectIxOk, postLiquidityLayerVaa, } from "./helpers"; +import { token } from "@coral-xyz/anchor/dist/cjs/utils"; +import { VaaAccount } from "../src/wormhole"; chaiUse(chaiAsPromised); @@ -1492,7 +1495,6 @@ describe("Token Router", function () { expect(localVariables.delete("args")).is.true; const vaa = localVariables.get("vaa") as PublicKey; - expect(localVariables.delete("vaa")).is.true; const ix = await tokenRouter.redeemCctpFillIx( { @@ -1510,89 +1512,90 @@ describe("Token Router", function () { }); // TODO: check prepared fill account. + + // Save for later. + localVariables.set("redeemIx", ix); }); - }); - describe("Consume Prepared Fill", function () { - const redeemer = Keypair.generate(); + it("Consume Prepared Fill after Redeem Fill", async function () { + const vaa = localVariables.get("vaa") as PublicKey; + expect(localVariables.delete("vaa")).is.true; - const localVariables = new Map(); + const someone = Keypair.generate(); + const dstToken = await splToken.createAssociatedTokenAccount( + connection, + payer, + USDC_MINT_ADDRESS, + someone.publicKey, + ); - it.skip("Redeem Fill (CCTP)", async function () { - // TODO - }); + const beneficiary = Keypair.generate().publicKey; - it.skip("Consume Prepared Fill after Redeem Fill (CCTP)", async function () { - // TODO - }); + const vaaAccount = await VaaAccount.fetch(connection, vaa); + const preparedFill = tokenRouter.preparedFillAddress(vaaAccount.digest()); - it.skip("Cannot Redeem Fill Again (CCTP)", async function () { - // TODO - }); + const expectedPreparedFillLamports = await connection + .getAccountInfo(preparedFill) + .then((info) => info!.lamports); - async function redeemFillCctp() { - const encodedMintRecipient = Array.from( - tokenRouter.cctpMintRecipientAddress().toBuffer(), + const custodyToken = tokenRouter.preparedCustodyTokenAddress(preparedFill); + const { amount: custodyTokenBalance } = await splToken.getAccount( + connection, + custodyToken, ); - const sourceCctpDomain = 0; - const cctpNonce = testCctpNonce++; - const amount = 69n; - - // Concoct a Circle message. - const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex")); - const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } = - await craftCctpTokenBurnMessage( - tokenRouter, - sourceCctpDomain, - cctpNonce, - encodedMintRecipient, - amount, - burnSource, - ); + const expectedCustodyTokenLamports = await connection + .getAccountInfo(custodyToken) + .then((info) => info!.lamports); - const message = new LiquidityLayerMessage({ - deposit: new LiquidityLayerDeposit( - { - tokenAddress: burnMessage.burnTokenAddress, - amount, - sourceCctpDomain, - destinationCctpDomain, - cctpNonce, - burnSource, - mintRecipient: encodedMintRecipient, - }, - { - fill: { - sourceChain: foreignChain, - orderSender: Array.from(Buffer.alloc(32, "d00d", "hex")), - redeemer: Array.from(redeemer.publicKey.toBuffer()), - redeemerMessage: Buffer.from("Somebody set up us the bomb"), - }, - }, - ), + const ix = await tokenRouter.consumePreparedFillIx({ + preparedFill, + redeemer: redeemer.publicKey, + dstToken, + beneficiary, }); - const vaa = await postLiquidityLayerVaa( - connection, - payer, - MOCK_GUARDIANS, - foreignEndpointAddress, - wormholeSequence++, - message, + await expectIxOk(connection, [ix], [payer, redeemer]); + + { + const accInfo = await connection.getAccountInfo(preparedFill); + expect(accInfo).is.null; + } + { + const accInfo = await connection.getAccountInfo(custodyToken); + expect(accInfo).is.null; + } + + const { amount: dstTokenBalance } = await splToken.getAccount(connection, dstToken); + expect(dstTokenBalance).equals(custodyTokenBalance); + + const beneficiaryBalance = await connection.getBalance(beneficiary); + expect(beneficiaryBalance).equals( + expectedPreparedFillLamports + expectedCustodyTokenLamports, ); - const redeemIx = await tokenRouter.redeemCctpFillIx( - { - payer: payer.publicKey, - vaa, - }, - { - encodedCctpMessage, - cctpAttestation, - }, + + // Save for later. + localVariables.set("consumeIx", ix); + }); + + it("Cannot Consume Fill Again", async function () { + const ix = localVariables.get("consumeIx") as TransactionInstruction; + expect(localVariables.delete("consumeIx")).is.true; + + await expectIxErr( + connection, + [ix], + [payer, redeemer], + "prepared_fill. Error Code: AccountNotInitialized", ); + }); - return { amount, message, vaa, redeemIx }; - } + it("Cannot Redeem Fill Again (CCTP)", async function () { + const ix = localVariables.get("redeemIx") as TransactionInstruction; + expect(localVariables.delete("redeemIx")).is.true; + + // NOTE: This is a CCTP message transmitter error. + await expectIxErr(connection, [ix], [payer], "Error Code: NonceAlreadyUsed"); + }); }); }); }); From a3ff062573474cca7e6f172f0dc0fbb3f91f0c58 Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Thu, 4 Apr 2024 12:53:10 -0500 Subject: [PATCH 8/8] solana: remove last skip --- solana/ts/tests/02__tokenRouter.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/solana/ts/tests/02__tokenRouter.ts b/solana/ts/tests/02__tokenRouter.ts index 02ba8360d..c95e40f77 100644 --- a/solana/ts/tests/02__tokenRouter.ts +++ b/solana/ts/tests/02__tokenRouter.ts @@ -707,8 +707,22 @@ describe("Token Router", function () { const localVariables = new Map(); - it.skip("Cannot Place Market Order without Prepared Order", async function () { - // TODO + it("Cannot Place Market Order without Prepared Order", async function () { + const ix = await tokenRouter.placeMarketOrderCctpIx( + { + payer: payer.publicKey, + preparedOrder: Keypair.generate().publicKey, + preparedBy: payer.publicKey, + }, + { targetChain: 23, destinationDomain: 3 }, + ); + + await expectIxErr( + connection, + [ix], + [payer], + "prepared_order. Error Code: AccountNotInitialized", + ); }); it("Prepare Market Order", async function () {