diff --git a/packages/solana-contracts/Anchor.toml b/packages/solana-contracts/Anchor.toml index 72cd8fa6a..c61d216e8 100644 --- a/packages/solana-contracts/Anchor.toml +++ b/packages/solana-contracts/Anchor.toml @@ -32,6 +32,7 @@ initialize_pool = "yarn run ts-node scripts/initializePool.ts" initialize_propeller = "yarn run ts-node scripts/initializePropeller.ts" scratch = "yarn run ts-node scripts/scratch.ts" add_to_pool = "yarn run ts-node scripts/addToPool.ts" +parse_account = "yarn run ts-node scripts/parseAccount.ts" [test] startup_wait = 100000 diff --git a/packages/solana-contracts/programs/propeller/src/constants.rs b/packages/solana-contracts/programs/propeller/src/constants.rs index 2b1c88f90..802141ad7 100644 --- a/packages/solana-contracts/programs/propeller/src/constants.rs +++ b/packages/solana-contracts/programs/propeller/src/constants.rs @@ -21,6 +21,15 @@ pub const PROPELLER_MINIMUM_OUTPUT_AMOUNT: u64 = 0u64; // pub const LAMPORTS_PER_SOL_DECIMAL: Decimal = Decimal::from_u64(1_000_000_000u64).unwrap(); pub const LAMPORTS_PER_SOL_DECIMAL: Decimal = Decimal::from_parts(1_000_000_000u32, 0, 0, false, 0u32); +#[cfg(all(feature = "localnet", not(feature = "devnet"), not(feature = "mainnet")))] +pub const TOKEN_BRIDGE_PROGRAM_ID: &str = "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE"; + +#[cfg(all(feature = "devnet", not(feature = "localnet"), not(feature = "mainnet")))] +pub const TOKEN_BRIDGE_PROGRAM_ID: &str = "DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe"; + +#[cfg(all(feature = "mainnet", not(feature = "localnet"), not(feature = "devnet")))] +pub const TOKEN_BRIDGE_PROGRAM_ID: &str = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"; + #[cfg(test)] mod test { use super::*; @@ -33,41 +42,3 @@ mod test { // println!("token_bridge_id2: {}", ID); } } -// // vaa payload types -// pub const PAYLOAD_SALE_INIT_SOLANA: u8 = 5; // 1 for everyone else -// pub const PAYLOAD_ATTEST_CONTRIBUTIONS: u8 = 2; -// pub const PAYLOAD_SALE_SEALED: u8 = 3; -// pub const PAYLOAD_SALE_ABORTED: u8 = 4; -// -// // universal -// pub const PAYLOAD_HEADER_LEN: usize = 33; // payload + sale id -// pub const INDEX_SALE_ID: usize = 1; -// -// // for sale init -// pub const INDEX_SALE_INIT_TOKEN_ADDRESS: usize = 33; -// pub const INDEX_SALE_INIT_TOKEN_CHAIN: usize = 65; -// pub const INDEX_SALE_INIT_TOKEN_DECIMALS: usize = 67; -// pub const INDEX_SALE_INIT_SALE_START: usize = 68; -// pub const INDEX_SALE_INIT_SALE_END: usize = 100; -// pub const INDEX_SALE_INIT_ACCEPTED_TOKENS_START: usize = 132; -// -// pub const ACCEPTED_TOKEN_NUM_BYTES: usize = 33; -// pub const ACCEPTED_TOKENS_MAX: usize = 8; -// pub const INDEX_ACCEPTED_TOKEN_INDEX: usize = 0; -// pub const INDEX_ACCEPTED_TOKEN_ADDRESS: usize = 1; -// pub const INDEX_ACCEPTED_TOKEN_END: usize = 33; -// -// // for attest contributions -// pub const ATTEST_CONTRIBUTIONS_ELEMENT_LEN: usize = 33; // token index + amount -// -// // for sale sealed -// pub const INDEX_SALE_SEALED_ALLOCATIONS_START: usize = 33; -// -// pub const ALLOCATION_NUM_BYTES: usize = 65; -// pub const INDEX_ALLOCATIONS_AMOUNT: usize = 1; -// pub const INDEX_ALLOCATIONS_EXCESS: usize = 33; -// pub const INDEX_ALLOCATIONS_END: usize = 65; - -// misc -// pub const PAD_U8: usize = 31; -// pub const PAD_U64: usize = 24; diff --git a/packages/solana-contracts/programs/propeller/src/error.rs b/packages/solana-contracts/programs/propeller/src/error.rs index 18b4963b6..91f9135f1 100644 --- a/packages/solana-contracts/programs/propeller/src/error.rs +++ b/packages/solana-contracts/programs/propeller/src/error.rs @@ -72,7 +72,7 @@ pub enum PropellerError { #[msg("Not a valid Switchboard account")] InvalidSwitchboardAccount, - #[msg("Switchboard feed has not been updated in 5 minutes")] + #[msg("Switchboard feed value is stale ")] StaleFeed, #[msg("Switchboard feed exceeded provided confidence interval")] @@ -81,29 +81,35 @@ pub enum PropellerError { #[msg("Insufficient Amount being transferred")] InsufficientAmount, + #[msg("Invalid Wormhole Claim Account")] + InvalidWormholeClaimAccount, + #[msg("Invalid claim data")] InvalidClaimData, #[msg("Claim Account not claimed")] ClaimNotClaimed, - #[msg("Invalid Propeller Admin")] - InvalidPropellerAdmin, + #[msg("Invalid Propeller GovernanceKey")] + InvalidPropellerGovernanceKey, + + #[msg("Invalid Propeller Pause Key")] + InvalidPropellerPauseKey, - #[msg("Invalid Pool for Token Id Map")] - InvalidTokenIdMapPool, + #[msg("Invalid Pool for Token Number Map")] + InvalidTokenNumberMapPool, #[msg("Invalid Output Token Index")] InvalidOutputTokenIndex, - #[msg("Invalid Pool Token Index for Token Id Map")] - InvalidTokenIdMapPoolTokenIndex, + #[msg("Invalid Pool Token Index for Token Number Map")] + InvalidTokenNumberMapPoolTokenIndex, - #[msg("Invalid Pool Token Mint for Token Id Map")] - InvalidTokenIdMapPoolTokenMint, + #[msg("Invalid Pool Token Mint for Token Number Map")] + InvalidTokenNumberMapPoolTokenMint, - #[msg("Invalid Pool Ix for Token Id Map")] - InvalidTokenIdMapPoolIx, + #[msg("Invalid To Token Step for Token Number Map")] + InvalidTokenNumberMapToTokenStep, #[msg("Invalid Gas Kickstart parameter in Swim Payload")] InvalidSwimPayloadGasKickstart, @@ -144,11 +150,8 @@ pub enum PropellerError { #[msg("Owner of token account != swimPayload.owner")] IncorrectOwnerForCreateTokenAccount, - #[msg("TokenIdMap exists. Please use the correct instruction")] - TokenIdMapExists, - - #[msg("Invalid address for TokenIdMap account")] - InvalidTokenIdMapAccountAddress, + #[msg("TokenNumberMap exists. Please use the correct instruction")] + TokenNumberMapExists, #[msg("Invalid Swim Payload version")] InvalidSwimPayloadVersion, @@ -158,4 +161,19 @@ pub enum PropellerError { #[msg("Invalid Fee Vault")] InvalidFeeVault, + + #[msg("Invalid Memo")] + InvalidMemo, + + #[msg("ToTokenNumber does not match SwimPayload.to_tokenNumber")] + ToTokenNumberMismatch, + + #[msg("Routing Contract is paused")] + IsPaused, + + #[msg("Target Chain is paused")] + TargetChainIsPaused, + + #[msg("Invalid SwimPayloadMessagePayer")] + InvalidSwimPayloadMessagePayer, } diff --git a/packages/solana-contracts/programs/propeller/src/fees.rs b/packages/solana-contracts/programs/propeller/src/fees.rs index 6ca08b179..ae6ff024e 100644 --- a/packages/solana-contracts/programs/propeller/src/fees.rs +++ b/packages/solana-contracts/programs/propeller/src/fees.rs @@ -1,7 +1,7 @@ use {crate::TOKEN_COUNT, anchor_lang::prelude::*, two_pool::BorshDecimal}; pub trait Fees<'info> { - /// Calculating the fees, including txn and rent exmptions, in lamports. + /// Calculate the fees, including txn and rent exemptions, in lamports. fn calculate_fees_in_lamports(&self) -> Result; fn get_marginal_prices(&self) -> Result<[BorshDecimal; TOKEN_COUNT]>; fn convert_fees_to_swim_usd_atomic(&self, fee_in_lamports: u64) -> Result; diff --git a/packages/solana-contracts/programs/propeller/src/instructions/create_owner_token_accounts.rs b/packages/solana-contracts/programs/propeller/src/instructions/create_owner_token_accounts.rs index 79fdee0b4..52d814e12 100644 --- a/packages/solana-contracts/programs/propeller/src/instructions/create_owner_token_accounts.rs +++ b/packages/solana-contracts/programs/propeller/src/instructions/create_owner_token_accounts.rs @@ -1,16 +1,15 @@ pub use switchboard_v2::{AggregatorAccountData, SwitchboardDecimal, SWITCHBOARD_PROGRAM_ID}; use { crate::{ - constants::LAMPORTS_PER_SOL_DECIMAL, - convert_fees_to_swim_usd_atomic, convert_fees_to_swim_usd_atomic_2, deserialize_message_payload, + constants::{LAMPORTS_PER_SOL_DECIMAL, TOKEN_BRIDGE_PROGRAM_ID}, + convert_fees_to_swim_usd_atomic, deserialize_message_payload, error::*, - get_lamports_intermediate_token_price, get_marginal_price_decimal, get_message_data, + get_lamports_intermediate_token_price, get_marginal_price_decimal, get_memo_as_utf8, get_message_data, get_swim_usd_mint_decimals, get_transfer_with_payload_from_message_account, hash_vaa, state::{SwimClaim, SwimPayloadMessage, *}, token_bridge::TokenBridge, - token_id_map::{PoolInstruction, TokenIdMap}, - ClaimData, FeeTracker, PayloadTransferWithPayload, PostVAAData, PostedVAAData, Propeller, RawSwimPayload, - TOKEN_COUNT, + token_number_map::{ToTokenStep, TokenNumberMap}, + ClaimData, FeeTracker, Fees, PayloadTransferWithPayload, PostVAAData, PostedVAAData, Propeller, TOKEN_COUNT, }, anchor_lang::{prelude::*, solana_program::program::invoke}, anchor_spl::{ @@ -20,7 +19,7 @@ use { num_traits::{FromPrimitive, ToPrimitive}, rust_decimal::Decimal, solana_program::{instruction::Instruction, program::invoke_signed}, - std::convert::TryInto, + std::{convert::TryInto, iter::zip}, two_pool::{state::TwoPool, BorshDecimal}, }; @@ -29,7 +28,9 @@ pub struct PropellerCreateOwnerTokenAccounts<'info> { #[account( seeds = [ b"propeller".as_ref(), propeller.swim_usd_mint.as_ref()], bump = propeller.bump, - has_one = aggregator @ PropellerError::InvalidAggregator + has_one = aggregator @ PropellerError::InvalidAggregator, + has_one = marginal_price_pool @ PropellerError::InvalidMarginalPricePool, + constraint = !propeller.is_paused @ PropellerError::IsPaused, )] pub propeller: Box>, #[account(mut)] @@ -74,7 +75,7 @@ pub struct PropellerCreateOwnerTokenAccounts<'info> { swim_payload_message.vaa_sequence.to_be_bytes().as_ref(), ], bump, - seeds::program = propeller.token_bridge().unwrap() + seeds::program = propeller.token_bridge().unwrap(), )] /// CHECK: WH Claim account pub claim: UncheckedAccount<'info>, @@ -86,7 +87,8 @@ pub struct PropellerCreateOwnerTokenAccounts<'info> { b"swim_payload".as_ref(), claim.key().as_ref(), ], - bump = swim_payload_message.bump + bump = swim_payload_message.bump, + has_one = claim, )] pub swim_payload_message: Box>, @@ -97,9 +99,10 @@ pub struct PropellerCreateOwnerTokenAccounts<'info> { propeller.key().as_ref(), &swim_payload_message.target_token_id.to_le_bytes() ], - bump = token_id_map.bump, + bump = token_number_map.bump, + has_one = pool @ PropellerError::InvalidTokenNumberMapPool, )] - pub token_id_map: Box>, + pub token_number_map: Box>, #[account( mut, @@ -113,8 +116,11 @@ pub struct PropellerCreateOwnerTokenAccounts<'info> { seeds::program = two_pool_program.key() )] pub pool: Box>, + #[account(address = pool.token_mint_keys[0])] pub pool_token_0_mint: Box>, + #[account(address = pool.token_mint_keys[1])] pub pool_token_1_mint: Box>, + #[account(address = pool.lp_mint_key)] pub pool_lp_mint: Box>, #[account(address = swim_payload_message.owner)] @@ -176,9 +182,9 @@ pub struct PropellerCreateOwnerTokenAccounts<'info> { impl<'info> PropellerCreateOwnerTokenAccounts<'info> { pub fn accounts(ctx: &Context) -> Result<()> { require_keys_eq!(ctx.accounts.user.key(), ctx.accounts.swim_payload_message.owner); - require_keys_eq!(ctx.accounts.pool.key(), ctx.accounts.token_id_map.pool); + require_keys_eq!(ctx.accounts.pool.key(), ctx.accounts.token_number_map.pool); let propeller = &ctx.accounts.propeller; - validate_marginal_prices_pool_accounts_2( + validate_marginal_prices_pool_accounts( &propeller, &ctx.accounts.marginal_price_pool, &[&ctx.accounts.marginal_price_pool_token_0_account, &ctx.accounts.marginal_price_pool_token_1_account], @@ -187,52 +193,69 @@ impl<'info> PropellerCreateOwnerTokenAccounts<'info> { Ok(()) } - fn into_marginal_prices(&self) -> CpiContext<'_, '_, '_, 'info, two_pool::cpi::accounts::MarginalPrices<'info>> { - let program = self.two_pool_program.to_account_info(); - let accounts = two_pool::cpi::accounts::MarginalPrices { - pool: self.marginal_price_pool.to_account_info(), - pool_token_account_0: self.marginal_price_pool_token_0_account.to_account_info(), - pool_token_account_1: self.marginal_price_pool_token_1_account.to_account_info(), - lp_mint: self.marginal_price_pool_lp_mint.to_account_info(), - }; - CpiContext::new(program, accounts) + fn initialize_user_ata_and_get_fees( + &self, + user_unchecked_token_account: &UncheckedAccount<'info>, + mint: &Account<'info, Mint>, + ) -> Result { + let ata_data_len = user_unchecked_token_account.data_len(); + + let payer = &self.payer; + let user = &self.user; + let system_program = &self.system_program; + let token_program = &self.token_program; + + if ata_data_len == TokenAccount::LEN { + let token_account = + TokenAccount::try_deserialize(&mut &**user_unchecked_token_account.data.try_borrow_mut().unwrap())?; + require_keys_eq!(token_account.owner, user.key(), PropellerError::IncorrectOwnerForCreateTokenAccount); + return Ok(0u64); + } else if ata_data_len != 0 { + //TODO: spl_token_2022? + // panic!("data_len != 0 && != TokenAcount::LEN"); + return err!(PropellerError::InvalidTokenAccountDataLen); + } else { + let ix = spl_associated_token_account::instruction::create_associated_token_account( + &payer.key(), + &user.key(), + &mint.key(), + ); + invoke( + &ix, + &[ + payer.to_account_info(), + user_unchecked_token_account.to_account_info(), + user.to_account_info(), + mint.to_account_info(), + system_program.to_account_info(), + token_program.to_account_info(), + ], + )?; + let fees = Rent::get()?.minimum_balance(TokenAccount::LEN) + self.propeller.init_ata_fee; + Ok(fees) + } } + fn log_memo(&self) -> Result<()> { + let memo = self.swim_payload_message.memo; + if memo != [0u8; 16] { + let memo_ix = spl_memo::build_memo(get_memo_as_utf8(memo)?.as_ref(), &[]); + invoke(&memo_ix, &[self.memo.to_account_info()])?; + } + Ok(()) + } +} + +impl<'info> Fees<'info> for PropellerCreateOwnerTokenAccounts<'info> { fn calculate_fees_in_lamports(&self) -> Result { let mut create_owner_token_account_total_fees_in_lamports = 0u64; - let init_ata_fee = self.propeller.init_ata_fee; - let token_program = self.token_program.to_account_info(); - let payer = self.payer.to_account_info(); - let user = self.user.to_account_info(); - let system_program = self.system_program.to_account_info(); - let init_token_account_0_fees = initialize_user_ata_and_get_fees( - self.user_pool_token_0_account.to_account_info().clone(), - payer.clone(), - user.clone(), - self.pool_token_0_mint.to_account_info().clone(), - system_program.clone(), - token_program.clone(), - init_ata_fee, - )?; - let init_token_account_1_fees = initialize_user_ata_and_get_fees( - self.user_pool_token_1_account.to_account_info().clone(), - payer.clone(), - user.clone(), - self.pool_token_1_mint.to_account_info().clone(), - system_program.clone(), - token_program.clone(), - init_ata_fee, - )?; - let init_lp_token_account_fees = initialize_user_ata_and_get_fees( - self.user_lp_token_account.to_account_info().clone(), - payer.clone(), - user.clone(), - self.pool_lp_mint.to_account_info().clone(), - system_program.clone(), - token_program.clone(), - init_ata_fee, - )?; + let init_token_account_0_fees = + self.initialize_user_ata_and_get_fees(&self.user_pool_token_0_account, &self.pool_token_0_mint)?; + let init_token_account_1_fees = + self.initialize_user_ata_and_get_fees(&self.user_pool_token_1_account, &self.pool_token_1_mint)?; + let init_lp_token_account_fees = + self.initialize_user_ata_and_get_fees(&self.user_lp_token_account, &self.pool_lp_mint)?; create_owner_token_account_total_fees_in_lamports = init_token_account_0_fees .checked_add(init_token_account_1_fees) .and_then(|f| f.checked_add(init_lp_token_account_fees)) @@ -253,36 +276,83 @@ impl<'info> PropellerCreateOwnerTokenAccounts<'info> { Ok(create_owner_token_account_total_fees_in_lamports) } + fn get_marginal_prices(&self) -> Result<[BorshDecimal; TOKEN_COUNT]> { + let result = two_pool::cpi::marginal_prices(CpiContext::new( + self.two_pool_program.to_account_info(), + two_pool::cpi::accounts::MarginalPrices { + pool: self.marginal_price_pool.to_account_info(), + pool_token_account_0: self.marginal_price_pool_token_0_account.to_account_info(), + pool_token_account_1: self.marginal_price_pool_token_1_account.to_account_info(), + lp_mint: self.marginal_price_pool_lp_mint.to_account_info(), + }, + ))?; + Ok(result.get()) + } + + fn convert_fees_to_swim_usd_atomic(&self, fee_in_lamports: u64) -> Result { + msg!("fee_in_lamports: {:?}", fee_in_lamports); + let marginal_price_pool_lp_mint = &self.marginal_price_pool_lp_mint; + + let propeller = &self.propeller; + let max_staleness = propeller.max_staleness; + let swim_usd_mint_key = propeller.swim_usd_mint; + // let marginal_prices = get_marginal_prices(cpi_ctx)?; + + let intermediate_token_price_decimal: Decimal = get_marginal_price_decimal( + &self.marginal_price_pool, + &self.get_marginal_prices()?, + propeller, + &marginal_price_pool_lp_mint.key(), + )?; + + msg!("intermediate_token_price_decimal: {:?}", intermediate_token_price_decimal); + + let fee_in_lamports_decimal = Decimal::from_u64(fee_in_lamports).ok_or(PropellerError::ConversionError)?; + msg!("fee_in_lamports(u64): {:?} fee_in_lamports_decimal: {:?}", fee_in_lamports, fee_in_lamports_decimal); + + let lamports_intermediate_token_price = get_lamports_intermediate_token_price(&self.aggregator, max_staleness)?; + let fee_in_swim_usd_decimal = lamports_intermediate_token_price + .checked_mul(fee_in_lamports_decimal) + .and_then(|x| x.checked_div(intermediate_token_price_decimal)) + .ok_or(PropellerError::IntegerOverflow)?; + + let swim_usd_decimals = + get_swim_usd_mint_decimals(&swim_usd_mint_key, &self.marginal_price_pool, &marginal_price_pool_lp_mint)?; + msg!("swim_usd_decimals: {:?}", swim_usd_decimals); + + let ten_pow_decimals = + Decimal::from_u64(10u64.pow(swim_usd_decimals as u32)).ok_or(PropellerError::IntegerOverflow)?; + let fee_in_swim_usd_atomic = fee_in_swim_usd_decimal + .checked_mul(ten_pow_decimals) + .and_then(|v| v.to_u64()) + .ok_or(PropellerError::ConversionError)?; + + msg!( + "fee_in_swim_usd_decimal: {:?} fee_in_swim_usd_atomic: {:?}", + fee_in_swim_usd_decimal, + fee_in_swim_usd_atomic + ); + Ok(fee_in_swim_usd_atomic) + } + fn track_and_transfer_fees(&mut self, fees_in_swim_usd: u64) -> Result<()> { let fee_tracker = &mut self.fee_tracker; - let updated_fees_owed = + fee_tracker.fees_owed = fee_tracker.fees_owed.checked_add(fees_in_swim_usd).ok_or(PropellerError::IntegerOverflow)?; - fee_tracker.fees_owed = updated_fees_owed; - let cpi_accounts = Transfer { - from: self.redeemer_escrow.to_account_info(), - to: self.fee_vault.to_account_info(), - authority: self.redeemer.to_account_info(), - }; token::transfer( CpiContext::new_with_signer( self.token_program.to_account_info(), - cpi_accounts, + Transfer { + from: self.redeemer_escrow.to_account_info(), + to: self.fee_vault.to_account_info(), + authority: self.redeemer.to_account_info(), + }, &[&[&b"redeemer".as_ref(), &[self.propeller.redeemer_bump]]], ), fees_in_swim_usd, ) } - - fn log_memo(&self) -> Result<()> { - let memo = self.swim_payload_message.memo; - if memo != [0u8; 16] { - let memo_ix = - spl_memo::build_memo(std::str::from_utf8(hex::encode(memo).as_bytes()).unwrap().as_ref(), &[]); - invoke(&memo_ix, &[self.memo.to_account_info()])?; - } - Ok(()) - } } //TODO: allow this regardless of gasKickstart or only if gasKickstart? @@ -291,33 +361,6 @@ impl<'info> PropellerCreateOwnerTokenAccounts<'info> { /// we penalize the engine by not reimbursing them anything in that situation so that they are incentivized to /// check if any of the require token accounts don't exist. pub fn handle_propeller_create_owner_token_accounts(ctx: Context) -> Result<()> { - //TODO: enforce that this step can only be done after CompleteNativeWithPayload is done? - // -> no way to have a `swim_payload_message` if not done yet. - - // let claim_data = ClaimData::try_from_slice(&mut ctx.accounts.claim.data.borrow()) - // .map_err(|_| error!(PropellerError::InvalidClaimData))?; - // require!(claim_data.claimed, PropellerError::ClaimNotClaimed); - - //TODO: check `vaa.to` is this program's address? - // let payload_transfer_with_payload = - // get_transfer_with_payload_from_message_account(&ctx.accounts.message.to_account_info())?; - // msg!("message_data_payload: {:?}", payload_transfer_with_payload); - // let PayloadTransferWithPayload { - // message_type, - // amount, - // token_address, - // token_chain, - // to, - // to_chain, - // from_address, - // payload, - // } = payload_transfer_with_payload; - // //TODO: do i need to re-check this? - // // any issue in doing so? - // msg!("payload_transfer_with_payload.to: {:?}", to); - // let to_pubkey = Pubkey::new_from_array(to); - // require_keys_eq!(to_pubkey, crate::ID); - let create_owner_token_account_total_fees_in_lamports = ctx.accounts.calculate_fees_in_lamports()?; if create_owner_token_account_total_fees_in_lamports == 0 { //TODO: log memo still? @@ -325,99 +368,31 @@ pub fn handle_propeller_create_owner_token_accounts(ctx: Context( - user_unchecked_token_account: AccountInfo<'info>, - payer: AccountInfo<'info>, - user: AccountInfo<'info>, - mint: AccountInfo<'info>, - system_program: AccountInfo<'info>, - token_program: AccountInfo<'info>, - create_ata_fee: u64, -) -> Result { - //TODO: figure out actual cost of create ata txn. - let create_ata_fee = 10000u64; - let ata_data_len = user_unchecked_token_account.data_len(); - if ata_data_len == TokenAccount::LEN { - let token_account = - TokenAccount::try_deserialize(&mut &**user_unchecked_token_account.data.try_borrow_mut().unwrap())?; - require_keys_eq!(token_account.owner, user.key(), PropellerError::IncorrectOwnerForCreateTokenAccount); - return Ok(0u64); - } else if ata_data_len != 0 { - //TODO: spl_token_2022? - // panic!("data_len != 0 && != TokenAcount::LEN"); - return err!(PropellerError::InvalidTokenAccountDataLen); - } else { - let ix = spl_associated_token_account::instruction::create_associated_token_account( - &payer.key(), - &user.key(), - &mint.key(), + ctx.accounts.track_and_transfer_fees(create_owner_token_account_total_fees_in_swim_usd)?; + + let transfer_amount = ctx.accounts.swim_payload_message.transfer_amount; + let new_transfer_amount = transfer_amount + .checked_sub(create_owner_token_account_total_fees_in_swim_usd) + .ok_or(error!(PropellerError::InsufficientFunds))?; + + msg!( + "transfer_amount: {} - fees_in_swim_usd: {} = {}", + transfer_amount, + create_owner_token_account_total_fees_in_swim_usd, + new_transfer_amount ); - invoke(&ix, &[payer, user_unchecked_token_account, user, mint, system_program, token_program])?; - let fees = Rent::get()?.minimum_balance(TokenAccount::LEN) + create_ata_fee; - Ok(fees) + ctx.accounts.swim_payload_message.transfer_amount = new_transfer_amount; } + + ctx.accounts.log_memo()?; + Ok(()) } #[derive(Accounts)] @@ -427,6 +402,8 @@ pub struct PropellerCreateOwnerSwimUsdAta<'info> { bump = propeller.bump, has_one = swim_usd_mint @ PropellerError::InvalidSwimUsdMint, has_one = aggregator @ PropellerError::InvalidAggregator, + has_one = marginal_price_pool @ PropellerError::InvalidMarginalPricePool, + constraint = !propeller.is_paused @ PropellerError::IsPaused, )] pub propeller: Box>, #[account(mut)] @@ -554,46 +531,28 @@ pub struct PropellerCreateOwnerSwimUsdAta<'info> { impl<'info> PropellerCreateOwnerSwimUsdAta<'info> { pub fn accounts(ctx: &Context) -> Result<()> { require_keys_eq!(ctx.accounts.owner.key(), ctx.accounts.swim_payload_message.owner); - // let (expected_token_id_map_address, _bump) = Pubkey::find_program_address( - // &[ - // b"propeller".as_ref(), - // b"token_id".as_ref(), - // ctx.accounts.propeller.key().as_ref(), - // ctx.accounts.swim_payload_message.target_token_id.to_le_bytes().as_ref(), - // ], - // ctx.program_id, - // ); - // //Note: the address should at least be valid even though it doesn't exist. - // require_keys_eq!(expected_token_id_map_address, ctx.accounts.token_id_map.key()); - TokenIdMap::assert_is_invalid(&ctx.accounts.token_id_map.to_account_info())?; - // let token_id_map = &ctx.accounts.token_id_map; - // if let Ok(_) = TokenIdMap::try_deserialize(&mut &**token_id_map.try_borrow_mut_data()?) { - // return err!(PropellerError::TokenIdMapExists); - // } + TokenNumberMap::assert_is_invalid(&ctx.accounts.token_id_map.to_account_info())?; let propeller = &ctx.accounts.propeller; validate_marginal_prices_pool_accounts( &propeller, - &ctx.accounts.marginal_price_pool.key(), - &[ - ctx.accounts.marginal_price_pool_token_0_account.mint, - ctx.accounts.marginal_price_pool_token_1_account.mint, - ], + &ctx.accounts.marginal_price_pool, + &[&ctx.accounts.marginal_price_pool_token_0_account, &ctx.accounts.marginal_price_pool_token_1_account], )?; msg!("Passed PropellerCreateOwnerTokenAccounts::accounts() check"); Ok(()) } - fn into_marginal_prices(&self) -> CpiContext<'_, '_, '_, 'info, two_pool::cpi::accounts::MarginalPrices<'info>> { - let program = self.two_pool_program.to_account_info(); - let accounts = two_pool::cpi::accounts::MarginalPrices { - pool: self.marginal_price_pool.to_account_info(), - pool_token_account_0: self.marginal_price_pool_token_0_account.to_account_info(), - pool_token_account_1: self.marginal_price_pool_token_1_account.to_account_info(), - lp_mint: self.marginal_price_pool_lp_mint.to_account_info(), - }; - CpiContext::new(program, accounts) + fn log_memo(&self) -> Result<()> { + let memo = self.swim_payload_message.memo; + if memo != [0u8; 16] { + let memo_ix = spl_memo::build_memo(get_memo_as_utf8(memo)?.as_ref(), &[]); + invoke(&memo_ix, &[self.memo.to_account_info()])?; + } + Ok(()) } +} +impl<'info> Fees<'info> for PropellerCreateOwnerSwimUsdAta<'info> { fn calculate_fees_in_lamports(&self) -> Result { let fees_in_lamports = Rent::get()? .minimum_balance(TokenAccount::LEN) @@ -602,21 +561,78 @@ impl<'info> PropellerCreateOwnerSwimUsdAta<'info> { Ok(fees_in_lamports) } - pub fn handle_fees(&mut self, fees_in_swim_usd: u64) -> Result<()> { + fn get_marginal_prices(&self) -> Result<[BorshDecimal; TOKEN_COUNT]> { + let result = two_pool::cpi::marginal_prices(CpiContext::new( + self.two_pool_program.to_account_info(), + two_pool::cpi::accounts::MarginalPrices { + pool: self.marginal_price_pool.to_account_info(), + pool_token_account_0: self.marginal_price_pool_token_0_account.to_account_info(), + pool_token_account_1: self.marginal_price_pool_token_1_account.to_account_info(), + lp_mint: self.marginal_price_pool_lp_mint.to_account_info(), + }, + ))?; + Ok(result.get()) + } + + fn convert_fees_to_swim_usd_atomic(&self, fee_in_lamports: u64) -> Result { + msg!("fee_in_lamports: {:?}", fee_in_lamports); + let marginal_price_pool_lp_mint = &self.marginal_price_pool_lp_mint; + + let propeller = &self.propeller; + let max_staleness = propeller.max_staleness; + let swim_usd_mint_key = propeller.swim_usd_mint; + // let marginal_prices = get_marginal_prices(cpi_ctx)?; + + let intermediate_token_price_decimal: Decimal = get_marginal_price_decimal( + &self.marginal_price_pool, + &self.get_marginal_prices()?, + propeller, + &marginal_price_pool_lp_mint.key(), + )?; + + msg!("intermediate_token_price_decimal: {:?}", intermediate_token_price_decimal); + + let fee_in_lamports_decimal = Decimal::from_u64(fee_in_lamports).ok_or(PropellerError::ConversionError)?; + msg!("fee_in_lamports(u64): {:?} fee_in_lamports_decimal: {:?}", fee_in_lamports, fee_in_lamports_decimal); + + let lamports_intermediate_token_price = get_lamports_intermediate_token_price(&self.aggregator, max_staleness)?; + let fee_in_swim_usd_decimal = lamports_intermediate_token_price + .checked_mul(fee_in_lamports_decimal) + .and_then(|x| x.checked_div(intermediate_token_price_decimal)) + .ok_or(PropellerError::IntegerOverflow)?; + + let swim_usd_decimals = + get_swim_usd_mint_decimals(&swim_usd_mint_key, &self.marginal_price_pool, &marginal_price_pool_lp_mint)?; + msg!("swim_usd_decimals: {:?}", swim_usd_decimals); + + let ten_pow_decimals = + Decimal::from_u64(10u64.pow(swim_usd_decimals as u32)).ok_or(PropellerError::IntegerOverflow)?; + let fee_in_swim_usd_atomic = fee_in_swim_usd_decimal + .checked_mul(ten_pow_decimals) + .and_then(|v| v.to_u64()) + .ok_or(PropellerError::ConversionError)?; + + msg!( + "fee_in_swim_usd_decimal: {:?} fee_in_swim_usd_atomic: {:?}", + fee_in_swim_usd_decimal, + fee_in_swim_usd_atomic + ); + Ok(fee_in_swim_usd_atomic) + } + + fn track_and_transfer_fees(&mut self, fees_in_swim_usd: u64) -> Result<()> { let fee_tracker = &mut self.fee_tracker; - let updated_fees_owed = + fee_tracker.fees_owed = fee_tracker.fees_owed.checked_add(fees_in_swim_usd).ok_or(PropellerError::IntegerOverflow)?; - fee_tracker.fees_owed = updated_fees_owed; - let cpi_accounts = Transfer { - from: self.redeemer_escrow.to_account_info(), - to: self.fee_vault.to_account_info(), - authority: self.redeemer.to_account_info(), - }; token::transfer( CpiContext::new_with_signer( self.token_program.to_account_info(), - cpi_accounts, + Transfer { + from: self.redeemer_escrow.to_account_info(), + to: self.fee_vault.to_account_info(), + authority: self.redeemer.to_account_info(), + }, &[&[&b"redeemer".as_ref(), &[self.propeller.redeemer_bump]]], ), fees_in_swim_usd, @@ -625,41 +641,28 @@ impl<'info> PropellerCreateOwnerSwimUsdAta<'info> { } pub fn handle_propeller_create_owner_swim_usd_ata(ctx: Context) -> Result<()> { - let fees_in_lamports = ctx.accounts.calculate_fees_in_lamports()?; - // let fees_in_lamports = Rent::get()? - // .minimum_balance(TokenAccount::LEN) - // .checked_add(ctx.accounts.propeller.init_ata_fee) - // .ok_or(PropellerError::IntegerOverflow)?; - // let init_token_bridge_ata_total_fee_in_token_bridge_mint = - // ctx.accounts.convert_fees_to_swim_usd_atomic(fee_in_lamports)?; - let marginal_prices = two_pool::cpi::marginal_prices(ctx.accounts.into_marginal_prices())?; - let fees_in_swim_usd_atomic = convert_fees_to_swim_usd_atomic_2( - fees_in_lamports, - &ctx.accounts.propeller, - &ctx.accounts.marginal_price_pool_lp_mint, - marginal_prices.get(), - &ctx.accounts.marginal_price_pool, - &ctx.accounts.aggregator, - i64::MAX, - )?; - ctx.accounts.handle_fees(fees_in_swim_usd_atomic)?; - - let transfer_amount = ctx.accounts.swim_payload_message.transfer_amount; - let new_transfer_amount = - transfer_amount.checked_sub(fees_in_swim_usd_atomic).ok_or(error!(PropellerError::InsufficientFunds))?; - - msg!( - "transfer_amount: {} - fees_in_swim_usd_atomic: {} = {}", - transfer_amount, - fees_in_swim_usd_atomic, - new_transfer_amount - ); - ctx.accounts.swim_payload_message.transfer_amount = new_transfer_amount; - - let memo = ctx.accounts.swim_payload_message.memo; - if memo != [0u8; 16] { - let memo_ix = spl_memo::build_memo(std::str::from_utf8(hex::encode(memo).as_bytes()).unwrap().as_ref(), &[]); - invoke(&memo_ix, &[ctx.accounts.memo.to_account_info()])?; + let create_user_swim_usd_ata_fees_in_lamports = ctx.accounts.calculate_fees_in_lamports()?; + let swim_payload_owner = ctx.accounts.swim_payload_message.owner; + + if swim_payload_owner != ctx.accounts.payer.key() { + let create_user_swim_usd_ata_fees_in_swim_usd_atomic = + ctx.accounts.convert_fees_to_swim_usd_atomic(create_user_swim_usd_ata_fees_in_lamports)?; + + ctx.accounts.track_and_transfer_fees(create_user_swim_usd_ata_fees_in_swim_usd_atomic)?; + + let transfer_amount = ctx.accounts.swim_payload_message.transfer_amount; + let new_transfer_amount = transfer_amount + .checked_sub(create_user_swim_usd_ata_fees_in_swim_usd_atomic) + .ok_or(error!(PropellerError::InsufficientFunds))?; + + msg!( + "transfer_amount: {} - fees_in_swim_usd: {} = {}", + transfer_amount, + create_user_swim_usd_ata_fees_in_swim_usd_atomic, + new_transfer_amount + ); + ctx.accounts.swim_payload_message.transfer_amount = new_transfer_amount; } + ctx.accounts.log_memo()?; Ok(()) } diff --git a/packages/solana-contracts/programs/propeller/src/instructions/governance/gov.rs b/packages/solana-contracts/programs/propeller/src/instructions/governance/gov.rs new file mode 100644 index 000000000..481254a30 --- /dev/null +++ b/packages/solana-contracts/programs/propeller/src/instructions/governance/gov.rs @@ -0,0 +1,23 @@ +use { + crate::{error::*, Propeller}, + anchor_lang::prelude::*, +}; +#[derive(Accounts)] +pub struct Governance<'info> { + #[account( + seeds = [ + b"propeller".as_ref(), + propeller.swim_usd_mint.as_ref(), + ], + bump = propeller.bump, + has_one = governance_key @ PropellerError::InvalidPropellerGovernanceKey, + )] + pub propeller: Account<'info, Propeller>, + pub governance_key: Signer<'info>, +} + +pub fn handle_prepare_governance_change(ctx: Context) -> Result<()> { + let propeller = &mut ctx.accounts.propeller; + propeller.governance_key = *ctx.accounts.governance_key.key; + Ok(()) +} diff --git a/packages/solana-contracts/programs/propeller/src/instructions/governance/mod.rs b/packages/solana-contracts/programs/propeller/src/instructions/governance/mod.rs new file mode 100644 index 000000000..3a2131296 --- /dev/null +++ b/packages/solana-contracts/programs/propeller/src/instructions/governance/mod.rs @@ -0,0 +1,4 @@ +pub use {gov::*, pause::*}; + +mod gov; +mod pause; diff --git a/packages/solana-contracts/programs/propeller/src/instructions/governance/pause.rs b/packages/solana-contracts/programs/propeller/src/instructions/governance/pause.rs new file mode 100644 index 000000000..f48a405f9 --- /dev/null +++ b/packages/solana-contracts/programs/propeller/src/instructions/governance/pause.rs @@ -0,0 +1,48 @@ +use { + crate::{error::*, Propeller}, + anchor_lang::prelude::*, +}; + +/// Accounts needed for all pause-related ixs +#[derive(Accounts)] +pub struct SetPaused<'info> { + #[account( + mut, + seeds = [ + b"propeller".as_ref(), + propeller.swim_usd_mint.as_ref(), + ], + bump = propeller.bump, + has_one = pause_key @ PropellerError::InvalidPropellerPauseKey, + )] + pub propeller: Box>, + pub pause_key: Signer<'info>, +} + +#[derive(Accounts)] +pub struct ChangePauseKey<'info> { + #[account( + mut, + seeds = [ + b"propeller".as_ref(), + propeller.swim_usd_mint.as_ref(), + ], + bump = propeller.bump, + has_one = governance_key @ PropellerError::InvalidPropellerGovernanceKey, + )] + pub propeller: Box>, + pub governance_key: Signer<'info>, + pub new_pause_key: Signer<'info>, +} + +pub fn handle_set_paused(ctx: Context, is_paused: bool) -> Result<()> { + let propeller = &mut ctx.accounts.propeller; + propeller.is_paused = is_paused; + Ok(()) +} + +pub fn handle_change_pause_key(ctx: Context) -> Result<()> { + let propeller = &mut ctx.accounts.propeller; + propeller.pause_key = ctx.accounts.new_pause_key.key(); + Ok(()) +} diff --git a/packages/solana-contracts/programs/propeller/src/instructions/initialize.rs b/packages/solana-contracts/programs/propeller/src/instructions/initialize.rs index c985d478e..a8e02645c 100644 --- a/packages/solana-contracts/programs/propeller/src/instructions/initialize.rs +++ b/packages/solana-contracts/programs/propeller/src/instructions/initialize.rs @@ -41,7 +41,9 @@ pub struct Initialize<'info> { )] pub propeller_fee_vault: Box>, - pub admin: Signer<'info>, + pub governance_key: Signer<'info>, + /// CHECK: pause_key + pub pause_key: Signer<'info>, pub swim_usd_mint: Box>, #[account(mut)] @@ -109,13 +111,14 @@ pub struct InitializeParams { pub fn handle_initialize(ctx: Context, params: InitializeParams) -> Result<()> { // let pool = &ctx.accounts.pool; // let mint0 = pool.get_token_mint_0().unwrap(); + msg!("in handle_initialize"); let propeller = &mut ctx.accounts.propeller; propeller.bump = *ctx.bumps.get("propeller").unwrap(); - propeller.nonce = 0; - propeller.admin = ctx.accounts.admin.key(); - //TODO: these should be passed in as params or read based on features used when deploying? - propeller.wormhole = propeller.wormhole()?; - propeller.token_bridge = propeller.token_bridge()?; + propeller.is_paused = false; + propeller.governance_key = ctx.accounts.governance_key.key(); + propeller.prepared_governance_key = Pubkey::default(); + propeller.governance_transition_ts = 0; + propeller.pause_key = ctx.accounts.pause_key.key(); propeller.swim_usd_mint = ctx.accounts.swim_usd_mint.key(); propeller.sender_bump = *ctx.bumps.get("propeller_sender").unwrap(); @@ -136,7 +139,6 @@ pub fn handle_initialize(ctx: Context, params: InitializeParams) -> // propeller.evm_routing_contract_address = params.evm_routing_contract_address; propeller.fee_vault = ctx.accounts.propeller_fee_vault.key(); propeller.aggregator = ctx.accounts.aggregator.key(); + propeller.max_staleness = params.max_staleness; Ok(()) } - -// pub fn init_redeemer_escrow diff --git a/packages/solana-contracts/programs/propeller/src/instructions/mod.rs b/packages/solana-contracts/programs/propeller/src/instructions/mod.rs index 7918a3a3f..02ebebf75 100644 --- a/packages/solana-contracts/programs/propeller/src/instructions/mod.rs +++ b/packages/solana-contracts/programs/propeller/src/instructions/mod.rs @@ -1,6 +1,6 @@ pub use { - create_owner_token_accounts::*, fee_tracker::*, initialize::*, process_swim_payload::*, target_chain_map::*, - token_id_map::*, utils::*, wormhole::*, + create_owner_token_accounts::*, fee_tracker::*, governance::*, initialize::*, process_swim_payload::*, + target_chain_map::*, token_number_map::*, utils::*, wormhole::*, }; pub mod process_swim_payload; @@ -9,8 +9,9 @@ pub mod initialize; // pub mod pool; pub mod create_owner_token_accounts; pub mod fee_tracker; +pub mod governance; pub mod target_chain_map; -pub mod token_id_map; +pub mod token_number_map; pub mod two_pool_cpi; pub mod utils; pub mod wormhole; diff --git a/packages/solana-contracts/programs/propeller/src/instructions/process_swim_payload.rs b/packages/solana-contracts/programs/propeller/src/instructions/process_swim_payload.rs index efe499840..790fb4337 100644 --- a/packages/solana-contracts/programs/propeller/src/instructions/process_swim_payload.rs +++ b/packages/solana-contracts/programs/propeller/src/instructions/process_swim_payload.rs @@ -13,13 +13,12 @@ use { hash_vaa, state::{SwimClaim, SwimPayloadMessage, *}, token_bridge::TokenBridge, - token_id_map::{PoolInstruction, TokenIdMap}, + token_number_map::{ToTokenStep, TokenNumberMap}, ClaimData, PayloadTransferWithPayload, PostVAAData, PostedVAAData, Propeller, - RawSwimPayload, TOKEN_COUNT, }, anchor_lang::system_program, @@ -38,18 +37,19 @@ use { two_pool::state::TwoPool, }; use { - crate::{convert_fees_to_swim_usd_atomic_2, get_lamports_intermediate_token_price, Fees}, + crate::{get_lamports_intermediate_token_price, get_memo_as_utf8, Fees}, two_pool::BorshDecimal, }; pub const SWIM_USD_TO_TOKEN_NUMBER: u16 = 0; #[derive(Accounts)] -#[instruction(target_token_id: u16)] +#[instruction(to_token_number: u16)] pub struct ProcessSwimPayload<'info> { #[account( seeds = [ b"propeller".as_ref(), propeller.swim_usd_mint.as_ref()], - bump = propeller.bump + bump = propeller.bump, + constraint = !propeller.is_paused @ PropellerError::IsPaused, )] pub propeller: Box>, #[account(mut)] @@ -88,8 +88,8 @@ pub struct ProcessSwimPayload<'info> { claim.key().as_ref(), ], bump = swim_payload_message.bump, - has_one = swim_payload_message_payer, - has_one = claim, + has_one = swim_payload_message_payer @ PropellerError::InvalidSwimPayloadMessagePayer, + has_one = claim @ PropellerError::InvalidWormholeClaimAccount, )] pub swim_payload_message: Box>, @@ -117,11 +117,12 @@ pub struct ProcessSwimPayload<'info> { b"propeller".as_ref(), b"token_id".as_ref(), propeller.key().as_ref(), - &target_token_id.to_le_bytes() + &to_token_number.to_le_bytes() ], - bump = token_id_map.bump, + bump = token_number_map.bump, + has_one = pool @ PropellerError::InvalidTokenNumberMapPool, )] - pub token_id_map: Account<'info, TokenIdMap>, + pub token_number_map: Account<'info, TokenNumberMap>, /* Pool Used for final swap to get token_id_map.pool_token_mint */ #[account( @@ -139,18 +140,18 @@ pub struct ProcessSwimPayload<'info> { #[account( mut, - token::mint = pool.token_mint_keys[0], - token::authority = pool, + address = get_associated_token_address(&pool.key(), &pool_token_account_0.mint), + constraint = pool.token_keys[0] == pool_token_account_0.key(), )] pub pool_token_account_0: Box>, #[account( mut, - token::mint = pool.token_mint_keys[1], - token::authority = pool, + address = get_associated_token_address(&pool.key(), &pool_token_account_1.mint), + constraint = pool.token_keys[1] == pool_token_account_1.key(), )] pub pool_token_account_1: Box>, - #[account(mut)] + #[account(mut, address = pool.lp_mint_key)] pub lp_mint: Box>, #[account( mut, @@ -190,8 +191,8 @@ impl<'info> ProcessSwimPayload<'info> { require_keys_eq!(ctx.accounts.swim_payload_message.claim.key(), ctx.accounts.claim.key()); require_keys_eq!( ctx.accounts.pool.key(), - ctx.accounts.token_id_map.pool, - PropellerError::InvalidTokenIdMapPool + ctx.accounts.token_number_map.pool, + PropellerError::InvalidTokenNumberMapPool ); Ok(()) } @@ -200,29 +201,21 @@ impl<'info> ProcessSwimPayload<'info> { // verify claim // verify message require_keys_eq!(self.swim_payload_message.claim.key(), self.claim.key()); - require_keys_eq!(self.pool.key(), self.token_id_map.pool, PropellerError::InvalidTokenIdMapPool); + require_keys_eq!(self.pool.key(), self.token_number_map.pool, PropellerError::InvalidTokenNumberMapPool); Ok(()) } - pub fn transfer_tokens( - &self, - output_token_index: u16, - transfer_amount: u64, - min_output_amount: u64, - ) -> Result { - let token_id_mapping = &self.token_id_map; - let pool_ix = &token_id_mapping.pool_ix; + pub fn transfer_tokens(&self, to_token_number: u16, transfer_amount: u64, min_output_amount: u64) -> Result { + let token_id_mapping = &self.token_number_map; + let to_token_step = &token_id_mapping.to_token_step; let pool_token_mint = &token_id_mapping.pool_token_mint; let pool_token_index = token_id_mapping.pool_token_index; - //TODO: decide if using user_transfer_auth - // remove user_transfer_authority account if not. - - self.execute_transfer_or_pool_ix( + self.execute_to_token_step( transfer_amount, min_output_amount, - output_token_index, - pool_ix, + to_token_number, + to_token_step, pool_token_index, pool_token_mint, &self.redeemer.to_account_info(), @@ -230,12 +223,12 @@ impl<'info> ProcessSwimPayload<'info> { ) } - fn execute_transfer_or_pool_ix( + fn execute_to_token_step( &self, transfer_amount: u64, min_output_amount: u64, output_token_index: u16, - pool_ix: &PoolInstruction, + to_token_step: &ToTokenStep, pool_token_index: u8, pool_token_mint: &Pubkey, user_transfer_authority: &AccountInfo<'info>, @@ -243,8 +236,8 @@ impl<'info> ProcessSwimPayload<'info> { ) -> Result { let swim_payload_owner = self.swim_payload_message.owner; require_gt!(TOKEN_COUNT, pool_token_index as usize); - match pool_ix { - PoolInstruction::RemoveExactBurn => { + match to_token_step { + ToTokenStep::RemoveExactBurn => { msg!("Executing RemoveExactBurn"); require_keys_eq!(self.pool.token_mint_keys[pool_token_index as usize], *pool_token_mint); @@ -272,7 +265,7 @@ impl<'info> ProcessSwimPayload<'info> { )? .get()) } - PoolInstruction::SwapExactInput => { + ToTokenStep::SwapExactInput => { msg!("Executing SwapExactInput"); require_keys_eq!(self.pool.token_mint_keys[pool_token_index as usize], *pool_token_mint); @@ -298,7 +291,7 @@ impl<'info> ProcessSwimPayload<'info> { )? .get()) } - PoolInstruction::Transfer => { + ToTokenStep::Transfer => { require_eq!(output_token_index, SWIM_USD_TO_TOKEN_NUMBER, PropellerError::InvalidOutputTokenIndex); self.transfer_swim_usd_tokens(transfer_amount, user_transfer_authority, signer_seeds) } @@ -337,7 +330,7 @@ impl<'info> ProcessSwimPayload<'info> { pub fn handle_process_swim_payload( ctx: Context, - target_token_id: u16, + to_token_number: u16, min_output_amount: u64, ) -> Result { let propeller_message = &ctx.accounts.swim_payload_message; @@ -353,10 +346,11 @@ pub fn handle_process_swim_payload( let token_program = &ctx.accounts.token_program; msg!("transfer_amount: {}", transfer_amount); - let output_amount = ctx.accounts.transfer_tokens(target_token_id, transfer_amount, min_output_amount)?; + let output_amount = ctx.accounts.transfer_tokens(to_token_number, transfer_amount, min_output_amount)?; let swim_claim_bump = *ctx.bumps.get("swim_claim").unwrap(); ctx.accounts.init_swim_claim(swim_claim_bump)?; + msg!("output_amount: {}", output_amount); Ok(output_amount) } @@ -437,8 +431,16 @@ pub struct PropellerProcessSwimPayload<'info> { } impl<'info> PropellerProcessSwimPayload<'info> { - pub fn accounts(ctx: &Context, target_token_id: u16) -> Result<()> { - ctx.accounts.validate()?; + pub fn accounts(ctx: &Context, to_token_number: u16) -> Result<()> { + require_keys_eq!( + ctx.accounts.process_swim_payload.swim_payload_message.claim.key(), + ctx.accounts.process_swim_payload.claim.key() + ); + require_keys_eq!( + ctx.accounts.process_swim_payload.pool.key(), + ctx.accounts.process_swim_payload.token_number_map.pool, + PropellerError::InvalidTokenNumberMapPool + ); require_keys_eq!( ctx.accounts.process_swim_payload.propeller.aggregator, ctx.accounts.aggregator.key(), @@ -446,36 +448,19 @@ impl<'info> PropellerProcessSwimPayload<'info> { ); require_eq!( ctx.accounts.process_swim_payload.swim_payload_message.target_token_id, - target_token_id, - // PropellerError::InvalidTargetTokenId + to_token_number, + PropellerError::ToTokenNumberMismatch ); + let propeller = &ctx.accounts.process_swim_payload.propeller; validate_marginal_prices_pool_accounts( - &ctx.accounts.process_swim_payload.propeller, - &ctx.accounts.marginal_price_pool.key(), - &[ - ctx.accounts.marginal_price_pool_token_0_account.mint, - ctx.accounts.marginal_price_pool_token_1_account.mint, - ], + &propeller, + &ctx.accounts.marginal_price_pool, + &[&ctx.accounts.marginal_price_pool_token_0_account, &ctx.accounts.marginal_price_pool_token_1_account], )?; msg!("Finished PropellerProcessSwimPayload::accounts()"); Ok(()) } - pub fn validate(&self) -> Result<()> { - // verify claim - // verify message - require_keys_eq!( - self.process_swim_payload.swim_payload_message.claim.key(), - self.process_swim_payload.claim.key() - ); - require_keys_eq!( - self.process_swim_payload.pool.key(), - self.process_swim_payload.token_id_map.pool, - PropellerError::InvalidTokenIdMapPool - ); - Ok(()) - } - fn transfer_gas_kickstart(&self) -> Result<()> { let propeller = &self.process_swim_payload.propeller; let owner_account = &self.owner.to_account_info(); @@ -503,11 +488,8 @@ impl<'info> PropellerProcessSwimPayload<'info> { fn log_memo(&self) -> Result<()> { let memo = self.process_swim_payload.swim_payload_message.memo; if memo != [0u8; 16] { - let memo_ix = - spl_memo::build_memo(std::str::from_utf8(hex::encode(memo).as_bytes()).unwrap().as_ref(), &[]); + let memo_ix = spl_memo::build_memo(get_memo_as_utf8(memo)?.as_ref(), &[]); invoke(&memo_ix, &[self.memo.to_account_info()])?; - } else { - msg!("memo is empty"); } Ok(()) } @@ -645,7 +627,7 @@ TODO: */ pub fn handle_propeller_process_swim_payload( ctx: Context, - target_token_id: u16, + to_token_number: u16, ) -> Result { let swim_payload_message = &ctx.accounts.process_swim_payload.swim_payload_message; let is_gas_kickstart = swim_payload_message.gas_kickstart; @@ -672,9 +654,6 @@ pub fn handle_propeller_process_swim_payload( } transfer_amount = transfer_amount.checked_sub(fees_in_swim_usd_atomic).ok_or(error!(PropellerError::InsufficientFunds))?; - } else { - //TODO: a user should just call processSwimPayload instead to avoid passing in extra accounts. end result is same. - msg!("swim_payload_owner == ctx.accounts.payer.key(). Owner bypass"); } msg!("transfer_amount - fee = {}", transfer_amount); @@ -736,9 +715,9 @@ pub struct PropellerProcessSwimPayloadFallback<'info> { claim.key().as_ref(), ], bump = swim_payload_message.bump, - has_one = swim_payload_message_payer, + has_one = swim_payload_message_payer @ PropellerError::InvalidSwimPayloadMessagePayer, + has_one = claim @ PropellerError::InvalidWormholeClaimAccount, has_one = owner, - has_one = claim, )] pub swim_payload_message: Box>, #[account(mut)] @@ -769,10 +748,10 @@ pub struct PropellerProcessSwimPayloadFallback<'info> { ], bump, )] - /// CHECK: Unchecked b/c if target_token_id is invalid then this account should not exist/be able to be - /// deserialized as a `TokenIdMap`. if it does exist, then engine should have called + /// CHECK: Unchecked b/c if `to_token_number` is invalid then this account should not exist/be able to be + /// deserialized as a `TokenNumberMap`. if it does exist, then engine should have called /// propeller_create_owner_token_accounts instead - pub token_id_map: UncheckedAccount<'info>, + pub token_number_map: UncheckedAccount<'info>, #[account( mut, @@ -841,11 +820,8 @@ impl<'info> PropellerProcessSwimPayloadFallback<'info> { require_keys_eq!(ctx.accounts.owner.key(), ctx.accounts.swim_payload_message.owner); validate_marginal_prices_pool_accounts( &ctx.accounts.propeller, - &ctx.accounts.marginal_price_pool.key(), - &[ - ctx.accounts.marginal_price_pool_token_0_account.mint, - ctx.accounts.marginal_price_pool_token_1_account.mint, - ], + &ctx.accounts.marginal_price_pool, + &[&ctx.accounts.marginal_price_pool_token_0_account, &ctx.accounts.marginal_price_pool_token_1_account], )?; msg!("Passed PropellerProcessSwimPayloadFallback::accounts() check"); Ok(()) @@ -903,8 +879,7 @@ impl<'info> PropellerProcessSwimPayloadFallback<'info> { fn log_memo(&self) -> Result<()> { let memo = self.swim_payload_message.memo; if memo != [0u8; 16] { - let memo_ix = - spl_memo::build_memo(std::str::from_utf8(hex::encode(memo).as_bytes()).unwrap().as_ref(), &[]); + let memo_ix = spl_memo::build_memo(get_memo_as_utf8(memo)?.as_ref(), &[]); invoke(&memo_ix, &[self.memo.to_account_info()])?; } Ok(()) @@ -1048,9 +1023,6 @@ pub fn handle_propeller_process_swim_payload_fallback( } transfer_amount = transfer_amount.checked_sub(fees_in_swim_usd_atomic).ok_or(error!(PropellerError::InsufficientFunds))?; - } else { - //TODO: a user should just call processSwimPayload instead to avoid passing in extra accounts. end result is same. - msg!("swim_payload_owner == ctx.accounts.payer.key(). Owner bypass"); } msg!("transfer_amount - fee = {}", transfer_amount); diff --git a/packages/solana-contracts/programs/propeller/src/instructions/target_chain_map.rs b/packages/solana-contracts/programs/propeller/src/instructions/target_chain_map.rs index e59621982..e0afe3c85 100644 --- a/packages/solana-contracts/programs/propeller/src/instructions/target_chain_map.rs +++ b/packages/solana-contracts/programs/propeller/src/instructions/target_chain_map.rs @@ -14,11 +14,11 @@ pub struct CreateTargetChainMap<'info> { #[account( seeds = [b"propeller".as_ref(), propeller.swim_usd_mint.as_ref()], bump = propeller.bump, - has_one = admin, + has_one = governance_key @ PropellerError::InvalidPropellerGovernanceKey, )] pub propeller: Account<'info, Propeller>, - pub admin: Signer<'info>, + pub governance_key: Signer<'info>, #[account(mut)] pub payer: Signer<'info>, @@ -42,10 +42,11 @@ pub struct TargetChainMap { pub bump: u8, pub target_chain: u16, pub target_address: [u8; 32], + pub is_paused: bool, } impl TargetChainMap { - pub const LEN: usize = 1 + 2 + 32; + pub const LEN: usize = 1 + 2 + 32 + 1; } pub fn handle_create_target_chain_map( @@ -58,6 +59,7 @@ pub fn handle_create_target_chain_map( target_chain_map.bump = *bump; target_chain_map.target_chain = target_chain; target_chain_map.target_address = target_address; + target_chain_map.is_paused = false; Ok(()) } @@ -66,11 +68,11 @@ pub struct UpdateTargetChainMap<'info> { #[account( seeds = [b"propeller".as_ref(), propeller.swim_usd_mint.as_ref()], bump = propeller.bump, - has_one = admin, + has_one = governance_key @ PropellerError::InvalidPropellerGovernanceKey, )] - pub propeller: Account<'info, Propeller>, + pub propeller: Box>, - pub admin: Signer<'info>, + pub governance_key: Signer<'info>, #[account(mut)] pub payer: Signer<'info>, @@ -84,7 +86,6 @@ pub struct UpdateTargetChainMap<'info> { bump = target_chain_map.bump, )] pub target_chain_map: Account<'info, TargetChainMap>, - pub system_program: Program<'info, System>, } pub fn handle_update_target_chain_map(ctx: Context, routing_contract: [u8; 32]) -> Result<()> { @@ -96,3 +97,35 @@ pub fn handle_update_target_chain_map(ctx: Context, routin pub fn handle_close_target_chain_map() -> Result<()> { todo!() } + +#[derive(Accounts)] +pub struct TargetChainMapSetPaused<'info> { + #[account( + seeds = [b"propeller".as_ref(), propeller.swim_usd_mint.as_ref()], + bump = propeller.bump, + has_one = pause_key @ PropellerError::InvalidPropellerPauseKey, + )] + pub propeller: Account<'info, Propeller>, + + pub pause_key: Signer<'info>, + + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + mut, + seeds = [ + b"propeller".as_ref(), + propeller.key().as_ref(), + &target_chain_map.target_chain.to_le_bytes() + ], + bump = target_chain_map.bump, + )] + pub target_chain_map: Account<'info, TargetChainMap>, +} + +pub fn handle_target_chain_map_set_paused(ctx: Context, is_paused: bool) -> Result<()> { + let target_chain_map = &mut ctx.accounts.target_chain_map; + target_chain_map.is_paused = is_paused; + Ok(()) +} diff --git a/packages/solana-contracts/programs/propeller/src/instructions/token_id_map.rs b/packages/solana-contracts/programs/propeller/src/instructions/token_id_map.rs deleted file mode 100644 index 2cf358ea2..000000000 --- a/packages/solana-contracts/programs/propeller/src/instructions/token_id_map.rs +++ /dev/null @@ -1,139 +0,0 @@ -use { - crate::{error::PropellerError, Propeller, TOKEN_COUNT}, - anchor_lang::prelude::*, - anchor_spl::{ - associated_token::{create, AssociatedToken, Create}, - token::{Mint, Token, TokenAccount}, - }, - two_pool::state::TwoPool, -}; - -#[derive(Accounts)] -#[instruction(target_token_index: u16, pool: Pubkey, pool_token_index: u8, pool_token_mint: Pubkey)] -pub struct CreateTokenIdMap<'info> { - #[account( - seeds = [b"propeller".as_ref(), propeller.swim_usd_mint.as_ref()], - bump = propeller.bump, - has_one = admin @ PropellerError::InvalidPropellerAdmin, - )] - pub propeller: Account<'info, Propeller>, - - pub admin: Signer<'info>, - - #[account(mut)] - pub payer: Signer<'info>, - - #[account( - seeds = [ - b"two_pool".as_ref(), - pool.get_token_mint_0().unwrap().as_ref(), - pool.get_token_mint_1().unwrap().as_ref(), - pool.lp_mint_key.as_ref(), - ], - bump = pool.bump, - seeds::program = two_pool_program.key(), - )] - pub pool: Account<'info, TwoPool>, - - #[account( - init, - payer = payer, - seeds = [ - b"propeller".as_ref(), - b"token_id".as_ref(), - propeller.key().as_ref(), - &target_token_index.to_le_bytes() - ], - bump, - space = 8 + TokenIdMap::LEN, - )] - pub token_id_map: Account<'info, TokenIdMap>, - pub system_program: Program<'info, System>, - pub two_pool_program: Program<'info, two_pool::program::TwoPool>, -} - -#[account] -pub struct TokenIdMap { - pub output_token_index: u16, - pub pool: Pubkey, - pub pool_token_index: u8, - pub pool_token_mint: Pubkey, - pub pool_ix: PoolInstruction, - pub bump: u8, -} - -impl TokenIdMap { - pub const LEN: usize = 2 + 32 + 1 + 32 + 1 + 1 + 1; - - pub fn assert_is_invalid(token_id_map: &AccountInfo) -> Result<()> { - if let Ok(_) = TokenIdMap::try_deserialize(&mut &**token_id_map.try_borrow_mut_data()?) { - return err!(PropellerError::TokenIdMapExists); - } - Ok(()) - } -} - -#[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, Debug)] -pub enum PoolInstruction { - Transfer, - RemoveExactBurn, - SwapExactInput, -} - -impl<'info> CreateTokenIdMap<'info> { - pub fn accounts( - ctx: &Context, - target_token_index: u16, - pool: Pubkey, - pool_token_index: u8, - pool_token_mint: Pubkey, - pool_ix: PoolInstruction, - ) -> Result<()> { - //TODO: add error codes - require_keys_eq!(ctx.accounts.propeller.admin, ctx.accounts.admin.key(), PropellerError::InvalidPropellerAdmin); - require_keys_eq!(ctx.accounts.pool.key(), pool, PropellerError::InvalidTokenIdMapPool); - if let PoolInstruction::Transfer = pool_ix { - require_keys_eq!( - ctx.accounts.propeller.swim_usd_mint, - pool_token_mint, - PropellerError::InvalidTokenIdMapPoolTokenMint - ); - return Ok(()); - } - - let pool_token_index = pool_token_index as usize; - require_gt!(TOKEN_COUNT, pool_token_index, PropellerError::InvalidTokenIdMapPoolTokenIndex); - require_keys_eq!( - ctx.accounts.pool.token_mint_keys[pool_token_index], - pool_token_mint, - PropellerError::InvalidTokenIdMapPoolTokenMint - ); - Ok(()) - } -} - -pub fn handle_create_token_id_map( - ctx: Context, - target_token_index: u16, - pool: Pubkey, - pool_token_index: u8, - pool_token_mint: Pubkey, - pool_ix: PoolInstruction, -) -> Result<()> { - let mut token_id_map = &mut ctx.accounts.token_id_map; - token_id_map.output_token_index = target_token_index; - token_id_map.pool = pool; - token_id_map.pool_token_index = pool_token_index; - token_id_map.pool_token_mint = pool_token_mint; - token_id_map.bump = *ctx.bumps.get("token_id_map").unwrap(); - token_id_map.pool_ix = pool_ix; - Ok(()) -} - -pub fn handle_update_token_id_map() -> Result<()> { - todo!() -} - -pub fn handle_close_token_id_map() -> Result<()> { - todo!() -} diff --git a/packages/solana-contracts/programs/propeller/src/instructions/token_number_map.rs b/packages/solana-contracts/programs/propeller/src/instructions/token_number_map.rs new file mode 100644 index 000000000..06f0c1f9e --- /dev/null +++ b/packages/solana-contracts/programs/propeller/src/instructions/token_number_map.rs @@ -0,0 +1,276 @@ +use { + crate::{error::PropellerError, Propeller, TOKEN_COUNT}, + anchor_lang::prelude::*, + anchor_spl::{ + associated_token::{create, AssociatedToken, Create}, + token::{Mint, Token, TokenAccount}, + }, + two_pool::state::TwoPool, +}; + +#[derive(Accounts)] +#[instruction(to_token_number: u16, pool: Pubkey, pool_token_index: u8, pool_token_mint: Pubkey)] +pub struct CreateTokenNumberMap<'info> { + #[account( + seeds = [b"propeller".as_ref(), propeller.swim_usd_mint.as_ref()], + bump = propeller.bump, + has_one = governance_key @ PropellerError::InvalidPropellerGovernanceKey, + )] + pub propeller: Box>, + + pub governance_key: Signer<'info>, + + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + seeds = [ + b"two_pool".as_ref(), + pool.token_mint_keys[0].as_ref(), + pool.token_mint_keys[1].as_ref(), + pool.lp_mint_key.as_ref(), + ], + bump = pool.bump, + seeds::program = two_pool_program.key(), + )] + pub pool: Box>, + + #[account( + init, + payer = payer, + seeds = [ + b"propeller".as_ref(), + b"token_id".as_ref(), + propeller.key().as_ref(), + &to_token_number.to_le_bytes() + ], + bump, + space = 8 + TokenNumberMap::LEN, + )] + pub token_number_map: Account<'info, TokenNumberMap>, + pub system_program: Program<'info, System>, + pub two_pool_program: Program<'info, two_pool::program::TwoPool>, +} + +#[account] +pub struct TokenNumberMap { + pub bump: u8, + pub to_token_number: u16, + pub pool: Pubkey, + pub pool_token_index: u8, + pub pool_token_mint: Pubkey, + pub to_token_step: ToTokenStep, +} + +impl TokenNumberMap { + pub const LEN: usize = 2 + 32 + 1 + 32 + 1 + 1 + 1; + + pub fn assert_is_invalid(token_id_map: &AccountInfo) -> Result<()> { + if let Ok(_) = TokenNumberMap::try_deserialize(&mut &**token_id_map.try_borrow_mut_data()?) { + return err!(PropellerError::TokenNumberMapExists); + } + Ok(()) + } +} + +#[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, Debug)] +pub enum ToTokenStep { + Transfer, + RemoveExactBurn, + SwapExactInput, +} + +impl<'info> CreateTokenNumberMap<'info> { + pub fn accounts( + ctx: &Context, + to_token_number: u16, + pool: Pubkey, + pool_token_index: u8, + pool_token_mint: Pubkey, + to_token_step: ToTokenStep, + ) -> Result<()> { + require_keys_eq!(ctx.accounts.pool.key(), pool, PropellerError::InvalidTokenNumberMapPool); + if let ToTokenStep::Transfer = to_token_step { + require_keys_eq!( + ctx.accounts.propeller.swim_usd_mint, + pool_token_mint, + PropellerError::InvalidTokenNumberMapPoolTokenMint + ); + return Ok(()); + } + + let pool_token_index = pool_token_index as usize; + require_gt!(TOKEN_COUNT, pool_token_index, PropellerError::InvalidTokenNumberMapPoolTokenIndex); + require_keys_eq!( + ctx.accounts.pool.token_mint_keys[pool_token_index], + pool_token_mint, + PropellerError::InvalidTokenNumberMapPoolTokenMint + ); + Ok(()) + } +} + +pub fn handle_create_token_number_map( + ctx: Context, + to_token_number: u16, + pool: Pubkey, + pool_token_index: u8, + pool_token_mint: Pubkey, + to_token_step: ToTokenStep, +) -> Result<()> { + let mut token_number_map = &mut ctx.accounts.token_number_map; + token_number_map.to_token_number = to_token_number; + token_number_map.pool = pool; + token_number_map.pool_token_index = pool_token_index; + token_number_map.pool_token_mint = pool_token_mint; + token_number_map.bump = *ctx.bumps.get("token_number_map").unwrap(); + token_number_map.to_token_step = to_token_step; + Ok(()) +} + +#[derive(Accounts)] +#[instruction(to_token_number: u16)] +pub struct UpdateTokenNumberMap<'info> { + #[account( + seeds = [b"propeller".as_ref(), propeller.swim_usd_mint.as_ref()], + bump = propeller.bump, + has_one = governance_key @ PropellerError::InvalidPropellerGovernanceKey, + )] + pub propeller: Box>, + + pub governance_key: Signer<'info>, + + #[account(mut)] + pub payer: Signer<'info>, + + // #[account( + // seeds = [ + // b"two_pool".as_ref(), + // pool.token_mint_keys[0].as_ref(), + // pool.token_mint_keys[1].as_ref(), + // pool.lp_mint_key.as_ref(), + // ], + // bump = pool.bump, + // seeds::program = two_pool_program.key(), + // )] + // Note: anchor is unable to compile when i use the `#[account]` macro here even though it's the + // exact same as above. manually validating the address in `accounts` fn + pub pool: Box>, + + #[account( + mut, + seeds = [ + b"propeller".as_ref(), + b"token_id".as_ref(), + propeller.key().as_ref(), + &to_token_number.to_le_bytes() + ], + bump = token_number_map.bump, + constraint = token_number_map.to_token_number == to_token_number, + )] + pub token_number_map: Account<'info, TokenNumberMap>, + pub two_pool_program: Program<'info, two_pool::program::TwoPool>, +} + +impl<'info> UpdateTokenNumberMap<'info> { + pub fn accounts( + ctx: &Context, + pool_token_index: &u8, + pool_token_mint: &Pubkey, + to_token_step: &ToTokenStep, + ) -> Result<()> { + let propeller = &ctx.accounts.propeller; + let pool = &ctx.accounts.pool; + let pool_token_mint = *pool_token_mint; + let expected_pool_key = Pubkey::create_program_address( + &[ + b"two_pool".as_ref(), + pool.token_mint_keys[0].as_ref(), + &pool.token_mint_keys[1].as_ref(), + &pool.lp_mint_key.as_ref(), + &[pool.bump], + ], + &ctx.accounts.two_pool_program.key(), + ) + .map_err(|_| PropellerError::InvalidTokenNumberMapPool)?; + require_keys_eq!( + *pool.to_account_info().owner, + ctx.accounts.two_pool_program.key(), + PropellerError::InvalidTokenNumberMapPool + ); + require_keys_eq!(ctx.accounts.pool.key(), expected_pool_key, PropellerError::InvalidTokenNumberMapPool); + let pool_token_index = *pool_token_index as usize; + require_gt!(TOKEN_COUNT, pool_token_index as usize, PropellerError::InvalidTokenNumberMapPoolTokenIndex); + match *to_token_step { + // metapools must have swimUSD as token_mint_keys[0] + ToTokenStep::SwapExactInput => { + require_keys_eq!(pool.token_mint_keys[0], propeller.swim_usd_mint); + require_keys_eq!(pool.token_mint_keys[1], pool_token_mint); + require_eq!(pool_token_index, 1 as usize); + } + ToTokenStep::RemoveExactBurn => { + require_keys_eq!(pool.lp_mint_key, pool_token_mint); + require_keys_eq!(pool.lp_mint_key, propeller.swim_usd_mint); + require_keys_eq!(pool.token_mint_keys[pool_token_index], pool_token_mint); + } + ToTokenStep::Transfer => { + require_keys_eq!( + ctx.accounts.propeller.swim_usd_mint, + pool_token_mint, + PropellerError::InvalidTokenNumberMapPoolTokenMint + ); + } + } + Ok(()) + } +} + +pub fn handle_update_token_number_map( + ctx: Context, + _to_token_number: u16, + pool_token_index: u8, + pool_token_mint: Pubkey, + to_token_step: ToTokenStep, +) -> Result<()> { + let token_number_map = &mut ctx.accounts.token_number_map; + token_number_map.pool = ctx.accounts.pool.key(); + token_number_map.pool_token_index = pool_token_index; + token_number_map.pool_token_mint = pool_token_mint; + token_number_map.to_token_step = to_token_step; + Ok(()) +} + +#[derive(Accounts)] +#[instruction(to_token_number: u16)] +pub struct CloseTokenNumberMap<'info> { + #[account( + seeds = [b"propeller".as_ref(), propeller.swim_usd_mint.as_ref()], + bump = propeller.bump, + has_one = governance_key @ PropellerError::InvalidPropellerGovernanceKey, + )] + pub propeller: Box>, + + pub governance_key: Signer<'info>, + + #[account(mut)] + pub payer: Signer<'info>, + + #[account( + mut, + seeds = [ + b"propeller".as_ref(), + b"token_id".as_ref(), + propeller.key().as_ref(), + &to_token_number.to_le_bytes() + ], + bump = token_number_map.bump, + close = payer, + )] + pub token_number_map: Account<'info, TokenNumberMap>, +} + +pub fn handle_close_token_number_map(ctx: Context, to_token_number: u16) -> Result<()> { + //TODO emit event? + msg!("Closed TokenNumberMap {} for to_token_number {}", ctx.accounts.token_number_map.key(), to_token_number); + Ok(()) +} diff --git a/packages/solana-contracts/programs/propeller/src/instructions/two_pool_cpi/add.rs b/packages/solana-contracts/programs/propeller/src/instructions/two_pool_cpi/add.rs index 2dc13ea37..081e46525 100644 --- a/packages/solana-contracts/programs/propeller/src/instructions/two_pool_cpi/add.rs +++ b/packages/solana-contracts/programs/propeller/src/instructions/two_pool_cpi/add.rs @@ -13,7 +13,7 @@ pub struct Add<'info> { #[account( seeds = [ b"propeller".as_ref(), lp_mint.key().as_ref()], bump = propeller.bump, - + constraint = !propeller.is_paused @ PropellerError::IsPaused, )] pub propeller: Box>, #[account( @@ -139,6 +139,7 @@ pub fn handle_cross_chain_add( lp_mint: ctx.accounts.lp_mint.to_account_info(), governance_fee: ctx.accounts.governance_fee.to_account_info(), user_transfer_authority: ctx.accounts.user_transfer_authority.to_account_info(), + user_token_account_0: ctx.accounts.user_token_account_0.to_account_info(), user_token_account_1: ctx.accounts.user_token_account_1.to_account_info(), user_lp_token_account: ctx.accounts.user_lp_token_account.to_account_info(), diff --git a/packages/solana-contracts/programs/propeller/src/instructions/two_pool_cpi/swap_exact_input.rs b/packages/solana-contracts/programs/propeller/src/instructions/two_pool_cpi/swap_exact_input.rs index 36186376f..091f88e29 100644 --- a/packages/solana-contracts/programs/propeller/src/instructions/two_pool_cpi/swap_exact_input.rs +++ b/packages/solana-contracts/programs/propeller/src/instructions/two_pool_cpi/swap_exact_input.rs @@ -17,6 +17,7 @@ pub struct SwapExactInput<'info> { ], bump = propeller.bump, has_one = swim_usd_mint @ PropellerError::InvalidSwimUsdMint, + constraint = !propeller.is_paused @ PropellerError::IsPaused, )] pub propeller: Account<'info, Propeller>, #[account( diff --git a/packages/solana-contracts/programs/propeller/src/instructions/utils.rs b/packages/solana-contracts/programs/propeller/src/instructions/utils.rs index cdaef24f0..7c216ac31 100644 --- a/packages/solana-contracts/programs/propeller/src/instructions/utils.rs +++ b/packages/solana-contracts/programs/propeller/src/instructions/utils.rs @@ -68,59 +68,59 @@ pub fn convert_fees_to_swim_usd_atomic<'info>( res = fee_in_swim_usd_atomic; Ok(res) } - -pub fn convert_fees_to_swim_usd_atomic_2<'info>( - fee_in_lamports: u64, - propeller: &Propeller, - marginal_price_pool_lp_mint: &Account<'info, Mint>, - marginal_prices: [BorshDecimal; TOKEN_COUNT], - marginal_price_pool: &TwoPool, - aggregator: &AccountLoader, - max_staleness: i64, -) -> Result { - // let propeller = &self.propeller; - - msg!("fee_in_lamports: {:?}", fee_in_lamports); - let marginal_price_pool_lp_mint = &marginal_price_pool_lp_mint; - - let swim_usd_mint_key = propeller.swim_usd_mint; - // let marginal_prices = get_marginal_prices(cpi_ctx)?; - - let intermediate_token_price_decimal: Decimal = get_marginal_price_decimal( - &marginal_price_pool, - &marginal_prices, - &propeller, - &marginal_price_pool_lp_mint.key(), - )?; - - msg!("intermediate_token_price_decimal: {:?}", intermediate_token_price_decimal); - - let fee_in_lamports_decimal = Decimal::from_u64(fee_in_lamports).ok_or(PropellerError::ConversionError)?; - msg!("fee_in_lamports(u64): {:?} fee_in_lamports_decimal: {:?}", fee_in_lamports, fee_in_lamports_decimal); - - let mut res = 0u64; - - let lamports_intermediate_token_price = get_lamports_intermediate_token_price(&aggregator, max_staleness)?; - let fee_in_swim_usd_decimal = lamports_intermediate_token_price - .checked_mul(fee_in_lamports_decimal) - .and_then(|x| x.checked_div(intermediate_token_price_decimal)) - .ok_or(PropellerError::IntegerOverflow)?; - - let swim_usd_decimals = - get_swim_usd_mint_decimals(&swim_usd_mint_key, &marginal_price_pool, &marginal_price_pool_lp_mint)?; - msg!("swim_usd_decimals: {:?}", swim_usd_decimals); - - let ten_pow_decimals = - Decimal::from_u64(10u64.pow(swim_usd_decimals as u32)).ok_or(PropellerError::IntegerOverflow)?; - let fee_in_swim_usd_atomic = fee_in_swim_usd_decimal - .checked_mul(ten_pow_decimals) - .and_then(|v| v.to_u64()) - .ok_or(PropellerError::ConversionError)?; - - msg!("fee_in_swim_usd_decimal: {:?} fee_in_swim_usd_atomic: {:?}", fee_in_swim_usd_decimal, fee_in_swim_usd_atomic); - res = fee_in_swim_usd_atomic; - Ok(res) -} +// +// pub fn convert_fees_to_swim_usd_atomic_2<'info>( +// fee_in_lamports: u64, +// propeller: &Propeller, +// marginal_price_pool_lp_mint: &Account<'info, Mint>, +// marginal_prices: [BorshDecimal; TOKEN_COUNT], +// marginal_price_pool: &TwoPool, +// aggregator: &AccountLoader, +// max_staleness: i64, +// ) -> Result { +// // let propeller = &self.propeller; +// +// msg!("fee_in_lamports: {:?}", fee_in_lamports); +// let marginal_price_pool_lp_mint = &marginal_price_pool_lp_mint; +// +// let swim_usd_mint_key = propeller.swim_usd_mint; +// // let marginal_prices = get_marginal_prices(cpi_ctx)?; +// +// let intermediate_token_price_decimal: Decimal = get_marginal_price_decimal( +// &marginal_price_pool, +// &marginal_prices, +// &propeller, +// &marginal_price_pool_lp_mint.key(), +// )?; +// +// msg!("intermediate_token_price_decimal: {:?}", intermediate_token_price_decimal); +// +// let fee_in_lamports_decimal = Decimal::from_u64(fee_in_lamports).ok_or(PropellerError::ConversionError)?; +// msg!("fee_in_lamports(u64): {:?} fee_in_lamports_decimal: {:?}", fee_in_lamports, fee_in_lamports_decimal); +// +// let mut res = 0u64; +// +// let lamports_intermediate_token_price = get_lamports_intermediate_token_price(&aggregator, max_staleness)?; +// let fee_in_swim_usd_decimal = lamports_intermediate_token_price +// .checked_mul(fee_in_lamports_decimal) +// .and_then(|x| x.checked_div(intermediate_token_price_decimal)) +// .ok_or(PropellerError::IntegerOverflow)?; +// +// let swim_usd_decimals = +// get_swim_usd_mint_decimals(&swim_usd_mint_key, &marginal_price_pool, &marginal_price_pool_lp_mint)?; +// msg!("swim_usd_decimals: {:?}", swim_usd_decimals); +// +// let ten_pow_decimals = +// Decimal::from_u64(10u64.pow(swim_usd_decimals as u32)).ok_or(PropellerError::IntegerOverflow)?; +// let fee_in_swim_usd_atomic = fee_in_swim_usd_decimal +// .checked_mul(ten_pow_decimals) +// .and_then(|v| v.to_u64()) +// .ok_or(PropellerError::ConversionError)?; +// +// msg!("fee_in_swim_usd_decimal: {:?} fee_in_swim_usd_atomic: {:?}", fee_in_swim_usd_decimal, fee_in_swim_usd_atomic); +// res = fee_in_swim_usd_atomic; +// Ok(res) +// } pub fn get_swim_usd_mint_decimals( swim_usd_mint: &Pubkey, @@ -169,12 +169,8 @@ pub fn get_lamports_intermediate_token_price( max_staleness: i64, ) -> Result { let feed = aggregator.load()?; - feed.check_staleness( - Clock::get().unwrap().unix_timestamp, - // 300 - i64::MAX, - ) - .map_err(|_| error!(PropellerError::StaleFeed))?; + feed.check_staleness(Clock::get().unwrap().unix_timestamp, max_staleness) + .map_err(|_| error!(PropellerError::StaleFeed))?; // check feed does not exceed max_confidence_interval // if let Some(max_confidence_interval) = params.max_confidence_interval { @@ -191,3 +187,7 @@ pub fn get_lamports_intermediate_token_price( Ok(lamports_usd_price) // check whether the feed has been updated in the last 300 seconds } + +pub fn get_memo_as_utf8(memo: [u8; 16]) -> Result { + String::from_utf8(hex::encode(memo).into_bytes()).map_err(|_| error!(PropellerError::InvalidMemo)) +} diff --git a/packages/solana-contracts/programs/propeller/src/instructions/wormhole/complete_native_with_payload.rs b/packages/solana-contracts/programs/propeller/src/instructions/wormhole/complete_native_with_payload.rs index f02ec58ac..ba015eb9c 100644 --- a/packages/solana-contracts/programs/propeller/src/instructions/wormhole/complete_native_with_payload.rs +++ b/packages/solana-contracts/programs/propeller/src/instructions/wormhole/complete_native_with_payload.rs @@ -1,13 +1,12 @@ -use crate::Fees; +use crate::{get_memo_as_utf8, validate_marginal_prices_pool_accounts, wormhole::SwimPayload, Fees}; use { crate::{ constants::LAMPORTS_PER_SOL_DECIMAL, deserialize_message_payload, error::*, get_lamports_intermediate_token_price, get_marginal_price_decimal, get_marginal_prices, get_message_data, get_swim_usd_mint_decimals, get_transfer_with_payload_from_message_account, hash_vaa, - instructions::fee_tracker::FeeTracker, state::SwimPayloadMessage, validate_marginal_prices_pool_accounts, - Address, ChainID, ClaimData, MessageData, PayloadTransferWithPayload, PostVAAData, PostedMessageData, - PostedVAAData, Propeller, RawSwimPayload, TokenBridge, Wormhole, COMPLETE_NATIVE_WITH_PAYLOAD_INSTRUCTION, - TOKEN_COUNT, + instructions::fee_tracker::FeeTracker, state::SwimPayloadMessage, Address, ChainID, ClaimData, MessageData, + PayloadTransferWithPayload, PostVAAData, PostedVAAData, Propeller, TokenBridge, Wormhole, + COMPLETE_NATIVE_WITH_PAYLOAD_INSTRUCTION, TOKEN_COUNT, }, anchor_lang::{ prelude::*, @@ -35,6 +34,7 @@ pub struct CompleteNativeWithPayload<'info> { bump = propeller.bump, has_one = swim_usd_mint @ PropellerError::InvalidSwimUsdMint, has_one = fee_vault @ PropellerError::InvalidFeeVault, + constraint = !propeller.is_paused @ PropellerError::IsPaused, )] pub propeller: Box>, @@ -45,7 +45,7 @@ pub struct CompleteNativeWithPayload<'info> { mut, seeds = [ b"config".as_ref() ], bump, - seeds::program = propeller.token_bridge().unwrap() + seeds::program = token_bridge.key(), )] /// CHECK: Token Bridge Config pub token_bridge_config: UncheckedAccount<'info>, @@ -77,14 +77,20 @@ pub struct CompleteNativeWithPayload<'info> { // bump, // seeds::program = propeller.wormhole()? // )] + // pub message: Box>, #[account( mut, - owner = propeller.wormhole()?, + owner = wormhole.key(), )] /// CHECK: wormhole message account. seeds = [ "PostedVAA", hash(vaa) ], seeds::program = token_bridge pub message: UncheckedAccount<'info>, - // pub message: Account<'info, PostedMessageData>, - // pub message: Account<'info, PostedVAAData>, + // Note: this works only without the Box wrapper + // error[E0277]: the trait bound `Box>: AsRef>` is not satisfied + // + // #[derive(Accounts)] + // ^^^^^^^^ the trait `AsRef>` is not implemented for `Box>` + + // pub message: Box>, /// seeds = [ /// vaa.emitter_address, vaa.emitter_chain, vaa.sequence ///], @@ -131,39 +137,49 @@ pub struct CompleteNativeWithPayload<'info> { )] /// this is "to_fees" /// recipient of fees for executing complete transfer (e.g. relayer) + /// this is only used in `propellerCompleteNativeWithPayload`. pub fee_vault: Box>, + + #[account( + init, + payer = payer, + seeds = [ + b"propeller".as_ref(), + b"swim_payload".as_ref(), + claim.key().as_ref(), + ], + bump, + space = 8 + SwimPayloadMessage::LEN, + )] + pub swim_payload_message: Box>, // #[account(mut)] // /// this is "to_fees" // /// TODO: type as TokenAccount? // /// CHECK: recipient of fees for executing complete transfer (e.g. relayer) // pub fee_recipient: AccountInfo<'info>, - #[account(mut)] + #[account( + mut, + seeds = [swim_usd_mint.key().as_ref()], + seeds::program = token_bridge.key(), + bump, + )] /// CHECK: wormhole_custody_account: seeds = [mint], seeds::program = token_bridge pub custody: UncheckedAccount<'info>, // #[account(address = propeller.token_bridge_mint)] pub swim_usd_mint: Box>, + #[account( + seeds = [b"custody_signer".as_ref()], + seeds::program = token_bridge.key(), + bump, + )] /// CHECK: custody_signer_account: seeds = [b"custody_signer"], seeds::program = token_bridge pub custody_signer: UncheckedAccount<'info>, - pub rent: Sysvar<'info, Rent>, - pub system_program: Program<'info, System>, - pub wormhole: Program<'info, Wormhole>, pub token_program: Program<'info, Token>, pub token_bridge: Program<'info, TokenBridge>, - - #[account( - init, - payer = payer, - seeds = [ - b"propeller".as_ref(), - b"swim_payload".as_ref(), - claim.key().as_ref(), - ], - bump, - space = 8 + SwimPayloadMessage::LEN, - )] - pub swim_payload_message: Account<'info, SwimPayloadMessage>, + pub system_program: Program<'info, System>, + pub rent: Sysvar<'info, Rent>, } impl<'info> CompleteNativeWithPayload<'info> { @@ -242,20 +258,45 @@ impl<'info> CompleteNativeWithPayload<'info> { } pub fn get_transfer_with_payload(&self, message_data: &MessageData) -> Result { - // let message_account_info = &self.message.to_account_info(); - // let message_data = get_message_data(message_account_info)?; - // msg!("message_data: {:?}", message_data); - let transfer_with_payload = deserialize_message_payload(&mut message_data.payload.as_slice())?; + // let transfer_with_payload = deserialize_message_payload(&mut message_data.payload.as_slice())?; + let transfer_with_payload = PayloadTransferWithPayload::deserialize(&mut message_data.payload.as_slice())?; msg!("transfer_with_payload: {:?}", transfer_with_payload); Ok(transfer_with_payload) } + // fn write_swim_payload_message( + // &mut self, + // bump: u8, + // message_data: &MessageData, + // transfer_amount: u64, + // swim_payload: &RawSwimPayload, + // ) -> Result<()> { + // let swim_payload_message = &mut self.swim_payload_message; + // swim_payload_message.bump = bump; + // swim_payload_message.claim = self.claim.key(); + // // swim_payload_message.claim_bump = *ctx.bumps.get("claim").unwrap(); + // swim_payload_message.swim_payload_message_payer = self.payer.key(); + // // swim_payload_message.wh_message_bump = *ctx.bumps.get("message").unwrap(); + // swim_payload_message.vaa_emitter_address = message_data.emitter_address; + // swim_payload_message.vaa_emitter_chain = message_data.emitter_chain; + // swim_payload_message.vaa_sequence = message_data.sequence; + // swim_payload_message.transfer_amount = transfer_amount; + // swim_payload_message.swim_payload_version = swim_payload.swim_payload_version; + // swim_payload_message.max_fee = swim_payload.max_fee; + // swim_payload_message.target_token_id = swim_payload.target_token_id; + // swim_payload_message.owner = Pubkey::new_from_array(swim_payload.owner); + // swim_payload_message.memo = swim_payload.memo; + // swim_payload_message.propeller_enabled = swim_payload.propeller_enabled; + // swim_payload_message.gas_kickstart = swim_payload.gas_kickstart; + // Ok(()) + // } + fn write_swim_payload_message( &mut self, bump: u8, message_data: &MessageData, transfer_amount: u64, - swim_payload: &RawSwimPayload, + swim_payload: &SwimPayload, ) -> Result<()> { let swim_payload_message = &mut self.swim_payload_message; swim_payload_message.bump = bump; @@ -268,11 +309,12 @@ impl<'info> CompleteNativeWithPayload<'info> { swim_payload_message.vaa_sequence = message_data.sequence; swim_payload_message.transfer_amount = transfer_amount; swim_payload_message.swim_payload_version = swim_payload.swim_payload_version; - swim_payload_message.target_token_id = swim_payload.target_token_id; swim_payload_message.owner = Pubkey::new_from_array(swim_payload.owner); - swim_payload_message.memo = swim_payload.memo; - swim_payload_message.propeller_enabled = swim_payload.propeller_enabled; - swim_payload_message.gas_kickstart = swim_payload.gas_kickstart; + swim_payload_message.propeller_enabled = swim_payload.propeller_enabled.unwrap_or_default(); + swim_payload_message.gas_kickstart = swim_payload.gas_kickstart.unwrap_or_default(); + swim_payload_message.max_fee = swim_payload.max_fee.unwrap_or_default(); + swim_payload_message.target_token_id = swim_payload.target_token_id.unwrap_or_default(); + swim_payload_message.memo = swim_payload.memo.unwrap_or_default(); Ok(()) } } @@ -369,11 +411,8 @@ impl<'info> PropellerCompleteNativeWithPayload<'info> { let propeller = &ctx.accounts.complete_native_with_payload.propeller; validate_marginal_prices_pool_accounts( &propeller, - &ctx.accounts.marginal_price_pool.key(), - &[ - ctx.accounts.marginal_price_pool_token_0_account.mint, - ctx.accounts.marginal_price_pool_token_1_account.mint, - ], + &ctx.accounts.marginal_price_pool, + &[&ctx.accounts.marginal_price_pool_token_0_account, &ctx.accounts.marginal_price_pool_token_1_account], )?; require_keys_eq!(ctx.accounts.complete_native_with_payload.fee_vault.key(), propeller.fee_vault); require_keys_eq!(ctx.accounts.complete_native_with_payload.fee_vault.owner, propeller.key()); @@ -383,9 +422,8 @@ impl<'info> PropellerCompleteNativeWithPayload<'info> { fn log_memo(&self, memo: [u8; 16]) -> Result<()> { if memo != [0u8; 16] { - let memo_ix = - spl_memo::build_memo(std::str::from_utf8(hex::encode(memo).as_bytes()).unwrap().as_ref(), &[]); - invoke(&memo_ix, &[ctx.accounts.memo.to_account_info()])?; + let memo_ix = spl_memo::build_memo(get_memo_as_utf8(memo)?.as_ref(), &[]); + invoke(&memo_ix, &[self.memo.to_account_info()])?; } Ok(()) } @@ -520,7 +558,7 @@ pub fn handle_propeller_complete_native_with_payload(ctx: Context { #[account( mut, seeds = [b"propeller".as_ref(), swim_usd_mint.key().as_ref()], bump = propeller.bump, has_one = swim_usd_mint @ PropellerError::InvalidSwimUsdMint, + constraint = !propeller.is_paused @ PropellerError::IsPaused, )] pub propeller: Box>, @@ -187,7 +191,8 @@ pub struct TransferNativeWithPayload<'info> { propeller.key().as_ref(), &target_chain.to_le_bytes() ], - bump = target_chain_map.bump + bump = target_chain_map.bump, + constraint = !target_chain_map.is_paused @ PropellerError::TargetChainIsPaused, )] pub target_chain_map: Account<'info, TargetChainMap>, pub system_program: Program<'info, System>, @@ -268,12 +273,6 @@ impl<'info> TransferNativeWithPayload<'info> { )?; Ok(()) } - - pub fn increment_nonce(&mut self) -> Result<()> { - let propeller = &mut self.propeller; - propeller.nonce = propeller.nonce.wrapping_add(1); - Ok(()) - } } #[derive(AnchorDeserialize, AnchorSerialize, Default)] @@ -289,6 +288,7 @@ pub struct TransferWithPayloadData { pub fn handle_cross_chain_transfer_native_with_payload( ctx: Context, + nonce: u32, amount: u64, target_chain: u16, owner: Vec, @@ -314,43 +314,11 @@ pub fn handle_cross_chain_transfer_native_with_payload( let swim_payload = SwimPayload { swim_payload_version: CURRENT_SWIM_PAYLOAD_VERSION, owner: owner_addr, ..Default::default() }; - // let swim_payload = RawSwimPayload { - // //TODO: this should come from the propeller or global constant? - // swim_payload_version: CURRENT_SWIM_PAYLOAD_VERSION, - // owner: owner_addr, - // propeller_enabled, - // gas_kickstart, - // max_fee, - // target_token_id, - // // min_output_amount: U256::from(0u64), - // memo: memo.clone().try_into().unwrap(), - // }; msg!("transfer_native_with_payload swim_payload: {:?}", swim_payload); - // let mut swim_payload_bytes = [0u8; 32]; - // let swim_payload_bytes = swim_payload.try_to_vec()?; - // anchor_lang::prelude::msg!("swim_payload_bytes {:?}", swim_payload_bytes); - // - // Note: - // 1. nonce is created randomly client side using this - // export function createNonce() { - // const nonceConst = Math.random() * 100000; - // const nonceBuffer = Buffer.alloc(4); - // nonceBuffer.writeUInt32LE(nonceConst, 0); - // return nonceBuffer; - // } - // 2. fee is relayerFee - // a. removed in payload3 - // 3. targetAddress is Uint8Array (on wasm.rs its Vec - // a. WH client has special handling/formatting for this - // see - wh-sdk/src/utils/array.ts tryNativeToUint8Array(address: string, chain: ChainId | ChainName) - // 4. targetChain is number/u16 - // 5. payload is Vec - // ok let target_address = ctx.accounts.target_chain_map.target_address.clone(); let transfer_with_payload_data = TransferWithPayloadData { - //TODO: update this. - nonce: ctx.accounts.propeller.nonce, + nonce, amount, target_address, target_chain, @@ -369,12 +337,12 @@ pub fn handle_cross_chain_transfer_native_with_payload( }, ))?; msg!("Revoked authority_signer approval"); - ctx.accounts.increment_nonce()?; Ok(()) } pub fn handle_propeller_transfer_native_with_payload( ctx: Context, + nonce: u32, amount: u64, target_chain: u16, owner: Vec, @@ -398,8 +366,6 @@ pub fn handle_propeller_transfer_native_with_payload( amount, )?; msg!("finished approve for authority_signer"); - // let mut target_token_addr = [0u8; 32]; - // target_token_addr.copy_from_slice(target_token.as_slice()); let mut owner_addr = [0u8; 32]; owner_addr.copy_from_slice(owner.as_slice()); @@ -416,7 +382,7 @@ pub fn handle_propeller_transfer_native_with_payload( let target_address = ctx.accounts.target_chain_map.target_address.clone(); let transfer_with_payload_data = TransferWithPayloadData { - nonce: ctx.accounts.propeller.nonce, + nonce, amount, target_address, target_chain, @@ -436,41 +402,5 @@ pub fn handle_propeller_transfer_native_with_payload( }, ))?; msg!("Revoked authority_signer approval"); - ctx.accounts.increment_nonce()?; Ok(()) } - -#[derive(PartialEq, Debug, Clone, AnchorDeserialize, Default)] -pub struct SwimPayload { - //TOOD: should this come from propeller? - //required - pub swim_payload_version: u8, - pub owner: [u8; 32], - // required for all propellerEngines - pub propeller_enabled: Option, - pub gas_kickstart: Option, - pub max_fee: Option, - pub target_token_id: Option, - // required for SWIM propellerEngine - pub memo: Option<[u8; 16]>, -} - -impl AnchorSerialize for SwimPayload { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - // Payload ID - // writer.write_u8(self.swim_payload_version)?; - writer.write_u8(self.swim_payload_version)?; - writer.write_all(&self.owner)?; - - if self.propeller_enabled.is_some() { - writer.write_u8(1)?; - writer.write_u8(self.gas_kickstart.unwrap() as u8)?; - writer.write_u64::(self.max_fee.unwrap())?; - writer.write_u16::(self.target_token_id.unwrap())?; - if self.memo.is_some() { - writer.write_all(&self.memo.unwrap())?; - } - } - Ok(()) - } -} diff --git a/packages/solana-contracts/programs/propeller/src/lib.rs b/packages/solana-contracts/programs/propeller/src/lib.rs index 7c2c6c9e8..29e0959bf 100644 --- a/packages/solana-contracts/programs/propeller/src/lib.rs +++ b/packages/solana-contracts/programs/propeller/src/lib.rs @@ -1,31 +1,15 @@ -use anchor_lang::solana_program::{ - borsh::try_from_slice_unchecked, - instruction::Instruction, - program::{get_return_data, invoke, invoke_signed}, - program_option::COption, - system_instruction::transfer, - sysvar::SysvarId, // sysvar::Sysvar::to_account_info, -}; use { crate::two_pool_cpi::{ - add::*, remove_exact_burn::*, remove_exact_output::*, remove_uniform::*, swap_exact_input::*, - swap_exact_output::*, + add::*, + swap_exact_input::*, + // remove_exact_burn::*, remove_exact_output::*, remove_uniform::*, swap_exact_output::*, }, anchor_lang::{prelude::*, solana_program}, // crate::two_pool_cpi::*, - anchor_spl::{ - associated_token::{get_associated_token_address, AssociatedToken}, - token::{Mint, Token, TokenAccount}, - *, - }, constants::TOKEN_COUNT, fees::*, - // error::PropellerError, - // instructions::*, - solana_program::clock::Epoch, state::*, token_bridge::*, - two_pool::instructions::AddParams, wormhole::*, }; @@ -52,27 +36,60 @@ pub mod propeller { handle_initialize(ctx, params) } + pub fn set_paused(ctx: Context, paused: bool) -> Result<()> { + handle_set_paused(ctx, paused) + } + + pub fn change_pause_key(ctx: Context) -> Result<()> { + handle_change_pause_key(ctx) + } + + pub fn prepare_governance_transition(ctx: Context) -> Result<()> { + Ok(()) + } + + pub fn enact_governance_transition(ctx: Context) -> Result<()> { + Ok(()) + } + #[inline(never)] #[access_control( - CreateTokenIdMap::accounts( + CreateTokenNumberMap::accounts( &ctx, - target_token_index, + to_token_number, pool, pool_token_index, pool_token_mint, - pool_ix, + to_token_step, ))] - pub fn create_token_id_map( - ctx: Context, - target_token_index: u16, + pub fn create_token_number_map( + ctx: Context, + to_token_number: u16, pool: Pubkey, pool_token_index: u8, pool_token_mint: Pubkey, - pool_ix: PoolInstruction, + to_token_step: ToTokenStep, + ) -> Result<()> { + handle_create_token_number_map(ctx, to_token_number, pool, pool_token_index, pool_token_mint, to_token_step) + } + + #[inline(never)] + #[access_control(UpdateTokenNumberMap::accounts(&ctx, &pool_token_index, &pool_token_mint, &to_token_step))] + pub fn update_token_number_map( + ctx: Context, + to_token_number: u16, + pool_token_index: u8, + pool_token_mint: Pubkey, + to_token_step: ToTokenStep, ) -> Result<()> { - handle_create_token_id_map(ctx, target_token_index, pool, pool_token_index, pool_token_mint, pool_ix) + handle_update_token_number_map(ctx, to_token_number, pool_token_index, pool_token_mint, to_token_step) } + pub fn close_token_number_map(ctx: Context, to_token_number: u16) -> Result<()> { + handle_close_token_number_map(ctx, to_token_number) + } + + /** Target Chain Map **/ pub fn create_target_chain_map( ctx: Context, target_chain: u16, @@ -81,10 +98,15 @@ pub mod propeller { handle_create_target_chain_map(ctx, target_chain, target_address) } + //TODO: pass in `target_chain` as input parameter for these? pub fn update_target_chain_map(ctx: Context, routing_contract: [u8; 32]) -> Result<()> { handle_update_target_chain_map(ctx, routing_contract) } + pub fn target_chain_map_set_paused(ctx: Context, is_paused: bool) -> Result<()> { + handle_target_chain_map_set_paused(ctx, is_paused) + } + #[inline(never)] pub fn initialize_fee_tracker(ctx: Context) -> Result<()> { handle_initialize_fee_tracker(ctx) @@ -280,21 +302,22 @@ pub mod propeller { #[access_control(TransferNativeWithPayload::accounts(&ctx))] pub fn cross_chain_transfer_native_with_payload( ctx: Context, + nonce: u32, amount: u64, target_chain: u16, owner: Vec, ) -> Result<()> { - handle_cross_chain_transfer_native_with_payload(ctx, amount, target_chain, owner) + handle_cross_chain_transfer_native_with_payload(ctx, nonce, amount, target_chain, owner) } #[inline(never)] #[access_control(TransferNativeWithPayload::accounts(&ctx))] pub fn propeller_transfer_native_with_payload( ctx: Context, + nonce: u32, amount: u64, target_chain: u16, owner: Vec, - // propeller_enabled: bool, gas_kickstart: bool, max_fee: u64, target_token_id: u16, @@ -302,6 +325,7 @@ pub mod propeller { ) -> Result<()> { handle_propeller_transfer_native_with_payload( ctx, + nonce, amount, target_chain, owner, @@ -322,10 +346,10 @@ pub mod propeller { #[access_control(ProcessSwimPayload::accounts(&ctx))] pub fn process_swim_payload( ctx: Context, - target_token_id: u16, + to_token_number: u16, min_output_amount: u64, ) -> Result { - handle_process_swim_payload(ctx, target_token_id, min_output_amount) + handle_process_swim_payload(ctx, to_token_number, min_output_amount) } #[inline(never)] @@ -344,12 +368,12 @@ pub mod propeller { /// Note: passing in target_token_id here due to PDA seed derivation. /// for propeller_process_swim_payload, require_eq!(target_token_id, propeller_message.target_token_id); #[inline(never)] - #[access_control(PropellerProcessSwimPayload::accounts(&ctx, target_token_id))] + #[access_control(PropellerProcessSwimPayload::accounts(&ctx, to_token_number))] pub fn propeller_process_swim_payload( ctx: Context, - target_token_id: u16, + to_token_number: u16, ) -> Result { - handle_propeller_process_swim_payload(ctx, target_token_id) + handle_propeller_process_swim_payload(ctx, to_token_number) } /// This ix is used if a propeller engine detects (off-chain) that the target_token_id is not valid diff --git a/packages/solana-contracts/programs/propeller/src/state/propeller.rs b/packages/solana-contracts/programs/propeller/src/state/propeller.rs index eb472eeb7..d01751ca6 100644 --- a/packages/solana-contracts/programs/propeller/src/state/propeller.rs +++ b/packages/solana-contracts/programs/propeller/src/state/propeller.rs @@ -16,11 +16,12 @@ use { #[account] pub struct Propeller { pub bump: u8, - pub nonce: u32, - pub admin: Pubkey, //32 - pub wormhole: Pubkey, //32 - pub token_bridge: Pubkey, //32 - pub swim_usd_mint: Pubkey, //32 + pub is_paused: bool, // 1 + pub governance_key: Pubkey, //32 + pub prepared_governance_key: Pubkey, // 32 + pub governance_transition_ts: i64, // 8 + pub pause_key: Pubkey, //32 + pub swim_usd_mint: Pubkey, //32 pub sender_bump: u8, pub redeemer_bump: u8, @@ -35,13 +36,6 @@ pub struct Propeller { pub init_ata_fee: u64, pub complete_with_payload_fee: u64, pub process_swim_payload_fee: u64, - // minimum amount of tokens that must be transferred in token bridge transfer - // if propeller enabled transfer. - // Note: No longer using min transfer amounts - // client will set `max_fee` for propellerEnabled transfers and engine will - // be responsible for checking if the fee is sufficient for it to relay the transfer - // pub propeller_min_transfer_amount: u64, - // pub propeller_eth_min_transfer_amount: u64, // gas kickstart parameters // 1. marginal_price_pool will be pool used to calculate token_bridge_mint -> stablecoin @@ -58,9 +52,10 @@ pub struct Propeller { pub fee_vault: Pubkey, //32 pub aggregator: Pubkey, //32 - pub max_staleness: i64, - //TODO: add this? - // pub fallback_oracle: Pubkey, //32 + pub max_staleness: i64, //8 + + //TODO: add this? + // pub fallback_oracle: Pubkey, //32 } // better to save pda keys on chain and always calculate/derive client side? // - if save pubkeys and don't use #[account(seeds=[...])] then need to manually call or save @@ -68,16 +63,17 @@ pub struct Propeller { // or save pda bumps impl Propeller { pub const LEN: usize = 1 + //bump - 4 + //nonce - 32 + //admin - 32 + //wormhole - 32 + //token_bridge + 1 + //is_paused + 32 + //governance_key + 32 + //prepared_governance_key + 8 + //governance_transition_ts + 32 + //pause_key 32 + //swim_usd_mint + 1 + //sender_bump + 1 + //redeemer_bump 32 + //marginal_price_pool 32 + //marginal_price_token_mint 1 + //marginal_price_pool_token_index - 1 + //sender_bump - 1 + //redeemer_bump 8 + //gas_kickstart_amount 8 + //propeller_fee 8 + // secp_verify_init_fee @@ -158,155 +154,39 @@ impl SwimPayloadMessage { 8 + // vaa_sequence 8 + // transfer_amount // swim_payload - RawSwimPayload::LEN; // swim_payload - // 1 + //version - // 32 + //owner - // 1 + // propeller_enabled - // 1 + // gas_kickstart - // 8 + // max_fee - // 2 + // target_token_id - // 16; // memo - // SwimPayload::LEN; // swim_payload -} - -//TODO: look into options for versioning. -// ex - metaplex metadata versioning - (probably not. its messy). -#[derive(PartialEq, Debug, Clone, Default)] -pub struct RawSwimPayload { - /* always required fields */ - pub swim_payload_version: u8, - pub owner: Address, - /* required for all propellerEnabled */ - pub propeller_enabled: bool, - pub gas_kickstart: bool, - pub max_fee: u64, - pub target_token_id: u16, - /* required for swim propeller */ - pub memo: [u8; 16], -} - -impl RawSwimPayload { - pub const LEN: usize = 1 + //version + 1 + //version 32 + //owner 1 + // propeller_enabled 1 + // gas_kickstart 8 + // max_fee 2 + // target_token_id 16; // memo -} -#[repr(u8)] -#[derive(PartialEq, Debug, Clone)] -pub enum SwimPayloadVersion { - V0 = 0, - V1 = 1, -} - -impl AnchorDeserialize for RawSwimPayload { - fn deserialize(buf: &mut &[u8]) -> std::io::Result { - let mut v = Cursor::new(buf); - - //TODO: add some error handling/checking here if payload version is incorrect. - // https://stackoverflow.com/questions/28028854/how-do-i-match-enum-values-with-an-integer - let swim_payload_version = v.read_u8()?; - // if swim_payload_version == 1 { - // deseraialize_swim_payload_v1() - // } - if swim_payload_version != CURRENT_SWIM_PAYLOAD_VERSION { - return Err(std::io::Error::new(ErrorKind::InvalidInput, "Wrong Swim Payload Version".to_string())); - } - - let mut owner: [u8; 32] = Address::default(); - v.read_exact(&mut owner)?; - - /* optional fields */ - match v.read_u8() { - Ok(propeller_enabled_val) => { - let propeller_enabled = !(propeller_enabled_val == 0); - let gas_kickstart = !(v.read_u8()? == 0); - let max_fee = v.read_u64::()?; - let target_token_id = v.read_u16::()?; - // optional memo field - let mut memo: [u8; 16] = [0; 16]; - if let Ok(_) = v.read_exact(&mut memo) { - Ok(RawSwimPayload { - swim_payload_version, - owner, - propeller_enabled, - gas_kickstart, - max_fee, - target_token_id, - memo, - }) - } else { - Ok(RawSwimPayload { - swim_payload_version, - owner, - propeller_enabled, - gas_kickstart, - max_fee, - target_token_id, - memo: [0; 16], - }) - } - } - Err(error) => match error.kind() { - ErrorKind::UnexpectedEof => Ok(RawSwimPayload { swim_payload_version, owner, ..Default::default() }), - _ => return Err(error), - }, - } - } -} - -impl AnchorSerialize for RawSwimPayload { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - // Payload ID - // writer.write_u8(self.swim_payload_version)?; - writer.write_u8(CURRENT_SWIM_PAYLOAD_VERSION)?; - - writer.write_all(&self.owner)?; - writer.write_u8(if self.propeller_enabled { 1 } else { 0 })?; - writer.write_u8(if self.gas_kickstart { 1 } else { 0 })?; - writer.write_u64::(self.max_fee)?; - writer.write_u16::(self.target_token_id)?; - writer.write_all(&self.memo)?; - Ok(()) - } + // RawSwimPayload::LEN; // swim_payload + // 1 + //version + // 32 + //owner + // 1 + // propeller_enabled + // 1 + // gas_kickstart + // 8 + // max_fee + // 2 + // target_token_id + // 16; // memo + // SwimPayload::LEN; // swim_payload } +/// Utility function that verifies: +/// 1. pool_token_index < TOKEN_COUNT +/// 2. marginal_price_pool_token_accounts[pool_token_index].mint = propeller.marginal_price_pool_token_mint +/// 3. marginal_price_pool.token_mint_keys[pool_token_index] = propeller.marginal_price_pool_token_mint pub fn validate_marginal_prices_pool_accounts<'info>( - propeller: &Propeller, - marginal_price_pool_key: &Pubkey, - marginal_price_pool_token_account_mints: &[Pubkey; TOKEN_COUNT], -) -> Result<()> { - require_keys_eq!(*marginal_price_pool_key, propeller.marginal_price_pool); - let pool_token_index = propeller.marginal_price_pool_token_index as usize; - require_gt!(TOKEN_COUNT, pool_token_index, PropellerError::InvalidMarginalPricePoolAccounts); - require_keys_eq!( - marginal_price_pool_token_account_mints[pool_token_index], - propeller.marginal_price_pool_token_mint, - ); - // match pool_token_index { - // 0 => require_keys_eq!(marginal_price_pool_token_account_mints[1], propeller.token_mint), - // } - // require_keys_eq!(marginal_price_pool_key.token_keys[pool_token_index], propeller); - Ok(()) -} - -pub fn validate_marginal_prices_pool_accounts_2<'info>( propeller: &Propeller, marginal_price_pool: &Account<'info, TwoPool>, marginal_price_pool_token_accounts: &[&Account<'info, TokenAccount>; TOKEN_COUNT], ) -> Result<()> { - let marginal_price_pool_key = &marginal_price_pool.key(); - require_keys_eq!(*marginal_price_pool_key, propeller.marginal_price_pool); let pool_token_index = propeller.marginal_price_pool_token_index as usize; require_gt!(TOKEN_COUNT, pool_token_index, PropellerError::InvalidMarginalPricePoolAccounts); let pool_token_account = marginal_price_pool_token_accounts[pool_token_index]; - require_keys_eq!(pool_token_account.mint, propeller.marginal_price_pool_token_mint,); - // match pool_token_index { - // 0 => require_keys_eq!(marginal_price_pool_token_accounts[1], propeller.token_mint), - // } + require_keys_eq!(pool_token_account.mint, propeller.marginal_price_pool_token_mint); + require_keys_eq!(marginal_price_pool.token_mint_keys[pool_token_index], propeller.marginal_price_pool_token_mint); require_keys_eq!(pool_token_account.key(), marginal_price_pool_token_accounts[pool_token_index].key()); Ok(()) } diff --git a/packages/solana-contracts/programs/propeller/src/token_bridge.rs b/packages/solana-contracts/programs/propeller/src/token_bridge.rs index 0cb7a79fc..3f54698ea 100644 --- a/packages/solana-contracts/programs/propeller/src/token_bridge.rs +++ b/packages/solana-contracts/programs/propeller/src/token_bridge.rs @@ -1,8 +1,22 @@ use { - anchor_lang::{prelude::*, solana_program::pubkey}, + anchor_lang::{ + prelude::*, + solana_program::{declare_id, pubkey}, + }, borsh::{BorshDeserialize, BorshSerialize}, }; - +// mod token_bridge { +// use super::*; +// #[cfg(all(feature = "localnet", not(feature = "devnet"), not(feature = "mainnet")))] +// declare_id!("B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE"); +// +// // #[cfg(feature = "devnet")] +// #[cfg(all(feature = "devnet", not(feature = "localnet"), not(feature = "mainnet")))] +// declare_id!("DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe"); +// +// #[cfg(all(feature = "mainnet", not(feature = "localnet"), not(feature = "devnet")))] +// declare_id!("wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"); +// } #[derive(Debug, Clone)] pub struct TokenBridge; @@ -11,6 +25,7 @@ impl anchor_lang::Id for TokenBridge { // #[cfg(feature = "localnet")] #[cfg(all(feature = "localnet", not(feature = "devnet"), not(feature = "mainnet")))] fn id() -> Pubkey { + // declare_id!("B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE") pubkey!("B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE") } @@ -18,11 +33,13 @@ impl anchor_lang::Id for TokenBridge { #[cfg(all(feature = "devnet", not(feature = "localnet"), not(feature = "mainnet")))] fn id() -> Pubkey { + // declare_id!("DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe") pubkey!("DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe") } #[cfg(all(feature = "mainnet", not(feature = "localnet"), not(feature = "devnet")))] fn id() -> Pubkey { + // declare_id!("wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb") pubkey!("wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb") } } diff --git a/packages/solana-contracts/programs/propeller/src/wormhole.rs b/packages/solana-contracts/programs/propeller/src/wormhole.rs index 3d17a44b7..6a31f6bd0 100644 --- a/packages/solana-contracts/programs/propeller/src/wormhole.rs +++ b/packages/solana-contracts/programs/propeller/src/wormhole.rs @@ -1,5 +1,5 @@ use { - crate::{Propeller, PropellerError, RawSwimPayload}, + crate::{constants::CURRENT_SWIM_PAYLOAD_VERSION, Propeller, PropellerError}, anchor_lang::{prelude::*, solana_program::pubkey}, borsh::{BorshDeserialize, BorshSerialize}, byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}, @@ -94,13 +94,48 @@ pub struct BridgeConfig { /// [From womrhole repo] #[repr(transparent)] -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct PostedMessageData { pub message: MessageData, } +impl AnchorSerialize for PostedMessageData { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + writer.write(b"msg")?; + BorshSerialize::serialize(&self.message, writer) + } +} + +impl AnchorDeserialize for PostedMessageData { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + *buf = &buf[3..]; + Ok(PostedMessageData { message: ::deserialize(buf)? }) + } +} +impl anchor_lang::Owner for PostedMessageData { + fn owner() -> Pubkey { + Wormhole::id() + } +} + +impl anchor_lang::AccountDeserialize for PostedMessageData { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { + *buf = &buf[3..]; + Ok(::deserialize(buf)?) + } +} +impl anchor_lang::AccountSerialize for PostedMessageData {} + +impl Deref for PostedMessageData { + type Target = PostedMessageData; + + fn deref(&self) -> &Self { + &self + } +} + // #[derive(Debug, Default, BorshDeserialize, BorshSerialize)] -#[derive(Debug, AnchorDeserialize, AnchorSerialize, Clone)] +#[derive(Debug, AnchorDeserialize, AnchorSerialize, Clone, PartialEq)] pub struct MessageData { /// Header of the posted VAA pub vaa_version: u8, @@ -155,6 +190,101 @@ impl anchor_lang::Owner for MessageData { impl anchor_lang::AccountSerialize for MessageData {} +// #[derive(PartialEq, Debug, Clone)] +// pub struct PayloadTransferWithPayload { +// pub message_type: u8, +// /// Amount being transferred (big-endian uint256) +// pub amount: U256, +// +// //TODO: safe to assume pubkey for these since this should only be used for +// // completeTransferWithPayload(e.g. solana side)? +// /// Address of the token. Left-zero-padded if shorter than 32 bytes +// pub token_address: Address, +// /// Chain ID of the token +// pub token_chain: ChainID, +// /// Address of the recipient. Left-zero-padded if shorter than 32 bytes +// pub to: Address, +// /// Chain ID of the recipient +// pub to_chain: ChainID, +// +// /// TODO: only this one needs to be `Address` since it should come from the evm contract/user +// /// Sender of the transaction +// pub from_address: Address, +// /// Arbitrary payload +// // pub payload: Vec, +// // pub payload: RawSwimPayload, +// } +// +// impl AnchorDeserialize for PayloadTransferWithPayload { +// fn deserialize(buf: &mut &[u8]) -> std::io::Result { +// let mut v = Cursor::new(buf); +// let message_type = v.read_u8()?; +// if message_type != 3 { +// // return Err(error!(PropellerError::InvalidPayloadTypeInVaa)).into() +// // return Err(ProgramError::BorshIoError("Wrong Payload Type".to_string()).into()); +// return Err(std::io::Error::new(ErrorKind::InvalidInput, "Wrong Payload Type".to_string())); +// // return Err(PropellerError::InvalidPayloadTypeInVaa); +// }; +// +// let mut am_data: [u8; 32] = [0; 32]; +// v.read_exact(&mut am_data)?; +// let amount = U256::from_big_endian(&am_data); +// +// let mut token_address = Address::default(); +// v.read_exact(&mut token_address)?; +// +// let token_chain = v.read_u16::()?; +// +// let mut to = Address::default(); +// v.read_exact(&mut to)?; +// +// let to_chain = v.read_u16::()?; +// +// let mut from_address = Address::default(); +// v.read_exact(&mut from_address)?; +// +// let mut payload = vec![]; +// v.read_to_end(&mut payload)?; +// let swim_payload = RawSwimPayload::deserialize(&mut payload.as_slice())?; +// +// Ok(PayloadTransferWithPayload { +// message_type, +// amount, +// token_address, +// token_chain, +// to, +// to_chain, +// from_address, +// payload: swim_payload, +// }) +// } +// } +// +// //TODO: probably not needed since we shouldn't be serializing any payload directly. +// // this would be handled in CPI +// impl AnchorSerialize for PayloadTransferWithPayload { +// fn serialize(&self, writer: &mut W) -> std::io::Result<()> { +// // Payload ID +// writer.write_u8(3)?; +// +// let mut am_data: [u8; 32] = [0; 32]; +// self.amount.to_big_endian(&mut am_data); +// writer.write_all(&am_data)?; +// +// writer.write_all(&self.token_address)?; +// writer.write_u16::(self.token_chain)?; +// writer.write_all(&self.to)?; +// writer.write_u16::(self.to_chain)?; +// +// writer.write_all(&self.from_address)?; +// +// AnchorSerialize::serialize(&self.payload, writer)?; +// // writer.write_all(self.payload.as_slice())?; +// +// Ok(()) +// } +// } + #[derive(PartialEq, Debug, Clone)] pub struct PayloadTransferWithPayload { pub message_type: u8, @@ -177,7 +307,8 @@ pub struct PayloadTransferWithPayload { pub from_address: Address, /// Arbitrary payload // pub payload: Vec, - pub payload: RawSwimPayload, + // pub payload: RawSwimPayload, + pub payload: SwimPayload, } impl AnchorDeserialize for PayloadTransferWithPayload { @@ -210,7 +341,7 @@ impl AnchorDeserialize for PayloadTransferWithPayload { let mut payload = vec![]; v.read_to_end(&mut payload)?; - let swim_payload = RawSwimPayload::deserialize(&mut payload.as_slice())?; + let swim_payload = SwimPayload::deserialize(&mut payload.as_slice())?; Ok(PayloadTransferWithPayload { message_type, @@ -243,27 +374,14 @@ impl AnchorSerialize for PayloadTransferWithPayload { writer.write_all(&self.from_address)?; - AnchorSerialize::serialize(&self.payload, writer)?; + self.payload.serialize(writer)?; + // AnchorSerialize::serialize(&self.payload, writer)?; // writer.write_all(self.payload.as_slice())?; Ok(()) } } -impl AnchorSerialize for PostedMessageData { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - writer.write(b"msg")?; - BorshSerialize::serialize(&self.message, writer) - } -} - -impl AnchorDeserialize for PostedMessageData { - fn deserialize(buf: &mut &[u8]) -> std::io::Result { - *buf = &buf[3..]; - Ok(PostedMessageData { message: ::deserialize(buf)? }) - } -} - pub fn get_message_data(vaa_account: &AccountInfo) -> Result { Ok(PostedMessageData::try_from_slice(&vaa_account.data.borrow())?.message) } @@ -278,9 +396,6 @@ pub fn deserialize_message_payload(buf: &mut &[u8]) -> Res Ok(T::deserialize(buf)?) } -pub fn deserialize_swim_payload(buf: &mut &[u8]) -> Result { - Ok(RawSwimPayload::deserialize(buf)?) -} /** Adding PostedVAA version here for parity */ #[repr(transparent)] @@ -411,3 +526,91 @@ pub fn hash_vaa(vaa: &PostVAAData) -> [u8; 32] { h.write_all(body.as_slice()).unwrap(); h.finalize().into() } + +#[derive(PartialEq, Debug, Clone, Default)] +pub struct SwimPayload { + //TOOD: should this come from propeller? + //required + pub swim_payload_version: u8, + pub owner: [u8; 32], + // required for all propellerEngines + pub propeller_enabled: Option, + pub gas_kickstart: Option, + pub max_fee: Option, + pub target_token_id: Option, + // required for SWIM propellerEngine + pub memo: Option<[u8; 16]>, +} + +impl AnchorSerialize for SwimPayload { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + // Payload ID + // writer.write_u8(self.swim_payload_version)?; + writer.write_u8(self.swim_payload_version)?; + writer.write_all(&self.owner)?; + + if self.propeller_enabled.is_some() { + writer.write_u8(1)?; + writer.write_u8(self.gas_kickstart.unwrap() as u8)?; + writer.write_u64::(self.max_fee.unwrap())?; + writer.write_u16::(self.target_token_id.unwrap())?; + if self.memo.is_some() { + writer.write_all(&self.memo.unwrap())?; + } + } + Ok(()) + } +} + +impl AnchorDeserialize for SwimPayload { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + let mut v = Cursor::new(buf); + + //TODO: add some error handling/checking here if payload version is incorrect. + // https://stackoverflow.com/questions/28028854/how-do-i-match-enum-values-with-an-integer + let swim_payload_version = v.read_u8()?; + if swim_payload_version != CURRENT_SWIM_PAYLOAD_VERSION { + return Err(std::io::Error::new(ErrorKind::InvalidInput, "Wrong Swim Payload Version".to_string())); + } + + let mut owner: [u8; 32] = Address::default(); + v.read_exact(&mut owner)?; + + /* optional fields */ + match v.read_u8() { + Ok(propeller_enabled_val) => { + let propeller_enabled = !(propeller_enabled_val == 0); + let gas_kickstart = !(v.read_u8()? == 0); + let max_fee = v.read_u64::()?; + let target_token_id = v.read_u16::()?; + // optional memo field + let mut memo: [u8; 16] = [0; 16]; + if let Ok(_) = v.read_exact(&mut memo) { + Ok(SwimPayload { + swim_payload_version, + owner, + propeller_enabled: Some(propeller_enabled), + gas_kickstart: Some(gas_kickstart), + max_fee: Some(max_fee), + target_token_id: Some(target_token_id), + memo: Some(memo), + }) + } else { + Ok(SwimPayload { + swim_payload_version, + owner, + propeller_enabled: Some(propeller_enabled), + gas_kickstart: Some(gas_kickstart), + max_fee: Some(max_fee), + target_token_id: Some(target_token_id), + memo: None, + }) + } + } + Err(error) => match error.kind() { + ErrorKind::UnexpectedEof => Ok(SwimPayload { swim_payload_version, owner, ..Default::default() }), + _ => return Err(error), + }, + } + } +} diff --git a/packages/solana-contracts/programs/two-pool/src/instructions/add.rs b/packages/solana-contracts/programs/two-pool/src/instructions/add.rs index dba350348..1ec0e709c 100644 --- a/packages/solana-contracts/programs/two-pool/src/instructions/add.rs +++ b/packages/solana-contracts/programs/two-pool/src/instructions/add.rs @@ -1,10 +1,7 @@ use { crate::{common, error::*, gen_pool_signer_seeds, invariant::Invariant, TwoPool, TOKEN_COUNT}, anchor_lang::prelude::*, - anchor_spl::{ - associated_token::get_associated_token_address, - token::{self, Mint, Token, TokenAccount}, - }, + anchor_spl::token::{self, Mint, Token, TokenAccount}, std::iter::zip, }; @@ -21,9 +18,6 @@ pub struct Add<'info> { bump = pool.bump, )] pub pool: Box>, - // /// TODO: could be removed if initialized with pool_v2 - // /// CHECK: checked in CPI - // pub pool_auth: UncheckedAccount<'info>, #[account( mut, address = pool.token_keys[0] @ PoolError::PoolTokenAccountExpected, @@ -45,7 +39,6 @@ pub struct Add<'info> { address = pool.governance_fee_key @ PoolError::InvalidGovernanceFeeAccount, )] pub governance_fee: Box>, - ///CHECK: checked in CPI pub user_transfer_authority: Signer<'info>, #[account( mut, diff --git a/packages/solana-contracts/programs/two-pool/src/instructions/governance/common_governance.rs b/packages/solana-contracts/programs/two-pool/src/instructions/governance/common_governance.rs index 7fba51476..7fa2500ce 100644 --- a/packages/solana-contracts/programs/two-pool/src/instructions/governance/common_governance.rs +++ b/packages/solana-contracts/programs/two-pool/src/instructions/governance/common_governance.rs @@ -9,8 +9,8 @@ pub struct CommonGovernance<'info> { mut, seeds = [ b"two_pool".as_ref(), - pool.get_token_mint_0().unwrap().as_ref(), - pool.get_token_mint_1().unwrap().as_ref(), + pool.get_token_mint_0().as_ref(), + pool.get_token_mint_1().as_ref(), pool.lp_mint_key.as_ref(), ], bump = pool.bump diff --git a/packages/solana-contracts/programs/two-pool/src/instructions/governance/set_paused.rs b/packages/solana-contracts/programs/two-pool/src/instructions/governance/set_paused.rs index 2954883ad..b25a53dcf 100644 --- a/packages/solana-contracts/programs/two-pool/src/instructions/governance/set_paused.rs +++ b/packages/solana-contracts/programs/two-pool/src/instructions/governance/set_paused.rs @@ -1,8 +1,5 @@ use { - crate::{ - error::*, - TwoPool, - }, + crate::{error::*, TwoPool}, anchor_lang::prelude::*, }; @@ -12,8 +9,8 @@ pub struct SetPaused<'info> { mut, seeds = [ b"two_pool".as_ref(), - pool.get_token_mint_0().unwrap().as_ref(), - pool.get_token_mint_1().unwrap().as_ref(), + pool.get_token_mint_0().as_ref(), + pool.get_token_mint_1().as_ref(), pool.lp_mint_key.as_ref(), ], bump = pool.bump diff --git a/packages/solana-contracts/programs/two-pool/src/instructions/initialize.rs b/packages/solana-contracts/programs/two-pool/src/instructions/initialize.rs index 92e18a700..88b564592 100644 --- a/packages/solana-contracts/programs/two-pool/src/instructions/initialize.rs +++ b/packages/solana-contracts/programs/two-pool/src/instructions/initialize.rs @@ -148,28 +148,5 @@ pub fn handle_initialize( two_pool.prepared_governance_fee = PoolFee::default(); two_pool.fee_transition_ts = 0; two_pool.previous_depth = 0; - - /** - &PoolState { - nonce, - is_paused: false, - amp_factor: AmpFactor::new(amp_factor)?, - lp_fee: PoolFee::new(lp_fee)?, - governance_fee: PoolFee::new(governance_fee)?, - lp_mint_key: lp_mint_account.key.clone(), - lp_decimal_equalizer: decimal_range_max - lp_mint_state.decimals, - token_mint_keys: create_array(|i| token_mint_accounts[i].key.clone()), - token_decimal_equalizers: create_array(|i| decimal_range_max - token_decimals[i]), - token_keys: create_array(|i| token_accounts[i].key.clone()), - governance_key: governance_account.key.clone(), - governance_fee_key: governance_fee_account.key.clone(), - prepared_governance_key: Pubkey::default(), - governance_transition_ts: 0, - prepared_lp_fee: PoolFee::default(), - prepared_governance_fee: PoolFee::default(), - fee_transition_ts: 0, - previous_depth: 0, - }, - */ Ok(()) } diff --git a/packages/solana-contracts/programs/two-pool/src/state.rs b/packages/solana-contracts/programs/two-pool/src/state.rs index 771200e38..ac07efb97 100644 --- a/packages/solana-contracts/programs/two-pool/src/state.rs +++ b/packages/solana-contracts/programs/two-pool/src/state.rs @@ -1,6 +1,6 @@ use { crate::{amp_factor::AmpFactor, pool_fee::PoolFee, TOKEN_COUNT}, - anchor_lang::{prelude::*}, + anchor_lang::prelude::*, }; // use pool_lib::amp_factor::AmpFactor; @@ -45,54 +45,42 @@ impl TwoPool { // self.lp_mint_key != Pubkey::default() // } - pub const LEN: usize = - // nonce - 1 + - // is_paused - 1 + - // amp_factor - AmpFactor::LEN + - // lp_fee - PoolFee::LEN + - // governance_fee - PoolFee::LEN + - // lp_mint_key - 32 + - // lp_decimal_equalizer - 1 + - // token_mint_keys - 32 * TOKEN_COUNT + - // token_decimal_equalizers - TOKEN_COUNT + - // token_keys - 32 * TOKEN_COUNT + - // pause_key - 32 + - // governance_key - 32 + - // governance_fee_key - 32 + - // prepared_governance_key - 32 + - // governance_transition_ts - 8 + - // prepared_lp_fee - PoolFee::LEN + - // prepared_governance_fee - PoolFee::LEN + - // fee_transition_ts - 8 + - // previous_depth - 16; + pub const LEN: usize = 1 + // nonce + 1 + // is_paused + AmpFactor::LEN + // amp_factor + PoolFee::LEN + // lp_fee + PoolFee::LEN + // governance_fee + 32 + // lp_mint_key + 1 + // lp_decimal_equalizer + (32 * TOKEN_COUNT) + // token_mint_keys + TOKEN_COUNT + // token_decimal_equalizers + (32 * TOKEN_COUNT) + // token_keys + 32 + // pause_key + 32 + // governance_key + 32 + // governance_fee_key + 32 + // prepared_governance_key + 8 + // governance_transition_ts + PoolFee::LEN + // prepared_lp_fee + PoolFee::LEN + // prepared_governance_fee + 8 + // fee_transition_ts + 16; // previous_depth // Note: this is a workaround for to be able to declare the seeds in the // governance ix. anchor does not handle using `pool.token_mint_keys[0]` directly // in the #[account] macro - pub fn get_token_mint_0(&self) -> Result { - Ok(self.token_mint_keys[0]) + pub fn get_token_mint_0(&self) -> Pubkey { + self.token_mint_keys[0] } - pub fn get_token_mint_1(&self) -> Result { - Ok(self.token_mint_keys[1]) + pub fn get_token_mint_1(&self) -> Pubkey { + self.token_mint_keys[1] + } + + pub fn new_get_token_mint_0(&self) -> Pubkey { + self.token_mint_keys[0] + } + + pub fn new_get_token_mint_1(&self) -> Pubkey { + self.token_mint_keys[1] } } diff --git a/packages/solana-contracts/scripts/initializePropeller.ts b/packages/solana-contracts/scripts/initializePropeller.ts index 04190d7d8..b97e791ab 100644 --- a/packages/solana-contracts/scripts/initializePropeller.ts +++ b/packages/solana-contracts/scripts/initializePropeller.ts @@ -1,9 +1,11 @@ +import crypto from "crypto"; import * as fs from "fs"; import * as path from "path"; import type { ChainId } from "@certusone/wormhole-sdk"; import { CHAIN_ID_ETH, + createNonce, tryHexToNativeString, tryNativeToHexString, } from "@certusone/wormhole-sdk"; @@ -33,13 +35,14 @@ import { TWO_POOL_PID, USDC_TO_TOKEN_NUMBER, USDT_TO_TOKEN_NUMBER, - setComputeUnitLimitIx, maxStaleness, + maxStaleness, + setComputeUnitLimitIx, } from "../src/__tests__/consts"; import { getPropellerPda, getPropellerRedeemerPda, getTargetChainIdMapAddr, - getTargetTokenIdMapAddr, + getToTokenNumberMapAddr, } from "../src/__tests__/propeller/propellerUtils"; // import type { Propeller } from "../src/artifacts/propeller"; @@ -61,7 +64,8 @@ const propellerProgram = new Program( const splToken = Spl.token(provider); const payer = (provider.wallet as NodeWallet).payer; -const propellerAdmin = payer; +const propellerGovernanceKey = payer; +const propellerPauseKey = payer; type InitParameters = { readonly gasKickstartAmount: BN; @@ -160,7 +164,7 @@ async function setupPropeller() { console.info(`swimUsdPoolInfo: ${JSON.stringify(swimUsdPoolInfo)}`); propellerInfo = await initializePropellerState(); console.info(`propellerInfo: ${JSON.stringify(propellerInfo, null, 2)}`); - targetTokenIdMaps = await createTargetTokenIdMaps(); + targetTokenIdMaps = await createTokenNumberMaps(); console.info( `targetTokenIdMaps: ${JSON.stringify( Object.fromEntries(targetTokenIdMaps), @@ -168,7 +172,7 @@ async function setupPropeller() { ); await fetchAndPrintIdMap( targetTokenIdMaps, - async (addr) => await propellerProgram.account.tokenIdMap.fetch(addr), + async (addr) => await propellerProgram.account.tokenNumberMap.fetch(addr), // propellerProgram.account.tokenIdMap.fetch, "targetTokenId", ); @@ -285,9 +289,9 @@ async function initializePropellerState(): Promise { propellerFeeVault: ${propellerFeeVault.toString()} propellerRedeemer: ${propellerRedeemer.toString()} propellerRedeemerEscrow: ${propellerRedeemerEscrow.toString()} - propellerAdmin: ${propellerAdmin.publicKey.toString()} + propellerGovernanceKey: ${propellerGovernanceKey.publicKey.toString()} + propellerPauseKey: ${propellerPauseKey.publicKey.toString()} payer: ${payer.publicKey.toString()} - `); let propellerData = await propellerProgram.account.propeller.fetchNullable( @@ -301,7 +305,8 @@ async function initializePropellerState(): Promise { propeller: propellerAddr, propellerRedeemerEscrow, propellerFeeVault, - admin: propellerAdmin.publicKey, + governanceKey: propellerGovernanceKey.publicKey, + pauseKey: propellerGovernanceKey.publicKey, swimUsdMint, payer: payer.publicKey, tokenProgram: TOKEN_PROGRAM_ID, @@ -315,7 +320,7 @@ async function initializePropellerState(): Promise { twoPoolProgram: twoPoolProgram.programId, aggregator, }) - .signers([propellerAdmin, payer]) + .signers([propellerGovernanceKey, payer]) .rpc(); } propellerData = await propellerProgram.account.propeller.fetch(propellerAddr); @@ -330,83 +335,89 @@ async function initializePropellerState(): Promise { }; } -async function createTargetTokenIdMaps(): Promise< +async function createTokenNumberMaps(): Promise< ReadonlyMap > { - const swimUsdTokenIdMap = { + const swimUsdTokenNumberMap = { pool: swimUsdPoolInfo.address, poolTokenIndex: 0, poolTokenMint: swimUsdPoolInfo.lpMint, - poolIx: { transfer: {} }, + toTokenStep: { transfer: {} }, }; - const usdcTokenIdMap = { + const usdcTokenNumberMap = { pool: swimUsdPoolInfo.address, poolTokenIndex: 0, poolTokenMint: swimUsdPoolInfo.tokenMints[0], - poolIx: { removeExactBurn: {} }, + toTokenStep: { removeExactBurn: {} }, }; - const usdtTokenIdMap = { + const usdtTokenNumberMap = { pool: swimUsdPoolInfo.address, poolTokenIndex: 1, poolTokenMint: swimUsdPoolInfo.tokenMints[1], - poolIx: { removeExactBurn: {} }, + toTokenStep: { removeExactBurn: {} }, }; - const outputTokenIdMapAddrEntries = await Promise.all( + const tokenNumberMapAddrEntries = await Promise.all( [ { - targetTokenId: SWIM_USD_TO_TOKEN_NUMBER, - tokenIdMap: swimUsdTokenIdMap, + toTokenNumber: SWIM_USD_TO_TOKEN_NUMBER, + tokenNumberMap: swimUsdTokenNumberMap, + }, + { + toTokenNumber: USDC_TO_TOKEN_NUMBER, + tokenNumberMap: usdcTokenNumberMap, + }, + { + toTokenNumber: USDT_TO_TOKEN_NUMBER, + tokenNumberMap: usdtTokenNumberMap, }, - { targetTokenId: USDC_TO_TOKEN_NUMBER, tokenIdMap: usdcTokenIdMap }, - { targetTokenId: USDT_TO_TOKEN_NUMBER, tokenIdMap: usdtTokenIdMap }, - ].map(async ({ targetTokenId, tokenIdMap }) => { - const [tokenIdMapAddr] = await getTargetTokenIdMapAddr( + ].map(async ({ toTokenNumber, tokenNumberMap }) => { + const [toTokenNumberMapAddr] = await getToTokenNumberMapAddr( propellerInfo.address, - targetTokenId, + toTokenNumber, propellerProgram.programId, ); if ( - !(await propellerProgram.account.tokenIdMap.fetchNullable( - tokenIdMapAddr, + !(await propellerProgram.account.tokenNumberMap.fetchNullable( + toTokenNumberMapAddr, )) ) { const createTokenIdMapTxn = propellerProgram.methods - .createTokenIdMap( - targetTokenId, - tokenIdMap.pool, - tokenIdMap.poolTokenIndex, - tokenIdMap.poolTokenMint, - tokenIdMap.poolIx, + .createTokenNumberMap( + toTokenNumber, + tokenNumberMap.pool, + tokenNumberMap.poolTokenIndex, + tokenNumberMap.poolTokenMint, + tokenNumberMap.toTokenStep, ) .accounts({ propeller: propellerInfo.address, - admin: propellerAdmin.publicKey, + governanceKey: propellerGovernanceKey.publicKey, payer: payer.publicKey, systemProgram: web3.SystemProgram.programId, // rent: web3.SYSVAR_RENT_PUBKEY, - pool: tokenIdMap.pool, + pool: tokenNumberMap.pool, twoPoolProgram: twoPoolProgram.programId, }) - .signers([propellerAdmin]); + .signers([propellerGovernanceKey]); const pubkeys = await createTokenIdMapTxn.pubkeys(); await createTokenIdMapTxn.rpc(); - const derivedTokenIdMapAddr = pubkeys.tokenIdMap; - if (!derivedTokenIdMapAddr) { + const derivedTokenNumberMapAddr = pubkeys.tokenNumberMap; + if (!derivedTokenNumberMapAddr) { throw new Error("Failed to derive tokenIdMapAddr"); } console.info(` - derivedTokenIdMapAddr: ${derivedTokenIdMapAddr.toString()} - tokenIdMapAddr: ${tokenIdMapAddr.toString()} + derivedTokenIdMapAddr: ${derivedTokenNumberMapAddr.toString()} + toTokenNumberMapAddr: ${toTokenNumberMapAddr.toString()} `); } - return { targetTokenId, tokenIdMapAddr }; + return { toTokenNumber, toTokenNumberMapAddr }; }), ); return new Map( - outputTokenIdMapAddrEntries.map(({ targetTokenId, tokenIdMapAddr }) => { - return [targetTokenId, tokenIdMapAddr]; + tokenNumberMapAddrEntries.map(({ toTokenNumber, toTokenNumberMapAddr }) => { + return [toTokenNumber, toTokenNumberMapAddr]; }), ); } @@ -466,13 +477,13 @@ async function createTargetChainMaps() { .createTargetChainMap(wormholeChainId, targetAddrWormholeFormat) .accounts({ propeller: propellerInfo.address, - admin: propellerAdmin.publicKey, + governanceKey: propellerGovernanceKey.publicKey, payer: payer.publicKey, targetChainMap: targetChainMapAddr, systemProgram: web3.SystemProgram.programId, // rent: web3.SYSVAR_RENT_PUBKEY, }) - .signers([propellerAdmin]) + .signers([propellerGovernanceKey]) .rpc(); } @@ -546,7 +557,6 @@ async function transferTokens() { payer.publicKey, ); - let memo = 0; // const evmTargetTokenIds = [0, 1]; const evmTargetTokenIds = [0]; const targetChain = CHAIN_ID_ETH; @@ -603,10 +613,7 @@ async function transferTokens() { // crossChainTransferNativeWithPayloadTxnSig: ${crossChainTransferNativeWithPayloadTxnSig}`, // ); - const memoStr = (++memo).toString().padStart(16, "0"); - const memoBuffer2 = Buffer.alloc(16); - memoBuffer2.write(memoStr); - + const memoBuffer = createMemoId(); const propellerEnabledTransferAmount = transferAmount.div(new BN(2)); const propellerEnabledWormholeMessage = web3.Keypair.generate(); @@ -619,21 +626,23 @@ async function transferTokens() { targetChain: ${targetChain}, targetOwner(native): ${evmOwnerNative}, targetOwner(Hex): ${evmOwnerEthHexStr}, - memo(str): ${memoStr}, - memo(buffer): ${memoBuffer2.toString("hex")}, + memo("hex"): ${memoBuffer.toString("hex")}, + ) `); + const nonce = createNonce().readUInt32LE(0); const propellerTransferNativeWithPayloadTxnSig: string = await propellerProgram.methods .propellerTransferNativeWithPayload( + nonce, propellerEnabledTransferAmount, targetChain, evmOwner, gasKickstart, maxFee, evmTargetTokenId, - memoBuffer2, + memoBuffer, ) .accounts({ propeller: propellerInfo.address, @@ -660,7 +669,7 @@ async function transferTokens() { tokenProgram: splToken.programId, }) .preInstructions([setComputeUnitLimitIx]) - .postInstructions([createMemoInstruction(memoStr)]) + .postInstructions([createMemoInstruction(memoBuffer.toString("hex"))]) .signers([payer, propellerEnabledWormholeMessage]) .rpc(); @@ -671,5 +680,12 @@ async function transferTokens() { ); } +function createMemoId() { + const SWIM_MEMO_LENGTH = 16; + // NOTE: Please always use random bytes to avoid conflicts with other users + return crypto.randomBytes(SWIM_MEMO_LENGTH); + // return (++memoId).toString().padStart(16, "0"); +} + void setupPropeller(); // voidloadConfig(); diff --git a/packages/solana-contracts/scripts/parseAccount.ts b/packages/solana-contracts/scripts/parseAccount.ts new file mode 100644 index 000000000..acc96678e --- /dev/null +++ b/packages/solana-contracts/scripts/parseAccount.ts @@ -0,0 +1,44 @@ +import { AnchorProvider, web3 } from "@project-serum/anchor"; + +import { + formatParsedTokenTransferWithSwimPayloadPostedMessage, + parseTokenTransferWithSwimPayloadPostedMessage, +} from "../src/__tests__/propeller/propellerUtils"; + +const envProvider = AnchorProvider.env(); + +async function parseWormholeMessageAccount(address: web3.PublicKey) { + const wormholeMessageAccountInfo = + await envProvider.connection.getAccountInfo(address); + if (!wormholeMessageAccountInfo) { + console.error(`No account found at ${address.toBase58()}`); + return; + } + const parsedTokenTransferWithSwimPayloadPostedMessage = + await parseTokenTransferWithSwimPayloadPostedMessage( + wormholeMessageAccountInfo.data, + ); + console.info(`finished parsing`); + const formattedMessage = + formatParsedTokenTransferWithSwimPayloadPostedMessage( + parsedTokenTransferWithSwimPayloadPostedMessage, + ); + console.info( + `formattedMessage: ${JSON.stringify(formattedMessage, null, 2)}`, + ); +} + +const main = async (): Promise => { + const [address] = process.argv.slice(2); + if (!address) { + console.error("Please provide a wormhole message account address"); + process.exit(1); + } + console.info(`address: ${address}`); + await parseWormholeMessageAccount(new web3.PublicKey(address)); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/solana-contracts/scripts/scratch.ts b/packages/solana-contracts/scripts/scratch.ts index 9c28a0ff0..37ab10c67 100644 --- a/packages/solana-contracts/scripts/scratch.ts +++ b/packages/solana-contracts/scripts/scratch.ts @@ -31,7 +31,7 @@ import { getPropellerPda, getPropellerRedeemerPda, getTargetChainIdMapAddr, - getTargetTokenIdMapAddr, + getToTokenNumberMapAddr, } from "../src/__tests__/propeller/propellerUtils"; import type { Propeller } from "../src/artifacts/propeller"; import type { TwoPool } from "../src/artifacts/two_pool"; @@ -143,8 +143,8 @@ async function setupPropeller() { // ); // await fetchAndPrintIdMap( // targetTokenIdMaps, - // async (addr) => await propellerProgram.account.tokenIdMap.fetch(addr), - // // propellerProgram.account.tokenIdMap.fetch, + // async (addr) => await propellerProgram.account.tokenNumberMap.fetch(addr), + // // propellerProgram.account.tokenNumberMap.fetch, // "targetTokenId", // ); // @@ -258,7 +258,7 @@ async function initializePropellerState(): Promise { propeller: propellerAddr, propellerRedeemerEscrow, propellerFeeVault, - admin: propellerAdmin.publicKey, + governanceKey: propellerAdmin.publicKey, swimUsdMint, payer: payer.publicKey, tokenProgram: TOKEN_PROGRAM_ID, diff --git a/packages/solana-contracts/src/__tests__/consts.ts b/packages/solana-contracts/src/__tests__/consts.ts index b46cdbe0c..27a3c0391 100644 --- a/packages/solana-contracts/src/__tests__/consts.ts +++ b/packages/solana-contracts/src/__tests__/consts.ts @@ -9,15 +9,15 @@ import { LAMPORTS_PER_SOL } from "@solana/web3.js"; export const commitment = "confirmed" as web3.Commitment; export const rpcCommitmentConfig = { - commitment: "confirmed", + commitment, preflightCommitment: commitment, skipPreflight: true, }; -const ethTokenBridgeStr = "0x0290FB167208Af455bB137780163b7B7a9a10C16"; +export const ethTokenBridgeNativeStr = "0x0290FB167208Af455bB137780163b7B7a9a10C16"; //0000000000000000000000000290fb167208af455bb137780163b7b7a9a10c16 const ethTokenBridgeEthHexStr = tryNativeToHexString( - ethTokenBridgeStr, + ethTokenBridgeNativeStr, CHAIN_ID_ETH, ); //ethTokenBridge.toString() = gibberish @@ -52,9 +52,9 @@ export const routingContracts = [ { targetChainId: CHAIN_ID_ETH, address: ethRoutingContract }, { targetChainId: CHAIN_ID_BSC, address: ethRoutingContract }, ]; +//TODO: figure out actual value for compute budget export const setComputeUnitLimitIx: web3.TransactionInstruction = web3.ComputeBudgetProgram.setComputeUnitLimit({ - // units: 420690, units: 900000, }); export const SWIM_USD_TO_TOKEN_NUMBER = 0; @@ -64,11 +64,11 @@ export const marginalPricePoolTokenIndex = 0; export const swimPayloadVersion = 1; export const usdcPoolTokenIndex = 0; export const usdtPoolTokenIndex = 1; -export const metapoolMint1OutputTokenIndex = 3; +export const metapoolMint1ToTokenNumber = 3; export const metapoolMint1PoolTokenIndex = 1; // const evmOwner = Buffer.from(evmOwnerEthHexStr, "hex"); -export const gasKickstartAmount: BN = new BN(0.75 * LAMPORTS_PER_SOL); -export const initAtaFee: BN = new BN(0.25 * LAMPORTS_PER_SOL); +export const gasKickstartAmount: BN = new BN(0.25 * LAMPORTS_PER_SOL); +export const initAtaFee: BN = new BN(0.0025 * LAMPORTS_PER_SOL); export const secpVerifyInitFee: BN = new BN(0.000045 * LAMPORTS_PER_SOL); export const secpVerifyFee: BN = new BN(0.00004 * LAMPORTS_PER_SOL); export const postVaaFee: BN = new BN(0.00005 * LAMPORTS_PER_SOL); diff --git a/packages/solana-contracts/src/__tests__/propeller/engine.test.ts b/packages/solana-contracts/src/__tests__/propeller/engine.test.ts index af1fc0178..304062baf 100644 --- a/packages/solana-contracts/src/__tests__/propeller/engine.test.ts +++ b/packages/solana-contracts/src/__tests__/propeller/engine.test.ts @@ -61,8 +61,8 @@ import { lpFee, marginalPricePoolTokenIndex, maxStaleness, - metapoolMint1OutputTokenIndex, metapoolMint1PoolTokenIndex, + metapoolMint1ToTokenNumber, postVaaFee, processSwimPayloadFee, routingContracts, @@ -75,6 +75,7 @@ import { usdtPoolTokenIndex, } from "../consts"; import { + getOwnerAtaAddrsForPool, getPoolUserBalances, printPoolUserBalances, setupPoolPrereqs, @@ -86,12 +87,11 @@ import { encodeSwimPayload, generatePropellerEngineTxns, getFeeTrackerPda, - getOwnerTokenAccountsForPool, getPropellerPda, getPropellerRedeemerPda, getSwimClaimPda, getSwimPayloadMessagePda, - getTargetTokenIdMapAddr, + getToTokenNumberMapAddr, getWormholeAddressesForMint, } from "./propellerUtils"; import { @@ -156,7 +156,8 @@ let propeller: web3.PublicKey; let propellerSender: web3.PublicKey; let propellerRedeemer: web3.PublicKey; let propellerRedeemerEscrowAccount: web3.PublicKey; -const propellerAdmin: web3.Keypair = web3.Keypair.generate(); +const propellerGovernanceKey: web3.Keypair = web3.Keypair.generate(); +const propellerPauseKey: web3.Keypair = web3.Keypair.generate(); let propellerFeeVault: web3.PublicKey; const initialMintAmount = new BN(100_000_000_000_000); @@ -217,7 +218,7 @@ let marginalPricePoolToken1Account: web3.PublicKey; let marginalPricePoolLpMint: web3.PublicKey; const marginalPricePoolTokenMint = usdcKeypair.publicKey; -let outputTokenIdMappingAddrs: ReadonlyMap; +let toTokenNumberMapAddrs: ReadonlyMap; let wormholeAddresses: WormholeAddresses; let custody: web3.PublicKey; @@ -716,30 +717,6 @@ describe("propeller", () => { await seedWormholeCustody(); console.info(`finished seeing wormhole custody`); - - console.info(`setting up switchboard`); - - // // If fails, fallback to looking for a local env file - // try { - // switchboard = await SwitchboardTestContext.loadFromEnv(provider); - // console.info(`set up switchboard`); - // const aggregatorAccount = await switchboard.createStaticFeed(100); - // aggregator = aggregatorAccount.publicKey; - // // switchboard = await SwitchboardTestContext.loadDevnetQueue( - // // provider, - // // "F8ce7MsckeZAbAGmxjJNetxYXQa9mKr9nnrC3qKubyYy" - // // ); - // // aggregatorKey = DEFAULT_SOL_USD_FEED; - // console.info("local env detected"); - // return; - // } catch (error: any) { - // console.info(`Error: SBV2 Localnet - ${JSON.stringify(error.message)}`); - // throw new Error( - // `Failed to load localenv SwitchboardTestContext: ${JSON.stringify( - // error.message, - // )}`, - // ); - // } }, 50000); describe("propellerEngine CompleteWithPayload and ProcessSwimPayload", () => { @@ -1282,7 +1259,7 @@ describe("propeller", () => { )}`, ); - const swimPayloadMessageAccountTargetTokenId = + const swimPayloadMessageAccountToTokenNumber = swimPayloadMessageAccount.targetTokenId; const propellerRedeemerEscrowBalanceBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) @@ -1298,7 +1275,7 @@ describe("propeller", () => { const processSwimPayloadPubkeys = await propellerEnginePropellerProgram.methods .processSwimPayload( - swimPayloadMessageAccountTargetTokenId, + swimPayloadMessageAccountToTokenNumber, minOutputAmount, ) .accounts({ @@ -1327,7 +1304,7 @@ describe("propeller", () => { const propellerProcessSwimPayloadIxs = propellerEnginePropellerProgram.methods .propellerProcessSwimPayload( - swimPayloadMessageAccountTargetTokenId, + swimPayloadMessageAccountToTokenNumber, ) .accounts({ processSwimPayload: processSwimPayloadPubkeys, @@ -1351,41 +1328,35 @@ describe("propeller", () => { console.info( `${JSON.stringify(propellerProcessSwimPayloadPubkeys, null, 2)}`, ); - if (!processSwimPayloadPubkeys.tokenIdMap) { + if (!processSwimPayloadPubkeys.tokenNumberMap) { throw new Error("tokenIdMap not derived"); } + const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(swimPayloadMessageAccountTargetTokenId).toArrayLike( - Buffer, - "le", - 2, - ), - ], + await getToTokenNumberMapAddr( + propeller, + swimPayloadMessageAccountToTokenNumber, propellerProgram.programId, ); - const expectedTokenIdMap = - outputTokenIdMappingAddrs.get(targetTokenId); - if (!expectedTokenIdMap) { - throw new Error("expectedTokenIdMap not found"); + const expectedTokenNumberMap = + toTokenNumberMapAddrs.get(targetTokenId); + if (!expectedTokenNumberMap) { + throw new Error("expectedTokenNumberMap not found"); } - const expectedTokenIdMapAcct = - await propellerProgram.account.tokenIdMap.fetch( - expectedTokenIdMap, + const expectedTokenNumberMapAcct = + await propellerProgram.account.tokenNumberMap.fetch( + expectedTokenNumberMap, ); console.info(` calculatedTokenIdMap: ${calculatedTokenIdMap.toBase58()} calculatedTokenIdMapBump: ${calculatedTokenIdMapBump} - expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : - ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} + expectedTokenIdMapAcct: ${expectedTokenNumberMap.toBase58()} : + ${JSON.stringify(expectedTokenNumberMapAcct, null, 2)} `); - const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; - expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); + const derivedTokenNumberMap = + processSwimPayloadPubkeys.tokenNumberMap; + expect(derivedTokenNumberMap).toEqual(expectedTokenNumberMap); if (!processSwimPayloadPubkeys.swimClaim) { throw new Error("swimClaim key not derived"); } @@ -1866,13 +1837,29 @@ describe("propeller", () => { const poolToken0Mint = usdcKeypair.publicKey; const poolToken1Mint = usdtKeypair.publicKey; const lpMint = swimUsdMint; + const swimPayloadMessageDataTransferAmountBefore = ( + await propellerEnginePropellerProgram.account.swimPayloadMessage.fetch( + swimPayloadMessage, + ) + ).transferAmount; + const userAtas = await Promise.all([ + getAssociatedTokenAddress(usdcKeypair.publicKey, owner), + getAssociatedTokenAddress(usdtKeypair.publicKey, owner), + getAssociatedTokenAddress(swimUsdKeypair.publicKey, owner), + ]); const [userTokenAccount0, userTokenAccount1, userLpTokenAccount] = - await Promise.all([ - getAssociatedTokenAddress(usdcKeypair.publicKey, owner), - getAssociatedTokenAddress(usdtKeypair.publicKey, owner), - getAssociatedTokenAddress(swimUsdKeypair.publicKey, owner), - ]); + userAtas; + const userAtaInitCount = ( + await Promise.all( + userAtas.map(async (ata) => { + return await splToken.account.token.fetchNullable(ata); + }), + ) + ).reduce((acc, ata) => { + return acc + (ata === null ? 1 : 0); + }, 0); + expect(userAtaInitCount).toEqual(3); const propellerFeeVaultBalanceBefore = ( await splToken.account.token.fetch(propellerFeeVault) ).amount; @@ -1920,6 +1907,37 @@ describe("propeller", () => { .preInstructions([setComputeUnitLimitIx]) .rpc(); + const userAtaAccountLength = ( + await connection.getAccountInfo(userAtas[0]) + ).data.length; + const userAtaRentExemption = + await connection.getMinimumBalanceForRentExemption( + userAtaAccountLength, + ); + const totalRentExemptionInLamports = new BN(userAtaInitCount).mul( + new BN(userAtaRentExemption), + ); + const expectedFeesInLamports = new BN(userAtaInitCount) + .mul(initAtaFee) + .add(totalRentExemptionInLamports); + + const feeSwimUsdBn = await convertLamportsToSwimUsdAtomic( + expectedFeesInLamports, + ); + + console.info(` + userAtaInitCount: ${userAtaInitCount} + * userAtaRentExemption: ${userAtaRentExemption} + = totalRentExemptionInLamports: ${totalRentExemptionInLamports.toString()} + + (initAtaFee: ${initAtaFee.toString()} + * userAtaInitCount: ${userAtaInitCount}) + + totalRentExemptionInLamports: ${totalRentExemptionInLamports.toString()} + = expectedFeesInLamports: ${expectedFeesInLamports.toString()} + + feeSwimUsdBn = ${feeSwimUsdBn.toString()} + `); + const propellerFeeVaultBalanceAfter = ( await splToken.account.token.fetch(propellerFeeVault) ).amount; @@ -1937,6 +1955,23 @@ describe("propeller", () => { propellerEngineFeeTrackerFeesOwedBefore, ), ).toBeTruthy(); + + const fees = propellerEngineFeeTrackerFeesOwedAfter.sub( + propellerEngineFeeTrackerFeesOwedBefore, + ); + expect(fees.eq(feeSwimUsdBn)).toBeTruthy(); + + const swimPayloadMessageDataTransferAmountAfter = ( + await propellerEnginePropellerProgram.account.swimPayloadMessage.fetch( + swimPayloadMessage, + ) + ).transferAmount; + + expect( + swimPayloadMessageDataTransferAmountAfter.eq( + swimPayloadMessageDataTransferAmountBefore.sub(feeSwimUsdBn), + ), + ).toBeTruthy(); const userTokenAccount0Data = await splToken.account.token.fetch( userTokenAccount0, ); @@ -1987,7 +2022,7 @@ describe("propeller", () => { await propellerProgram.account.swimPayloadMessage.fetch( swimPayloadMessage, ); - const swimPayloadMessageAccountTargetTokenId = + const swimPayloadMessageAccountToTokenNumber = swimPayloadMessageAccount.targetTokenId; const propellerRedeemerEscrowBalanceBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) @@ -2002,33 +2037,19 @@ describe("propeller", () => { await splToken.account.token.fetch(userTokenAccount1) ).amount; const [calculatedSwimClaim, calculatedSwimClaimBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("claim"), - wormholeClaim.toBuffer(), - ], - propellerProgram.programId, - ); + await getSwimClaimPda(wormholeClaim, propellerProgram.programId); - const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(swimPayloadMessageAccountTargetTokenId).toArrayLike( - Buffer, - "le", - 2, - ), - ], + const [calculatedTokenNumberMap, calculatedTokenNumberMapBump] = + await getToTokenNumberMapAddr( + propeller, + swimPayloadMessageAccountToTokenNumber, propellerProgram.programId, ); + const processSwimPayloadPubkeys = await propellerEnginePropellerProgram.methods .processSwimPayload( - swimPayloadMessageAccountTargetTokenId, + swimPayloadMessageAccountToTokenNumber, new BN(0), ) .accounts({ @@ -2041,7 +2062,7 @@ describe("propeller", () => { swimPayloadMessageAccount.swimPayloadMessagePayer, redeemer: propellerRedeemer, redeemerEscrow: propellerRedeemerEscrowAccount, - // tokenIdMap: calculatedTokenIdMap, + // tokenIdMap: calculatedTokenNumberMap, pool, poolTokenAccount0, poolTokenAccount1, @@ -2062,7 +2083,7 @@ describe("propeller", () => { const propellerProcessSwimPayloadIxs = propellerEnginePropellerProgram.methods .propellerProcessSwimPayload( - swimPayloadMessageAccountTargetTokenId, + swimPayloadMessageAccountToTokenNumber, ) .accounts({ // Note: anchor can't autoderive nested accounts. @@ -2075,7 +2096,7 @@ describe("propeller", () => { // swimPayloadMessage, // redeemer: propellerRedeemer, // redeemerEscrow: propellerRedeemerEscrowAccount, - // tokenIdMap: calculatedTokenIdMap, + // tokenIdMap: calculatedTokenNumberMap, // pool, // poolTokenAccount0, // poolTokenAccount1, @@ -2114,26 +2135,26 @@ describe("propeller", () => { console.info( `${JSON.stringify(propellerProcessSwimPayloadPubkeys, null, 2)}`, ); - // if (!propellerProcessSwimPayloadPubkeys.tokenIdMap) { + // if (!propellerProcessSwimPayloadPubkeys.tokenNumberMap) { // throw new Error("tokenIdMap not derived"); // } - const expectedTokenIdMap = - outputTokenIdMappingAddrs.get(targetTokenId); - if (!expectedTokenIdMap) { - throw new Error("expectedTokenIdMap not found"); + const expectedTokenNumberMap = + toTokenNumberMapAddrs.get(targetTokenId); + if (!expectedTokenNumberMap) { + throw new Error("expectedTokenNumberMap not found"); } - const expectedTokenIdMapAcct = - await propellerProgram.account.tokenIdMap.fetch( - expectedTokenIdMap, + const expectedTokenNumberMapAcct = + await propellerProgram.account.tokenNumberMap.fetch( + expectedTokenNumberMap, ); console.info(` - calculatedTokenIdMap: ${calculatedTokenIdMap.toBase58()} - calculatedTokenIdMapBump: ${calculatedTokenIdMapBump} - expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : - ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} + calculatedTokenIdMap: ${calculatedTokenNumberMap.toBase58()} + calculatedTokenIdMapBump: ${calculatedTokenNumberMapBump} + expectedTokenIdMapAcct: ${expectedTokenNumberMap.toBase58()} : + ${JSON.stringify(expectedTokenNumberMapAcct, null, 2)} `); - expect(calculatedTokenIdMap).toEqual(expectedTokenIdMap); + expect(calculatedTokenNumberMap).toEqual(expectedTokenNumberMap); const processSwimPayloadTxnSig: string = await propellerProcessSwimPayloadIxs.rpc(); @@ -2650,7 +2671,7 @@ describe("propeller", () => { ) ).feesOwed; - [invalidTokenIdMapAddr] = await getTargetTokenIdMapAddr( + [invalidTokenIdMapAddr] = await getToTokenNumberMapAddr( propeller, targetTokenId, propellerEnginePropellerProgram.programId, @@ -2753,7 +2774,7 @@ describe("propeller", () => { swimPayloadMessageAccount.swimPayloadMessagePayer, redeemer: propellerRedeemer, redeemerEscrow: propellerRedeemerEscrowAccount, - tokenIdMap: invalidTokenIdMapAddr, + tokenNumberMap: invalidTokenIdMapAddr, userSwimUsdAta: ownerSwimUsdAta, tokenProgram: splToken.programId, memo: MEMO_PROGRAM_ID, @@ -3228,7 +3249,7 @@ describe("propeller", () => { // // const processSwimPayloadPubkeys = await processSwimPayload.pubkeys(); // console.info(`${JSON.stringify(processSwimPayloadPubkeys, null, 2)}`); - // if (!processSwimPayloadPubkeys.tokenIdMap) { + // if (!processSwimPayloadPubkeys.tokenNumberMap) { // throw new Error("tokenIdMap not derived"); // } // @@ -3248,19 +3269,19 @@ describe("propeller", () => { // ); // // const expectedTokenIdMap = - // outputTokenIdMappingAddrs.get(targetTokenId); + // toTokenNumberMapAddrs.get(targetTokenId); // if (!expectedTokenIdMap) { // throw new Error("expectedTokenIdMap not found"); // } // const expectedTokenIdMapAcct = - // await propellerProgram.account.tokenIdMap.fetch(expectedTokenIdMap); + // await propellerProgram.account.tokenNumberMap.fetch(expectedTokenIdMap); // console.info(` // calculatedTokenIdMap: ${calculatedTokenIdMap.toBase58()} // calculatedTokenIdMapBump: ${calculatedTokenIdMapBump} // expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : // ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} // `); - // const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; + // const derivedTokenIdMap = processSwimPayloadPubkeys.tokenNumberMap; // expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); // if (!processSwimPayloadPubkeys.swimClaim) { // throw new Error("swimClaim key not derived"); @@ -3341,7 +3362,7 @@ describe("propeller", () => { // let wormholeClaim: web3.PublicKey; // let wormholeMessage: web3.PublicKey; // let swimPayloadMessage: web3.PublicKey; - // const targetTokenId = metapoolMint1OutputTokenIndex; + // const targetTokenId = metapoolMint1ToTokenNumber; // const memoBuffer = createMemoId(); // // const memo = "e45794d6c5a2750b"; // @@ -3642,7 +3663,7 @@ describe("propeller", () => { // // const processSwimPayloadPubkeys = await processSwimPayload.pubkeys(); // console.info(`${JSON.stringify(processSwimPayloadPubkeys, null, 2)}`); - // if (!processSwimPayloadPubkeys.tokenIdMap) { + // if (!processSwimPayloadPubkeys.tokenNumberMap) { // throw new Error("tokenIdMap not derived"); // } // const [calculatedTokenIdMap, calculatedTokenIdMapBump] = @@ -3661,19 +3682,19 @@ describe("propeller", () => { // ); // // const expectedTokenIdMap = - // outputTokenIdMappingAddrs.get(targetTokenId); + // toTokenNumberMapAddrs.get(targetTokenId); // if (!expectedTokenIdMap) { // throw new Error("expectedTokenIdMap not found"); // } // const expectedTokenIdMapAcct = - // await propellerProgram.account.tokenIdMap.fetch(expectedTokenIdMap); + // await propellerProgram.account.tokenNumberMap.fetch(expectedTokenIdMap); // console.info(` // calculatedTokenIdMap: ${calculatedTokenIdMap.toBase58()} // calculatedTokenIdMapBump: ${calculatedTokenIdMapBump} // expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : // ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} // `); - // const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; + // const derivedTokenIdMap = processSwimPayloadPubkeys.tokenNumberMap; // expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); // if (!processSwimPayloadPubkeys.swimClaim) { // throw new Error("swimClaim key not derived"); @@ -4257,7 +4278,7 @@ describe("propeller", () => { await propellerProgram.account.swimPayloadMessage.fetch( swimPayloadMessage, ); - const swimPayloadMessageAccountTargetTokenId = + const swimPayloadMessageAccountToTokenNumber = swimPayloadMessageAccount.targetTokenId; const propellerRedeemerEscrowBalanceBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) @@ -4271,7 +4292,7 @@ describe("propeller", () => { const processSwimPayloadPubkeys = await propellerEnginePropellerProgram.methods .processSwimPayload( - swimPayloadMessageAccountTargetTokenId, + swimPayloadMessageAccountToTokenNumber, new BN(0), ) .accounts({ @@ -4301,7 +4322,7 @@ describe("propeller", () => { const propellerProcessSwimPayloadIxs = propellerEnginePropellerProgram.methods .propellerProcessSwimPayload( - swimPayloadMessageAccountTargetTokenId, + swimPayloadMessageAccountToTokenNumber, ) .accounts({ processSwimPayload: processSwimPayloadPubkeys, @@ -4325,31 +4346,23 @@ describe("propeller", () => { console.info( `${JSON.stringify(propellerProcessSwimPayloadPubkeys, null, 2)}`, ); - if (!processSwimPayloadPubkeys.tokenIdMap) { + if (!processSwimPayloadPubkeys.tokenNumberMap) { throw new Error("tokenIdMap not derived"); } + const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(swimPayloadMessageAccountTargetTokenId).toArrayLike( - Buffer, - "le", - 2, - ), - ], + await getToTokenNumberMapAddr( + propeller, + swimPayloadMessageAccountToTokenNumber, propellerProgram.programId, ); - const expectedTokenIdMap = - outputTokenIdMappingAddrs.get(targetTokenId); + const expectedTokenIdMap = toTokenNumberMapAddrs.get(targetTokenId); if (!expectedTokenIdMap) { throw new Error("expectedTokenIdMap not found"); } const expectedTokenIdMapAcct = - await propellerProgram.account.tokenIdMap.fetch( + await propellerProgram.account.tokenNumberMap.fetch( expectedTokenIdMap, ); console.info(` @@ -4358,7 +4371,7 @@ describe("propeller", () => { expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} `); - const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; + const derivedTokenIdMap = processSwimPayloadPubkeys.tokenNumberMap; expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); if (!processSwimPayloadPubkeys.swimClaim) { throw new Error("swimClaim key not derived"); @@ -4886,7 +4899,7 @@ describe("propeller", () => { // getAssociatedTokenAddress(usdtKeypair.publicKey, owner), // getAssociatedTokenAddress(swimUsdKeypair.publicKey, owner), // ]); - const userPoolAtas = await getOwnerTokenAccountsForPool( + const userPoolAtas = await getOwnerAtaAddrsForPool( flagshipPool, owner, twoPoolProgram, @@ -4951,7 +4964,7 @@ describe("propeller", () => { `[Ricky] swimPayloadMessage: ${swimPayloadMessage.toString()}`, ); const userBalanceBefore = await connection.getBalance(owner); - const userPoolAtas = await getOwnerTokenAccountsForPool( + const userPoolAtas = await getOwnerAtaAddrsForPool( flagshipPool, owner, twoPoolProgram, @@ -5456,7 +5469,7 @@ describe("propeller", () => { ) ).feesOwed; - [invalidTokenIdMapAddr] = await getTargetTokenIdMapAddr( + [invalidTokenIdMapAddr] = await getToTokenNumberMapAddr( propeller, targetTokenId, propellerEnginePropellerProgram.programId, @@ -6139,7 +6152,7 @@ describe("propeller", () => { await propellerProgram.account.swimPayloadMessage.fetch( swimPayloadMessage, ); - const swimPayloadMessageAccountTargetTokenId = + const swimPayloadMessageAccountToTokenNumber = swimPayloadMessageAccount.targetTokenId; const propellerRedeemerEscrowBalanceBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) @@ -6153,7 +6166,7 @@ describe("propeller", () => { const processSwimPayloadPubkeys = await propellerEnginePropellerProgram.methods .processSwimPayload( - swimPayloadMessageAccountTargetTokenId, + swimPayloadMessageAccountToTokenNumber, new BN(0), ) .accounts({ @@ -6183,7 +6196,7 @@ describe("propeller", () => { const propellerProcessSwimPayloadIxs = propellerEnginePropellerProgram.methods .propellerProcessSwimPayload( - swimPayloadMessageAccountTargetTokenId, + swimPayloadMessageAccountToTokenNumber, ) .accounts({ processSwimPayload: processSwimPayloadPubkeys, @@ -6207,31 +6220,22 @@ describe("propeller", () => { console.info( `${JSON.stringify(propellerProcessSwimPayloadPubkeys, null, 2)}`, ); - if (!processSwimPayloadPubkeys.tokenIdMap) { + if (!processSwimPayloadPubkeys.tokenNumberMap) { throw new Error("tokenIdMap not derived"); } const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(swimPayloadMessageAccountTargetTokenId).toArrayLike( - Buffer, - "le", - 2, - ), - ], + await getToTokenNumberMapAddr( + propeller, + swimPayloadMessageAccountToTokenNumber, propellerProgram.programId, ); - const expectedTokenIdMap = - outputTokenIdMappingAddrs.get(targetTokenId); + const expectedTokenIdMap = toTokenNumberMapAddrs.get(targetTokenId); if (!expectedTokenIdMap) { throw new Error("expectedTokenIdMap not found"); } const expectedTokenIdMapAcct = - await propellerProgram.account.tokenIdMap.fetch( + await propellerProgram.account.tokenNumberMap.fetch( expectedTokenIdMap, ); console.info(` @@ -6240,7 +6244,7 @@ describe("propeller", () => { expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} `); - const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; + const derivedTokenIdMap = processSwimPayloadPubkeys.tokenNumberMap; expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); if (!processSwimPayloadPubkeys.swimClaim) { throw new Error("swimClaim key not derived"); @@ -6500,7 +6504,7 @@ describe("propeller", () => { // ], // propellerProgramId, // ); -// const tokenIdMapData = await propellerProgram.account.tokenIdMap.fetch( +// const tokenIdMapData = await propellerProgram.account.tokenNumberMap.fetch( // tokenIdMap, // ); // const poolKey = tokenIdMapData.pool; @@ -6889,21 +6893,18 @@ const initializePropeller = async () => { completeWithPayloadFee, initAtaFee, processSwimPayloadFee, - // propellerMinTransferAmount, - // propellerEthMinTransferAmount, marginalPricePool, marginalPricePoolTokenIndex, marginalPricePoolTokenMint, maxStaleness, - // evmRoutingContractAddress: ethRoutingContract, - // evmRoutingContractAddress: ethRoutingContractEthUint8Arr }; const tx = propellerProgram.methods .initialize(initializeParams) .accounts({ propellerRedeemerEscrow: propellerRedeemerEscrowAddr, propellerFeeVault, - admin: propellerAdmin.publicKey, + governanceKey: propellerGovernanceKey.publicKey, + pauseKey: propellerPauseKey.publicKey, swimUsdMint: swimUsdMint, payer: userKeypair.publicKey, tokenProgram: TOKEN_PROGRAM_ID, @@ -6917,7 +6918,7 @@ const initializePropeller = async () => { twoPoolProgram: twoPoolProgram.programId, aggregator, }) - .signers([propellerAdmin]); + .signers([propellerGovernanceKey, propellerPauseKey]); const pubkeys = await tx.pubkeys(); console.info(`pubkeys: ${JSON.stringify(pubkeys, null, 2)}`); @@ -6974,102 +6975,105 @@ const initializePropeller = async () => { }; const createTokenIdMaps = async () => { - const swimUsdTokenIdMap = { + const swimUsdTokenNumberMap = { pool: flagshipPool, poolTokenIndex: 0, poolTokenMint: swimUsdKeypair.publicKey, - poolIx: { transfer: {} }, + toTokenStep: { transfer: {} }, }; - const usdcTokenIdMap = { + const usdcTokenNumberMap = { pool: flagshipPool, poolTokenIndex: usdcPoolTokenIndex, poolTokenMint: usdcKeypair.publicKey, - poolIx: { removeExactBurn: {} }, + toTokenStep: { removeExactBurn: {} }, }; - const usdtTokenIdMap = { + const usdtTokenNumberMap = { pool: flagshipPool, poolTokenIndex: usdtPoolTokenIndex, poolTokenMint: usdtKeypair.publicKey, - poolIx: { removeExactBurn: {} }, + toTokenStep: { removeExactBurn: {} }, }; - const metapoolMint1TokenIdMap = { + const metapoolMint1TokenNumberMap = { pool: metapool, poolTokenIndex: metapoolMint1PoolTokenIndex, poolTokenMint: metapoolMint1Keypair.publicKey, - poolIx: { swapExactInput: {} }, + toTokenStep: { swapExactInput: {} }, }; - const outputTokenIdMapAddrEntries = await Promise.all( + const toTokenNumberMapAddrEntries = await Promise.all( [ { - outputTokenIndex: SWIM_USD_TO_TOKEN_NUMBER, - tokenIdMap: swimUsdTokenIdMap, + toTokenNumber: SWIM_USD_TO_TOKEN_NUMBER, + tokenNumberMap: swimUsdTokenNumberMap, + }, + { + toTokenNumber: USDC_TO_TOKEN_NUMBER, + tokenNumberMap: usdcTokenNumberMap, + }, + { + toTokenNumber: USDT_TO_TOKEN_NUMBER, + tokenNumberMap: usdtTokenNumberMap, }, - { outputTokenIndex: USDC_TO_TOKEN_NUMBER, tokenIdMap: usdcTokenIdMap }, - { outputTokenIndex: USDT_TO_TOKEN_NUMBER, tokenIdMap: usdtTokenIdMap }, { - outputTokenIndex: metapoolMint1OutputTokenIndex, - tokenIdMap: metapoolMint1TokenIdMap, + toTokenNumber: metapoolMint1ToTokenNumber, + tokenNumberMap: metapoolMint1TokenNumberMap, }, - ].map(async ({ outputTokenIndex, tokenIdMap }) => { - const createTokenIdMappingTxn = propellerProgram.methods - .createTokenIdMap( - outputTokenIndex, - tokenIdMap.pool, - tokenIdMap.poolTokenIndex, - tokenIdMap.poolTokenMint, - tokenIdMap.poolIx, + ].map(async ({ toTokenNumber, tokenNumberMap }) => { + console.info(`creating tokenNumberMap for ${toTokenNumber}`); + const createTokenNumberMapTxn = propellerProgram.methods + .createTokenNumberMap( + toTokenNumber, + tokenNumberMap.pool, + tokenNumberMap.poolTokenIndex, + tokenNumberMap.poolTokenMint, + tokenNumberMap.toTokenStep, ) .accounts({ propeller, - admin: propellerAdmin.publicKey, - payer: userKeypair.publicKey, + governanceKey: propellerGovernanceKey.publicKey, + payer: payer.publicKey, systemProgram: web3.SystemProgram.programId, // rent: web3.SYSVAR_RENT_PUBKEY, - pool: tokenIdMap.pool, + pool: tokenNumberMap.pool, twoPoolProgram: twoPoolProgram.programId, }) - .signers([propellerAdmin]); + .signers([propellerGovernanceKey]); - const pubkeys = await createTokenIdMappingTxn.pubkeys(); - if (!pubkeys.tokenIdMap) { + const pubkeys = await createTokenNumberMapTxn.pubkeys(); + if (!pubkeys.tokenNumberMap) { throw new Error("Token Id Map PDA Address was not auto-derived"); } - const tokenIdMapAddr = pubkeys.tokenIdMap; - expect(tokenIdMapAddr).toBeTruthy(); + const tokenNumberMapAddr = pubkeys.tokenNumberMap; + expect(tokenNumberMapAddr).toBeTruthy(); const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(outputTokenIndex).toArrayLike(Buffer, "le", 2), - // byteify.serializeUint16(usdcOutputTokenIndex), - ], + await getToTokenNumberMapAddr( + propeller, + toTokenNumber, propellerProgram.programId, ); - expect(tokenIdMapAddr).toEqual(calculatedTokenIdMap); - await createTokenIdMappingTxn.rpc(); + expect(tokenNumberMapAddr).toEqual(calculatedTokenIdMap); + await createTokenNumberMapTxn.rpc(); - const fetchedTokenIdMap = await propellerProgram.account.tokenIdMap.fetch( - tokenIdMapAddr, - ); - expect(fetchedTokenIdMap.outputTokenIndex).toEqual(outputTokenIndex); - expect(fetchedTokenIdMap.pool).toEqual(tokenIdMap.pool); + const fetchedTokenIdMap = + await propellerProgram.account.tokenNumberMap.fetch(tokenNumberMapAddr); + expect(fetchedTokenIdMap.toTokenNumber).toEqual(toTokenNumber); + expect(fetchedTokenIdMap.pool).toEqual(tokenNumberMap.pool); expect(fetchedTokenIdMap.poolTokenIndex).toEqual( - tokenIdMap.poolTokenIndex, + tokenNumberMap.poolTokenIndex, ); - expect(fetchedTokenIdMap.poolTokenMint).toEqual(tokenIdMap.poolTokenMint); - expect(fetchedTokenIdMap.poolIx).toEqual(tokenIdMap.poolIx); + expect(fetchedTokenIdMap.poolTokenMint).toEqual( + tokenNumberMap.poolTokenMint, + ); + expect(fetchedTokenIdMap.toTokenStep).toEqual(tokenNumberMap.toTokenStep); expect(fetchedTokenIdMap.bump).toEqual(calculatedTokenIdMapBump); - return { outputTokenIndex, tokenIdMapAddr }; + return { toTokenNumber, tokenNumberMapAddr }; }), ); - outputTokenIdMappingAddrs = new Map( - outputTokenIdMapAddrEntries.map(({ outputTokenIndex, tokenIdMapAddr }) => { - return [outputTokenIndex, tokenIdMapAddr]; + toTokenNumberMapAddrs = new Map( + toTokenNumberMapAddrEntries.map(({ toTokenNumber, tokenNumberMapAddr }) => { + return [toTokenNumber, tokenNumberMapAddr]; }), ); }; @@ -7081,12 +7085,12 @@ const createTargetChainMaps = async () => { .createTargetChainMap(targetChainId, address) .accounts({ propeller, - admin: propellerAdmin.publicKey, + governanceKey: propellerGovernanceKey.publicKey, payer: propellerEngineKeypair.publicKey, systemProgram: web3.SystemProgram.programId, // rent: web3.SYSVAR_RENT_PUBKEY, }) - .signers([propellerAdmin]); + .signers([propellerGovernanceKey]); const createTargetChainMapPubkeys = await createTargetChainMap.pubkeys(); await createTargetChainMap.rpc(); @@ -7118,8 +7122,14 @@ const seedWormholeCustody = async () => { const transferAmount = userLpTokenBalanceBefore.div(new BN(2)); const wormholeMessage = web3.Keypair.generate(); + const nonce = createNonce().readUInt32LE(0); const crossChainTransferNativeTxnSig = await propellerProgram.methods - .crossChainTransferNativeWithPayload(transferAmount, CHAIN_ID_ETH, evmOwner) + .crossChainTransferNativeWithPayload( + nonce, + transferAmount, + CHAIN_ID_ETH, + evmOwner, + ) .accounts({ propeller, payer: payer.publicKey, @@ -7186,6 +7196,37 @@ const convertLamportsToSwimUsdAtomic = async (feeInLamports: BN) => { return new BN(feeSwimUsdAtomic.toNumber()); }; +const getExpectedFeesForCreateUserAtasInLamports = async ( + swimUsdMintKey: web3.PublicKey, + swimPayloadMessage: web3.PublicKey, +) => { + const swimPayloadMessageAcctData = + await propellerProgram.account.swimPayloadMessage.fetch(swimPayloadMessage); + const swimPayloadOwner = swimPayloadMessageAcctData.owner; + const outputTokenIndex = swimPayloadMessageAcctData.toTokenNumber; + const propellerAddr = await getPropellerPda( + swimUsdMintKey, + propellerProgram.programId, + ); + const [tokenIdMapAddr] = await getToTokenNumberMapAddr( + propellerAddr, + outputTokenIndex, + propellerProgram.programId, + ); + const tokenNumberMapAcctData = + await propellerProgram.account.tokenNumberMap.fetch(tokenIdMapAddr); + const tokenNumberMapPool = tokenNumberMapAcctData.pool; + const poolAcctData = await twoPoolProgram.account.twoPool.fetch( + tokenNumberMapPool, + ); + const poolMints = [...poolAcctData.tokenMintKeys, poolAcctData.lpMintKey]; + const userAtas = await Promise.all( + poolMints.map(async (mint) => { + return await getAssociatedTokenAddress(mint, swimPayloadOwner); + }), + ); +}; + function createMemoId() { const SWIM_MEMO_LENGTH = 16; // NOTE: Please always use random bytes to avoid conflicts with other users diff --git a/packages/solana-contracts/src/__tests__/propeller/propeller.test.ts b/packages/solana-contracts/src/__tests__/propeller/propeller.test.ts index 58eebee77..6a2c799d0 100644 --- a/packages/solana-contracts/src/__tests__/propeller/propeller.test.ts +++ b/packages/solana-contracts/src/__tests__/propeller/propeller.test.ts @@ -1,3 +1,5 @@ +import * as crypto from "crypto"; + import { CHAIN_ID_ETH, CHAIN_ID_SOLANA, @@ -26,6 +28,7 @@ import { TOKEN_PROGRAM_ID, getAccount, getAssociatedTokenAddress, + getOrCreateAssociatedTokenAccount, } from "@solana/spl-token"; import { PublicKey } from "@solana/web3.js"; @@ -46,6 +49,7 @@ import { ethRoutingContract, ethRoutingContractEthHexStr, ethTokenBridge, + ethTokenBridgeNativeStr, evmOwner, evmTargetTokenId, gasKickstartAmount, @@ -54,8 +58,8 @@ import { lpFee, marginalPricePoolTokenIndex, maxStaleness, - metapoolMint1OutputTokenIndex, metapoolMint1PoolTokenIndex, + metapoolMint1ToTokenNumber, postVaaFee, processSwimPayloadFee, routingContracts, @@ -68,23 +72,31 @@ import { usdtPoolTokenIndex, } from "../consts"; import { + getPoolTokenAccountBalances, getPoolUserBalances, + getUserAtaBalancesForPool, printBeforeAndAfterPoolUserBalances, printPoolUserBalances, setupPoolPrereqs, setupUserAssociatedTokenAccts, } from "../twoPool/poolTestUtils"; +import type { WormholeAddresses } from "./propellerUtils"; import { + DummyForeignRoutingProgram, encodeSwimPayload, formatParsedTokenTransferWithSwimPayloadPostedMessage, + getCompleteNativeWithPayloadTxn, + getProcessSwimPayloadTxn, getPropellerPda, getPropellerRedeemerPda, getPropellerSenderPda, getSwimClaimPda, getSwimPayloadMessagePda, + getToTokenNumberMapAddr, getWormholeAddressesForMint, - parseTokenTransferWithSwimPayloadPostedMessage, WormholeAddresses, + parseTokenTransferWithSwimPayloadPostedMessage, + postVaaToSolana, } from "./propellerUtils"; import { deriveEndpointPda, @@ -98,23 +110,8 @@ import { signAndEncodeVaa, } from "./wormholeUtils"; -// this just breaks everything for some reason... -// think it was something related to the cjs/esm stuff for spl-memo -// -// const MEMO_PROGRAM_ID: PublicKey = new PublicKey( -// "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", -// ); + setDefaultWasm("node"); -// const pr2 = new AnchorProvider( -// connection, -// provider.wallet, -// { -// commitment: "confirmed", -// } -// ) -// const process = require("process"); -// const url = process.env.ANCHOR_PROVIDER_URL; -// const provider = AnchorProvider.local(url, {commitment: "confirmed"}); const envProvider = AnchorProvider.env(); const provider = new AnchorProvider( @@ -143,7 +140,7 @@ const wormhole = WORMHOLE_CORE_BRIDGE; const tokenBridge = WORMHOLE_TOKEN_BRIDGE; // start these higher to avoid issues when running this suite & engine.test.ts suite in one go -let ethTokenBridgeSequence = 1000; +const ethTokenBridgeSequence = 1000; // let ethTokenBridgeSequence = BigInt(0); let metapool: web3.PublicKey; @@ -157,7 +154,8 @@ let propeller: web3.PublicKey; let propellerSender: web3.PublicKey; let propellerRedeemer: web3.PublicKey; let propellerRedeemerEscrowAccount: web3.PublicKey; -const propellerAdmin: web3.Keypair = web3.Keypair.generate(); +const propellerGovernanceKey: web3.Keypair = web3.Keypair.generate(); +const propellerPauseKey: web3.Keypair = web3.Keypair.generate(); let propellerFeeVault: web3.PublicKey; const dummyUser = payer; @@ -181,8 +179,8 @@ const poolMintKeypairs = [usdcKeypair, usdtKeypair]; const poolMintDecimals = [mintDecimal, mintDecimal]; const poolMintAuthorities = [payer, payer]; const swimUsdKeypair = web3.Keypair.generate(); -const governanceKeypair = web3.Keypair.generate(); -const pauseKeypair = web3.Keypair.generate(); +const poolGovernanceKeypair = web3.Keypair.generate(); +const poolPauseKeypair = web3.Keypair.generate(); let poolUsdcAtaAddr: web3.PublicKey; let poolUsdtAtaAddr: web3.PublicKey; @@ -248,6 +246,16 @@ let tokenBridgeConfig: web3.PublicKey; let custodySigner: web3.PublicKey; const aggregator: web3.PublicKey = DEFAULT_SOL_USD_FEED; +const dummyEthTokenBridge: DummyForeignRoutingProgram = + new DummyForeignRoutingProgram( + ethTokenBridgeNativeStr, + CHAIN_ID_ETH, + ethTokenBridgeSequence, + swimUsdMint.toBuffer(), + propellerProgram.programId, + ethRoutingContract, + ); + describe("propeller", () => { beforeAll(async () => { console.info(`initializing two pool v2`); @@ -262,7 +270,7 @@ describe("propeller", () => { poolMintDecimals, poolMintAuthorities.map((k) => k.publicKey), swimUsdKeypair.publicKey, - governanceKeypair.publicKey, + poolGovernanceKeypair.publicKey, )); const initFlagshipPoolTxn = twoPoolProgram.methods // .initialize(params) @@ -274,8 +282,8 @@ describe("propeller", () => { lpMint: swimUsdKeypair.publicKey, poolTokenAccount0: poolUsdcAtaAddr, poolTokenAccount1: poolUsdtAtaAddr, - pauseKey: pauseKeypair.publicKey, - governanceAccount: governanceKeypair.publicKey, + pauseKey: poolPauseKeypair.publicKey, + governanceAccount: poolGovernanceKeypair.publicKey, governanceFeeAccount: flagshipPoolGovernanceFeeAcct, tokenProgram: splToken.programId, associatedTokenProgram: splAssociatedToken.programId, @@ -373,7 +381,7 @@ describe("propeller", () => { // [metapoolMint1Decimal], // [metapoolMint1Authority.publicKey], metapoolLpMintKeypair.publicKey, - governanceKeypair.publicKey, + poolGovernanceKeypair.publicKey, )); const initMetapoolTxn = twoPoolProgram.methods @@ -386,8 +394,8 @@ describe("propeller", () => { lpMint: metapoolLpMintKeypair.publicKey, poolTokenAccount0: metapoolPoolToken0Ata, poolTokenAccount1: metapoolPoolToken1Ata, - pauseKey: pauseKeypair.publicKey, - governanceAccount: governanceKeypair.publicKey, + pauseKey: poolPauseKeypair.publicKey, + governanceAccount: poolGovernanceKeypair.publicKey, governanceFeeAccount: metapoolGovernanceFeeAta, tokenProgram: splToken.programId, associatedTokenProgram: splAssociatedToken.programId, @@ -475,7 +483,6 @@ describe("propeller", () => { } `); - wormholeAddresses = await getWormholeAddressesForMint( WORMHOLE_CORE_BRIDGE, WORMHOLE_TOKEN_BRIDGE, @@ -483,7 +490,7 @@ describe("propeller", () => { ethTokenBridge, bscTokenBridge, ); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + ({ authoritySigner, custody, @@ -550,7 +557,8 @@ describe("propeller", () => { .accounts({ propellerRedeemerEscrow: propellerRedeemerEscrowAddr, propellerFeeVault, - admin: propellerAdmin.publicKey, + governanceKey: propellerGovernanceKey.publicKey, + pauseKey: propellerPauseKey.publicKey, swimUsdMint: swimUsdMint, payer: payer.publicKey, tokenProgram: TOKEN_PROGRAM_ID, @@ -564,7 +572,7 @@ describe("propeller", () => { twoPoolProgram: twoPoolProgram.programId, aggregator, }) - .signers([propellerAdmin]); + .signers([propellerGovernanceKey, propellerPauseKey]); const pubkeys = await tx.pubkeys(); console.info(`pubkeys: ${JSON.stringify(pubkeys, null, 2)}`); @@ -625,7 +633,9 @@ describe("propeller", () => { propeller, ); console.info(`propellerData: ${JSON.stringify(propellerData)}`); - expect(propellerData.admin).toEqual(propellerAdmin.publicKey); + expect(propellerData.governanceKey).toEqual( + propellerGovernanceKey.publicKey, + ); expect(propellerData.swimUsdMint).toEqual(swimUsdMint); console.info( @@ -648,117 +658,126 @@ describe("propeller", () => { `); }); - it("Creates Token Id Mappings", async () => { - const swimUsdTokenIdMap = { + it("Creates Token Number Maps", async () => { + const swimUsdTokenNumberMap = { pool: flagshipPool, poolTokenIndex: 0, poolTokenMint: swimUsdKeypair.publicKey, - poolIx: { transfer: {} }, + toTokenStep: { transfer: {} }, }; - const usdcTokenIdMap = { + const usdcTokenNumberMap = { pool: flagshipPool, poolTokenIndex: usdcPoolTokenIndex, poolTokenMint: usdcKeypair.publicKey, - poolIx: { removeExactBurn: {} }, + toTokenStep: { removeExactBurn: {} }, }; - const usdtTokenIdMap = { + const usdtTokenNumberMap = { pool: flagshipPool, poolTokenIndex: usdtPoolTokenIndex, poolTokenMint: usdtKeypair.publicKey, - poolIx: { removeExactBurn: {} }, + toTokenStep: { removeExactBurn: {} }, }; - const metapoolMint1TokenIdMap = { + const metapoolMint1TokenNumberMap = { pool: metapool, poolTokenIndex: metapoolMint1PoolTokenIndex, poolTokenMint: metapoolMint1Keypair.publicKey, - poolIx: { swapExactInput: {} }, + toTokenStep: { swapExactInput: {} }, }; - const outputTokenIdMapAddrEntries = await Promise.all( + const toTokenNumberMapAddrEntries = await Promise.all( [ { - outputTokenIndex: SWIM_USD_TO_TOKEN_NUMBER, - tokenIdMap: swimUsdTokenIdMap, + toTokenNumber: SWIM_USD_TO_TOKEN_NUMBER, + tokenNumberMap: swimUsdTokenNumberMap, + }, + { + toTokenNumber: USDC_TO_TOKEN_NUMBER, + tokenNumberMap: usdcTokenNumberMap, }, - { outputTokenIndex: USDC_TO_TOKEN_NUMBER, tokenIdMap: usdcTokenIdMap }, - { outputTokenIndex: USDT_TO_TOKEN_NUMBER, tokenIdMap: usdtTokenIdMap }, { - outputTokenIndex: metapoolMint1OutputTokenIndex, - tokenIdMap: metapoolMint1TokenIdMap, + toTokenNumber: USDT_TO_TOKEN_NUMBER, + tokenNumberMap: usdtTokenNumberMap, }, - ].map(async ({ outputTokenIndex, tokenIdMap }) => { - const createTokenIdMappingTxn = propellerProgram.methods - .createTokenIdMap( - outputTokenIndex, - tokenIdMap.pool, - tokenIdMap.poolTokenIndex, - tokenIdMap.poolTokenMint, - tokenIdMap.poolIx, + { + toTokenNumber: metapoolMint1ToTokenNumber, + tokenNumberMap: metapoolMint1TokenNumberMap, + }, + ].map(async ({ toTokenNumber, tokenNumberMap }) => { + console.info(`creating tokenNumberMap for ${toTokenNumber}`); + const createTokenNumberMapTxn = propellerProgram.methods + .createTokenNumberMap( + toTokenNumber, + tokenNumberMap.pool, + tokenNumberMap.poolTokenIndex, + tokenNumberMap.poolTokenMint, + tokenNumberMap.toTokenStep, ) .accounts({ propeller, - admin: propellerAdmin.publicKey, + governanceKey: propellerGovernanceKey.publicKey, payer: payer.publicKey, systemProgram: web3.SystemProgram.programId, // rent: web3.SYSVAR_RENT_PUBKEY, - pool: tokenIdMap.pool, + pool: tokenNumberMap.pool, twoPoolProgram: twoPoolProgram.programId, }) - .signers([propellerAdmin]); + .signers([propellerGovernanceKey]); - const pubkeys = await createTokenIdMappingTxn.pubkeys(); - if (!pubkeys.tokenIdMap) { + const pubkeys = await createTokenNumberMapTxn.pubkeys(); + if (!pubkeys.tokenNumberMap) { throw new Error("Token Id Map PDA Address was not auto-derived"); } - const tokenIdMapAddr = pubkeys.tokenIdMap; - expect(tokenIdMapAddr).toBeTruthy(); + const tokenNumberMapAddr = pubkeys.tokenNumberMap; + expect(tokenNumberMapAddr).toBeTruthy(); const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(outputTokenIndex).toArrayLike(Buffer, "le", 2), - // byteify.serializeUint16(usdcOutputTokenIndex), - ], + await getToTokenNumberMapAddr( + propeller, + toTokenNumber, propellerProgram.programId, ); - expect(tokenIdMapAddr).toEqual(calculatedTokenIdMap); - await createTokenIdMappingTxn.rpc(); + + expect(tokenNumberMapAddr).toEqual(calculatedTokenIdMap); + await createTokenNumberMapTxn.rpc(); const fetchedTokenIdMap = - await propellerProgram.account.tokenIdMap.fetch(tokenIdMapAddr); - expect(fetchedTokenIdMap.outputTokenIndex).toEqual(outputTokenIndex); - expect(fetchedTokenIdMap.pool).toEqual(tokenIdMap.pool); + await propellerProgram.account.tokenNumberMap.fetch( + tokenNumberMapAddr, + ); + expect(fetchedTokenIdMap.toTokenNumber).toEqual(toTokenNumber); + expect(fetchedTokenIdMap.pool).toEqual(tokenNumberMap.pool); expect(fetchedTokenIdMap.poolTokenIndex).toEqual( - tokenIdMap.poolTokenIndex, + tokenNumberMap.poolTokenIndex, ); expect(fetchedTokenIdMap.poolTokenMint).toEqual( - tokenIdMap.poolTokenMint, + tokenNumberMap.poolTokenMint, + ); + expect(fetchedTokenIdMap.toTokenStep).toEqual( + tokenNumberMap.toTokenStep, ); - expect(fetchedTokenIdMap.poolIx).toEqual(tokenIdMap.poolIx); expect(fetchedTokenIdMap.bump).toEqual(calculatedTokenIdMapBump); - return { outputTokenIndex, tokenIdMapAddr }; + return { toTokenNumber, tokenNumberMapAddr }; }), ); outputTokenIdMappingAddrs = new Map( - outputTokenIdMapAddrEntries.map( - ({ outputTokenIndex, tokenIdMapAddr }) => { - return [outputTokenIndex, tokenIdMapAddr]; + toTokenNumberMapAddrEntries.map( + ({ toTokenNumber, tokenNumberMapAddr }) => { + return [toTokenNumber, tokenNumberMapAddr]; }, ), ); for (const [ - outputTokenIndex, - tokenIdMapAddr, + toTokenNumber, + tokenNumberMapAddr, ] of outputTokenIdMappingAddrs.entries()) { console.info(` - outputTokenIndex: ${outputTokenIndex} - tokenIdMapAddr: ${tokenIdMapAddr.toBase58()} - tokenIdMap: ${JSON.stringify( - await propellerProgram.account.tokenIdMap.fetch(tokenIdMapAddr), + toTokenNumber: ${toTokenNumber} + tokenNumberMapAddr: ${tokenNumberMapAddr.toBase58()} + tokenNumberMap: ${JSON.stringify( + await propellerProgram.account.tokenNumberMap.fetch( + tokenNumberMapAddr, + ), )} `); } @@ -771,12 +790,12 @@ describe("propeller", () => { .createTargetChainMap(targetChainId, address) .accounts({ propeller, - admin: propellerAdmin.publicKey, + governanceKey: propellerGovernanceKey.publicKey, payer: payer.publicKey, systemProgram: web3.SystemProgram.programId, // rent: web3.SYSVAR_RENT_PUBKEY, }) - .signers([propellerAdmin]); + .signers([propellerGovernanceKey]); const createTargetChainMapPubkeys = await createTargetChainMap.pubkeys(); @@ -788,7 +807,6 @@ describe("propeller", () => { return { targetChainId, targetAddress: address, - targetChainMapData, }; }), @@ -1515,11 +1533,10 @@ describe("propeller", () => { const memo = createMemoId(); const wormholeMessage = web3.Keypair.generate(); - const nonceBefore = ( - await propellerProgram.account.propeller.fetch(propeller) - ).nonce; + const nonce = createNonce().readUInt32LE(0); const transferNativeTxn = await propellerProgram.methods .crossChainTransferNativeWithPayload( + nonce, transferAmount, CHAIN_ID_ETH, evmOwner, @@ -1558,11 +1575,6 @@ describe("propeller", () => { rpcCommitmentConfig, ); - const nonceAfter = ( - await propellerProgram.account.propeller.fetch(propeller) - ).nonce; - expect(nonceAfter).toEqual(nonceBefore + 1); - const transferNativeTxnSize = transferNativeTxn.serialize().length; console.info(`transferNativeTxnSize txnSize: ${transferNativeTxnSize}`); await connection.confirmTransaction({ @@ -1630,32 +1642,12 @@ describe("propeller", () => { 2, )}`, ); - expect(tokenTransferMessage.core.nonce).toEqual(nonceBefore); expect(swimPayload.owner).toEqual(evmOwner); expect(swimPayload.propellerEnabled).toBeUndefined(); expect(swimPayload.gasKickstart).toBeUndefined(); expect(swimPayload.maxFee).toBeUndefined(); expect(swimPayload.targetTokenId).toBeUndefined(); expect(swimPayload.memo).toBeUndefined(); - // this fails due to capitalization - // expect(formattedTokenTransferMessage.tokenTransfer.to).to.equal(ethRoutingContractStr); - // console.info(` - // tokenTransferMessage: ${JSON.stringify(tokenTransferMessage, null ,2)} - // `) - // console.info(`postedMessage:\n${JSON.stringify(postedMessage)}`); - // console.info(`postedMessagePayload:\n${JSON.stringify(postedMessagePayload)}`); - // const { - // payload: swimPayload - // } = postedMessagePayload; - // console.info(` - // evmOwnerEthHexStr: ${evmOwnerEthHexStr} - // evmOwner(Buffer.toString('hex')): ${evmOwner.toString('hex')} - // evmTargetTokenAddrEthHexStr: ${evmTargetTokenAddrEthHexStr} - // evmTargetTokenAddr(Buffer.toString('hex')): ${evmTargetTokenAddr.toString('hex')} - // `); - // console.info(`swimPayload:\n${JSON.stringify(swimPayload)}`); - // const transferAmountBuffer = transferAmount.toBuffer('be'); - // console.info(`transferAmountBufferHexStr: ${transferAmountBuffer.toString('hex')}`); }); it("calls propellerTransferNativeWithPayload with memo", async () => { @@ -1679,18 +1671,18 @@ describe("propeller", () => { const wormholeMessage = web3.Keypair.generate(); const gasKickstart = false; const maxFee = new BN(100_000); - const nonceBefore = ( - await propellerProgram.account.propeller.fetch(propeller) - ).nonce; + const nonce = createNonce().readUInt32LE(0); + const propellerTransferNativeTxn = await propellerProgram.methods .propellerTransferNativeWithPayload( + nonce, transferAmount, CHAIN_ID_ETH, evmOwner, gasKickstart, maxFee, evmTargetTokenId, - Buffer.from(memo, "hex"), + memo, ) .accounts({ propeller, @@ -1726,11 +1718,6 @@ describe("propeller", () => { rpcCommitmentConfig, ); - const nonceAfter = ( - await propellerProgram.account.propeller.fetch(propeller) - ).nonce; - expect(nonceAfter).toEqual(nonceBefore + 1); - const transferNativeTxnSize = propellerTransferNativeTxn.serialize().length; console.info(`transferNativeTxnSize txnSize: ${transferNativeTxnSize}`); @@ -1800,36 +1787,13 @@ describe("propeller", () => { 2, )}`, ); - expect(tokenTransferMessage.core.nonce).toEqual(nonceBefore); expect(swimPayload.owner).toEqual(evmOwner); expect(swimPayload.propellerEnabled).toEqual(true); expect(swimPayload.gasKickstart).toEqual(gasKickstart); expect(swimPayload.maxFee).toEqual(maxFee); expect(swimPayload.targetTokenId).toEqual(evmTargetTokenId); expect(swimPayload.memo.toString("hex")).toEqual(memo.toString("hex")); - await checkTxnLogsForMemo( - propellerTransferNativeTxnSig, - memo.toString("hex"), - ); - // this fails due to capitalization - // expect(formattedTokenTransferMessage.tokenTransfer.to).to.equal(ethRoutingContractStr); - // console.info(` - // tokenTransferMessage: ${JSON.stringify(tokenTransferMessage, null ,2)} - // `) - // console.info(`postedMessage:\n${JSON.stringify(postedMessage)}`); - // console.info(`postedMessagePayload:\n${JSON.stringify(postedMessagePayload)}`); - // const { - // payload: swimPayload - // } = postedMessagePayload; - // console.info(` - // evmOwnerEthHexStr: ${evmOwnerEthHexStr} - // evmOwner(Buffer.toString('hex')): ${evmOwner.toString('hex')} - // evmTargetTokenAddrEthHexStr: ${evmTargetTokenAddrEthHexStr} - // evmTargetTokenAddr(Buffer.toString('hex')): ${evmTargetTokenAddr.toString('hex')} - // `); - // console.info(`swimPayload:\n${JSON.stringify(swimPayload)}`); - // const transferAmountBuffer = transferAmount.toBuffer('be'); - // console.info(`transferAmountBufferHexStr: ${transferAmountBuffer.toString('hex')}`); + await checkTxnLogsForMemo(propellerTransferNativeTxnSig, memo); }); it("calls propellerTransferNativeWithPayload without memo", async () => { @@ -1852,11 +1816,11 @@ describe("propeller", () => { const wormholeMessage = web3.Keypair.generate(); const gasKickstart = false; const maxFee = new BN(100_000); - const nonceBefore = ( - await propellerProgram.account.propeller.fetch(propeller) - ).nonce; + const nonce = createNonce().readUInt32LE(0); + const propellerTransferNativeTxn = await propellerProgram.methods .propellerTransferNativeWithPayload( + nonce, transferAmount, CHAIN_ID_ETH, evmOwner, @@ -1898,10 +1862,6 @@ describe("propeller", () => { rpcCommitmentConfig, ); - const nonceAfter = ( - await propellerProgram.account.propeller.fetch(propeller) - ).nonce; - expect(nonceAfter).toEqual(nonceBefore + 1); const transferNativeTxnSize = propellerTransferNativeTxn.serialize().length; console.info(`transferNativeTxnSize txnSize: ${transferNativeTxnSize}`); @@ -1971,37 +1931,13 @@ describe("propeller", () => { 2, )}`, ); - expect(tokenTransferMessage.core.nonce).toEqual(nonceBefore); expect(swimPayload.owner).toEqual(evmOwner); expect(swimPayload.propellerEnabled).toEqual(true); expect(swimPayload.gasKickstart).toEqual(gasKickstart); expect(swimPayload.maxFee).toEqual(maxFee); expect(swimPayload.targetTokenId).toEqual(evmTargetTokenId); expect(swimPayload.memo).toBeUndefined(); - await checkTxnLogsForMemo( - transferNativeTxnSig, - memo.toString("hex"), - false, - ); - // this fails due to capitalization - // expect(formattedTokenTransferMessage.tokenTransfer.to).to.equal(ethRoutingContractStr); - // console.info(` - // tokenTransferMessage: ${JSON.stringify(tokenTransferMessage, null ,2)} - // `) - // console.info(`postedMessage:\n${JSON.stringify(postedMessage)}`); - // console.info(`postedMessagePayload:\n${JSON.stringify(postedMessagePayload)}`); - // const { - // payload: swimPayload - // } = postedMessagePayload; - // console.info(` - // evmOwnerEthHexStr: ${evmOwnerEthHexStr} - // evmOwner(Buffer.toString('hex')): ${evmOwner.toString('hex')} - // evmTargetTokenAddrEthHexStr: ${evmTargetTokenAddrEthHexStr} - // evmTargetTokenAddr(Buffer.toString('hex')): ${evmTargetTokenAddr.toString('hex')} - // `); - // console.info(`swimPayload:\n${JSON.stringify(swimPayload)}`); - // const transferAmountBuffer = transferAmount.toBuffer('be'); - // console.info(`transferAmountBufferHexStr: ${transferAmountBuffer.toString('hex')}`); + await checkTxnLogsForMemo(transferNativeTxnSig, memo, false); }); it("Fails propellerTransferNativeWithPayload if transferAmount <= maxFee for targetChain", async () => { @@ -2010,9 +1946,11 @@ describe("propeller", () => { const wormholeMessage = web3.Keypair.generate(); const gasKickstart = false; const maxFee = transferAmount.add(new BN(1)); + const nonce = createNonce().readUInt32LE(0); await expect(() => { return propellerProgram.methods .propellerTransferNativeWithPayload( + nonce, transferAmount, CHAIN_ID_ETH, evmOwner, @@ -2068,46 +2006,15 @@ describe("propeller", () => { const swimPayload = { version: swimPayloadVersion, owner: provider.publicKey.toBuffer(), - // owner: owner.toBuffer(), - // propellerEnabled, - // gasKickstart, - // maxFee, - // targetTokenId, - // memo: memoBuffer, }; - // - // const swimPayload = { - // version: swimPayloadVersion, - // targetTokenId, - // owner: provider.publicKey.toBuffer(), - // memo: memoBuffer, - // propellerEnabled, - // gasKickstart, - // }; + const amount = parseUnits("1", mintDecimal); console.info(`amount: ${amount.toString()}`); - /** - * this is encoding a token transfer from eth routing contract - * with a swimUSD token address that originated on solana - * the same as initializing a `TransferWrappedWithPayload` from eth - */ - - const nonce = createNonce().readUInt32LE(0); - const tokenTransferWithPayloadSignedVaa = signAndEncodeVaa( - 0, - nonce, - CHAIN_ID_ETH as number, - ethTokenBridge, - BigInt(++ethTokenBridgeSequence), - encodeTokenTransferWithPayload( + const tokenTransferWithPayloadSignedVaa = + dummyEthTokenBridge.createSignedTokenTransferWithSwimPayloadVAA( amount.toString(), - swimUsdKeypair.publicKey.toBuffer(), - CHAIN_ID_SOLANA, - propellerProgram.programId, - ethRoutingContract, - encodeSwimPayload(swimPayload), - ), - ); + swimPayload, + ); const propellerRedeemerEscrowAccountBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) ).amount; @@ -2159,7 +2066,6 @@ describe("propeller", () => { tokenTransferWithPayloadSignedVaa, ); - const completeNativeWithPayloadIxs = propellerProgram.methods .completeNativeWithPayload() .accounts({ @@ -2236,17 +2142,16 @@ describe("propeller", () => { expect(swimPayloadMessageAccount.bump).toEqual( expectedSwimPayloadMessageBump, ); - // expect(swimPayloadMessageAccount.whMessage).toEqual(wormholeMessage); expect(swimPayloadMessageAccount.claim).toEqual(wormholeClaim); expect( Buffer.from(swimPayloadMessageAccount.vaaEmitterAddress), - ).toEqual(ethTokenBridge); + ).toEqual(dummyEthTokenBridge.emitterAddress); expect(swimPayloadMessageAccount.vaaEmitterChain).toEqual( - CHAIN_ID_ETH, + dummyEthTokenBridge.emitterChain, ); expect( swimPayloadMessageAccount.vaaSequence.eq( - new BN(ethTokenBridgeSequence), + new BN(dummyEthTokenBridge.sequence), ), ).toBeTruthy(); expect( @@ -2254,14 +2159,7 @@ describe("propeller", () => { new BN(amount.toString()), ), ).toBeTruthy(); - // const { - // swimPayloadVersion, - // targetTokenId, - // owner, - // memo, - // propellerEnabled, - // gasKickstart - // } = swimPayloadMessageAccount.swimPayload; + expect(swimPayloadMessageAccount.swimPayloadVersion).toEqual( swimPayloadVersion, ); @@ -2367,21 +2265,13 @@ describe("propeller", () => { const processSwimPayloadPubkeys = await processSwimPayloadIxs.pubkeys(); console.info(`${JSON.stringify(processSwimPayloadPubkeys, null, 2)}`); - if (!processSwimPayloadPubkeys.tokenIdMap) { + if (!processSwimPayloadPubkeys.tokenNumberMap) { throw new Error("tokenIdMap not derived"); } const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(swimPayloadMessageAccountTargetTokenId).toArrayLike( - Buffer, - "le", - 2, - ), - ], + await getToTokenNumberMapAddr( + propeller, + swimPayloadMessageAccountTargetTokenId, propellerProgram.programId, ); @@ -2391,14 +2281,16 @@ describe("propeller", () => { throw new Error("expectedTokenIdMap not found"); } const expectedTokenIdMapAcct = - await propellerProgram.account.tokenIdMap.fetch(expectedTokenIdMap); + await propellerProgram.account.tokenNumberMap.fetch( + expectedTokenIdMap, + ); console.info(` calculatedTokenIdMap: ${calculatedTokenIdMap.toBase58()} calculatedTokenIdMapBump: ${calculatedTokenIdMapBump} expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} `); - const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; + const derivedTokenIdMap = processSwimPayloadPubkeys.tokenNumberMap; expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); if (!processSwimPayloadPubkeys.swimClaim) { throw new Error("swimClaim key not derived"); @@ -2477,28 +2369,12 @@ describe("propeller", () => { const amount = parseUnits("1", mintDecimal); console.info(`amount: ${amount.toString()}`); - /** - * this is encoding a token transfer from eth routing contract - * with a swimUSD token address that originated on solana - * the same as initializing a `TransferWrappedWithPayload` from eth - */ - const nonce = createNonce().readUInt32LE(0); - const tokenTransferWithPayloadSignedVaa = signAndEncodeVaa( - 0, - nonce, - CHAIN_ID_ETH as number, - ethTokenBridge, - BigInt(++ethTokenBridgeSequence), - encodeTokenTransferWithPayload( + const tokenTransferWithPayloadSignedVaa = + dummyEthTokenBridge.createSignedTokenTransferWithSwimPayloadVAA( amount.toString(), - swimUsdKeypair.publicKey.toBuffer(), - CHAIN_ID_SOLANA, - propellerProgram.programId, - ethRoutingContract, - encodeSwimPayload(swimPayload), - ), - ); + swimPayload, + ); const propellerRedeemerEscrowAccountBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) ).amount; @@ -2614,13 +2490,13 @@ describe("propeller", () => { expect(swimPayloadMessageAccount.claim).toEqual(wormholeClaim); expect( Buffer.from(swimPayloadMessageAccount.vaaEmitterAddress), - ).toEqual(ethTokenBridge); + ).toEqual(dummyEthTokenBridge.emitterAddress); expect(swimPayloadMessageAccount.vaaEmitterChain).toEqual( - CHAIN_ID_ETH, + dummyEthTokenBridge.emitterChain, ); expect( swimPayloadMessageAccount.vaaSequence.eq( - new BN(ethTokenBridgeSequence), + new BN(dummyEthTokenBridge.sequence), ), ).toBeTruthy(); expect( @@ -2696,7 +2572,7 @@ describe("propeller", () => { await propellerProgram.account.swimPayloadMessage.fetch( swimPayloadMessage, ); - const swimPayloadMessageAccountTargetTokenId = + const swimPayloadMessageAccountToTokenNumber = swimPayloadMessageAccount.targetTokenId; const propellerRedeemerEscrowBalanceBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) @@ -2739,39 +2615,32 @@ describe("propeller", () => { const processSwimPayloadPubkeys = await processSwimPayload.pubkeys(); console.info(`${JSON.stringify(processSwimPayloadPubkeys, null, 2)}`); - if (!processSwimPayloadPubkeys.tokenIdMap) { + if (!processSwimPayloadPubkeys.tokenNumberMap) { throw new Error("tokenIdMap not derived"); } const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(swimPayloadMessageAccountTargetTokenId).toArrayLike( - Buffer, - "le", - 2, - ), - ], + await getToTokenNumberMapAddr( + propeller, + swimPayloadMessageAccountToTokenNumber, propellerProgram.programId, ); - const expectedTokenIdMap = outputTokenIdMappingAddrs.get(targetTokenId); if (!expectedTokenIdMap) { throw new Error("expectedTokenIdMap not found"); } const expectedTokenIdMapAcct = - await propellerProgram.account.tokenIdMap.fetch(expectedTokenIdMap); + await propellerProgram.account.tokenNumberMap.fetch( + expectedTokenIdMap, + ); console.info(` calculatedTokenIdMap: ${calculatedTokenIdMap.toBase58()} calculatedTokenIdMapBump: ${calculatedTokenIdMapBump} expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} `); - const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; + const derivedTokenIdMap = processSwimPayloadPubkeys.tokenNumberMap; expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); if (!processSwimPayloadPubkeys.swimClaim) { throw new Error("swimClaim key not derived"); @@ -2844,7 +2713,7 @@ describe("propeller", () => { let wormholeClaim: web3.PublicKey; let wormholeMessage: web3.PublicKey; let swimPayloadMessage: web3.PublicKey; - const targetTokenId = metapoolMint1OutputTokenIndex; + const targetTokenId = metapoolMint1ToTokenNumber; const memoStr = createMemoId(); // const memo = "e45794d6c5a2750b"; @@ -2856,28 +2725,12 @@ describe("propeller", () => { const amount = parseUnits("1", mintDecimal); console.info(`amount: ${amount.toString()}`); - /** - * this is encoding a token transfer from eth routing contract - * with a swimUSD token address that originated on solana - * the same as initializing a `TransferWrappedWithPayload` from eth - */ - const nonce = createNonce().readUInt32LE(0); - const tokenTransferWithPayloadSignedVaa = signAndEncodeVaa( - 0, - nonce, - CHAIN_ID_ETH as number, - ethTokenBridge, - BigInt(++ethTokenBridgeSequence), - encodeTokenTransferWithPayload( + const tokenTransferWithPayloadSignedVaa = + dummyEthTokenBridge.createSignedTokenTransferWithSwimPayloadVAA( amount.toString(), - swimUsdKeypair.publicKey.toBuffer(), - CHAIN_ID_SOLANA, - propellerProgram.programId, - ethRoutingContract, - encodeSwimPayload(swimPayload), - ), - ); + swimPayload, + ); const propellerRedeemerEscrowAccountBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) ).amount; @@ -2992,13 +2845,13 @@ describe("propeller", () => { expect(swimPayloadMessageAccount.claim).toEqual(wormholeClaim); expect( Buffer.from(swimPayloadMessageAccount.vaaEmitterAddress), - ).toEqual(ethTokenBridge); + ).toEqual(dummyEthTokenBridge.emitterAddress); expect(swimPayloadMessageAccount.vaaEmitterChain).toEqual( - CHAIN_ID_ETH, + dummyEthTokenBridge.emitterChain, ); expect( swimPayloadMessageAccount.vaaSequence.eq( - new BN(ethTokenBridgeSequence), + new BN(dummyEthTokenBridge.sequence), ), ).toBeTruthy(); expect( @@ -3067,7 +2920,7 @@ describe("propeller", () => { await propellerProgram.account.swimPayloadMessage.fetch( swimPayloadMessage, ); - const swimPayloadMessageAccountTargetTokenId = + const swimPayloadMessageAccountToTokenNumber = swimPayloadMessageAccount.targetTokenId; const propellerRedeemerEscrowBalanceBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) @@ -3110,21 +2963,13 @@ describe("propeller", () => { const processSwimPayloadPubkeys = await processSwimPayload.pubkeys(); console.info(`${JSON.stringify(processSwimPayloadPubkeys, null, 2)}`); - if (!processSwimPayloadPubkeys.tokenIdMap) { + if (!processSwimPayloadPubkeys.tokenNumberMap) { throw new Error("tokenIdMap not derived"); } const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(swimPayloadMessageAccountTargetTokenId).toArrayLike( - Buffer, - "le", - 2, - ), - ], + await getToTokenNumberMapAddr( + propeller, + swimPayloadMessageAccountToTokenNumber, propellerProgram.programId, ); @@ -3134,14 +2979,16 @@ describe("propeller", () => { throw new Error("expectedTokenIdMap not found"); } const expectedTokenIdMapAcct = - await propellerProgram.account.tokenIdMap.fetch(expectedTokenIdMap); + await propellerProgram.account.tokenNumberMap.fetch( + expectedTokenIdMap, + ); console.info(` calculatedTokenIdMap: ${calculatedTokenIdMap.toBase58()} calculatedTokenIdMapBump: ${calculatedTokenIdMapBump} expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} `); - const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; + const derivedTokenIdMap = processSwimPayloadPubkeys.tokenNumberMap; expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); if (!processSwimPayloadPubkeys.swimClaim) { throw new Error("swimClaim key not derived"); @@ -3251,21 +3098,26 @@ describe("propeller", () => { */ const nonce = createNonce().readUInt32LE(0); - const tokenTransferWithPayloadSignedVaa = signAndEncodeVaa( - 0, - nonce, - CHAIN_ID_ETH as number, - ethTokenBridge, - BigInt(++ethTokenBridgeSequence), - encodeTokenTransferWithPayload( + // const tokenTransferWithPayloadSignedVaa = signAndEncodeVaa( + // 0, + // nonce, + // CHAIN_ID_ETH as number, + // ethTokenBridge, + // BigInt(++ethTokenBridgeSequence), + // encodeTokenTransferWithPayload( + // amount.toString(), + // swimUsdKeypair.publicKey.toBuffer(), + // CHAIN_ID_SOLANA, + // propellerProgram.programId, + // ethRoutingContract, + // encodeSwimPayload(swimPayload), + // ), + // ); + const tokenTransferWithPayloadSignedVaa = + dummyEthTokenBridge.createSignedTokenTransferWithSwimPayloadVAA( amount.toString(), - swimUsdKeypair.publicKey.toBuffer(), - CHAIN_ID_SOLANA, - propellerProgram.programId, - ethRoutingContract, - encodeSwimPayload(swimPayload), - ), - ); + swimPayload, + ); const propellerRedeemerEscrowAccountBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) ).amount; @@ -3382,13 +3234,13 @@ describe("propeller", () => { expect(swimPayloadMessageAccount.claim).toEqual(wormholeClaim); expect( Buffer.from(swimPayloadMessageAccount.vaaEmitterAddress), - ).toEqual(ethTokenBridge); + ).toEqual(dummyEthTokenBridge.emitterAddress); expect(swimPayloadMessageAccount.vaaEmitterChain).toEqual( - CHAIN_ID_ETH, + dummyEthTokenBridge.emitterChain, ); expect( swimPayloadMessageAccount.vaaSequence.eq( - new BN(ethTokenBridgeSequence), + new BN(dummyEthTokenBridge.sequence), ), ).toBeTruthy(); expect( @@ -3464,7 +3316,7 @@ describe("propeller", () => { await propellerProgram.account.swimPayloadMessage.fetch( swimPayloadMessage, ); - const swimPayloadMessageAccountTargetTokenId = + const swimPayloadMessageAccountToTokenNumber = swimPayloadMessageAccount.targetTokenId; const propellerRedeemerEscrowBalanceBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) @@ -3507,21 +3359,14 @@ describe("propeller", () => { const processSwimPayloadPubkeys = await processSwimPayloadIxs.pubkeys(); console.info(`${JSON.stringify(processSwimPayloadPubkeys, null, 2)}`); - if (!processSwimPayloadPubkeys.tokenIdMap) { + if (!processSwimPayloadPubkeys.tokenNumberMap) { throw new Error("tokenIdMap not derived"); } + const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(swimPayloadMessageAccountTargetTokenId).toArrayLike( - Buffer, - "le", - 2, - ), - ], + await getToTokenNumberMapAddr( + propeller, + swimPayloadMessageAccountToTokenNumber, propellerProgram.programId, ); @@ -3531,14 +3376,16 @@ describe("propeller", () => { throw new Error("expectedTokenIdMap not found"); } const expectedTokenIdMapAcct = - await propellerProgram.account.tokenIdMap.fetch(expectedTokenIdMap); + await propellerProgram.account.tokenNumberMap.fetch( + expectedTokenIdMap, + ); console.info(` calculatedTokenIdMap: ${calculatedTokenIdMap.toBase58()} calculatedTokenIdMapBump: ${calculatedTokenIdMapBump} expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} `); - const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; + const derivedTokenIdMap = processSwimPayloadPubkeys.tokenNumberMap; expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); if (!processSwimPayloadPubkeys.swimClaim) { throw new Error("swimClaim key not derived"); @@ -3639,21 +3486,26 @@ describe("propeller", () => { */ const nonce = createNonce().readUInt32LE(0); - const tokenTransferWithPayloadSignedVaa = signAndEncodeVaa( - 0, - nonce, - CHAIN_ID_ETH as number, - ethTokenBridge, - BigInt(++ethTokenBridgeSequence), - encodeTokenTransferWithPayload( + // const tokenTransferWithPayloadSignedVaa = signAndEncodeVaa( + // 0, + // nonce, + // CHAIN_ID_ETH as number, + // ethTokenBridge, + // BigInt(++ethTokenBridgeSequence), + // encodeTokenTransferWithPayload( + // amount.toString(), + // swimUsdKeypair.publicKey.toBuffer(), + // CHAIN_ID_SOLANA, + // propellerProgram.programId, + // ethRoutingContract, + // encodeSwimPayload(swimPayload), + // ), + // ); + const tokenTransferWithPayloadSignedVaa = + dummyEthTokenBridge.createSignedTokenTransferWithSwimPayloadVAA( amount.toString(), - swimUsdKeypair.publicKey.toBuffer(), - CHAIN_ID_SOLANA, - propellerProgram.programId, - ethRoutingContract, - encodeSwimPayload(swimPayload), - ), - ); + swimPayload, + ); const propellerRedeemerEscrowAccountBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) ).amount; @@ -3788,13 +3640,13 @@ describe("propeller", () => { expect(swimPayloadMessageAccount.claim).toEqual(wormholeClaim); expect( Buffer.from(swimPayloadMessageAccount.vaaEmitterAddress), - ).toEqual(ethTokenBridge); + ).toEqual(dummyEthTokenBridge.emitterAddress); expect(swimPayloadMessageAccount.vaaEmitterChain).toEqual( - CHAIN_ID_ETH, + dummyEthTokenBridge.emitterChain, ); expect( swimPayloadMessageAccount.vaaSequence.eq( - new BN(ethTokenBridgeSequence), + new BN(dummyEthTokenBridge.sequence), ), ).toBeTruthy(); expect( @@ -3870,7 +3722,7 @@ describe("propeller", () => { await propellerProgram.account.swimPayloadMessage.fetch( swimPayloadMessage, ); - const swimPayloadMessageAccountTargetTokenId = + const swimPayloadMessageAccountToTokenNumber = swimPayloadMessageAccount.targetTokenId; const propellerRedeemerEscrowBalanceBefore = ( await splToken.account.token.fetch(propellerRedeemerEscrowAccount) @@ -3913,22 +3765,14 @@ describe("propeller", () => { const processSwimPayloadPubkeys = await processSwimPayload.pubkeys(); console.info(`${JSON.stringify(processSwimPayloadPubkeys, null, 2)}`); - if (!processSwimPayloadPubkeys.tokenIdMap) { + if (!processSwimPayloadPubkeys.tokenNumberMap) { throw new Error("tokenIdMap not derived"); } const [calculatedTokenIdMap, calculatedTokenIdMapBump] = - await web3.PublicKey.findProgramAddress( - [ - Buffer.from("propeller"), - Buffer.from("token_id"), - propeller.toBuffer(), - new BN(swimPayloadMessageAccountTargetTokenId).toArrayLike( - Buffer, - "le", - 2, - ), - ], + await getToTokenNumberMapAddr( + propeller, + swimPayloadMessageAccountToTokenNumber, propellerProgram.programId, ); @@ -3938,14 +3782,16 @@ describe("propeller", () => { throw new Error("expectedTokenIdMap not found"); } const expectedTokenIdMapAcct = - await propellerProgram.account.tokenIdMap.fetch(expectedTokenIdMap); + await propellerProgram.account.tokenNumberMap.fetch( + expectedTokenIdMap, + ); console.info(` calculatedTokenIdMap: ${calculatedTokenIdMap.toBase58()} calculatedTokenIdMapBump: ${calculatedTokenIdMapBump} expectedTokenIdMapAcct: ${expectedTokenIdMap.toBase58()} : ${JSON.stringify(expectedTokenIdMapAcct, null, 2)} `); - const derivedTokenIdMap = processSwimPayloadPubkeys.tokenIdMap; + const derivedTokenIdMap = processSwimPayloadPubkeys.tokenNumberMap; expect(derivedTokenIdMap).toEqual(expectedTokenIdMap); if (!processSwimPayloadPubkeys.swimClaim) { throw new Error("swimClaim key not derived"); @@ -4016,9 +3862,537 @@ describe("propeller", () => { }); }); + describe("Updating/Closing Token Number Maps", () => { + let newMetapool: web3.PublicKey; + let newMetapoolToken0Ata: web3.PublicKey; + let newMetapoolToken1Ata: web3.PublicKey; + const newMetapoolLpMintKeypair = web3.Keypair.generate(); + let newMetapoolGovernanceFeeAta: web3.PublicKey; + let userNewMetapoolLpTokenAta: web3.PublicKey; + + // intiialize new metapool with same token_mint_keys as previous metapool but new lp token + beforeAll(async () => { + ({ + poolPubkey: newMetapool, + poolTokenAccounts: [newMetapoolToken0Ata, newMetapoolToken1Ata], + governanceFeeAccount: newMetapoolGovernanceFeeAta, + } = await setupPoolPrereqs( + twoPoolProgram, + splToken, + metapoolMintKeypairs, + metapoolMintDecimals, + [flagshipPool, metapoolMint1Authority.publicKey], + newMetapoolLpMintKeypair.publicKey, + poolGovernanceKeypair.publicKey, + )); + + await twoPoolProgram.methods + .initialize(ampFactor, lpFee, governanceFee) + .accounts({ + payer: provider.publicKey, + poolMint0: metapoolMintKeypairs[0].publicKey, + poolMint1: metapoolMintKeypairs[1].publicKey, + lpMint: newMetapoolLpMintKeypair.publicKey, + poolTokenAccount0: newMetapoolToken0Ata, + poolTokenAccount1: newMetapoolToken1Ata, + pauseKey: poolPauseKeypair.publicKey, + governanceAccount: poolGovernanceKeypair.publicKey, + governanceFeeAccount: newMetapoolGovernanceFeeAta, + tokenProgram: splToken.programId, + associatedTokenProgram: splAssociatedToken.programId, + systemProgram: web3.SystemProgram.programId, + rent: web3.SYSVAR_RENT_PUBKEY, + }) + .signers([newMetapoolLpMintKeypair]) + .rpc(); + const newMetapoolData = await twoPoolProgram.account.twoPool.fetch( + newMetapool, + ); + console.info(` + newMetapool: ${newMetapool.toBase58()} + newMetapoolData: ${JSON.stringify(newMetapoolData, null, 2)} + `); + console.info("seeding new metapool"); + userNewMetapoolLpTokenAta = ( + await getOrCreateAssociatedTokenAccount( + connection, + payer, + newMetapoolLpMintKeypair.publicKey, + dummyUser.publicKey, + ) + ).address; + const uiInputAmounts: readonly BN[] = [new BN(2_000), new BN(1_500)]; + const inputAmounts: readonly BN[] = uiInputAmounts.map( + (bn: BN, i: number) => { + const powerOfTen = new BN(10).pow(new BN(metapoolMintDecimals[i])); + return bn.mul(powerOfTen); + }, + ); + const minimumMintAmount = new BN(0); + // const addParams = { + // inputAmounts, + // minimumMintAmount, + // }; + const userTransferAuthority = web3.Keypair.generate(); + const [approveIxs, revokeIxs] = await getApproveAndRevokeIxs( + splToken, + [userMetapoolTokenAccount0, userMetapoolTokenAccount1], + inputAmounts, + userTransferAuthority.publicKey, + payer, + ); + const seedNewMetapoolTxn = await twoPoolProgram.methods + .add(inputAmounts, minimumMintAmount) + .accounts({ + // propeller: propeller, + poolTokenAccount0: newMetapoolToken0Ata, + poolTokenAccount1: newMetapoolToken1Ata, + lpMint: newMetapoolLpMintKeypair.publicKey, + governanceFee: newMetapoolGovernanceFeeAta, + userTransferAuthority: userTransferAuthority.publicKey, + userTokenAccount0: userMetapoolTokenAccount0, + userTokenAccount1: userMetapoolTokenAccount1, + userLpTokenAccount: userNewMetapoolLpTokenAta, + tokenProgram: splToken.programId, + }) + .preInstructions([...approveIxs]) + .postInstructions([...revokeIxs]) + .signers([userTransferAuthority]) + .rpc(); + + console.info(`seedNewMetapoolTxn: ${seedNewMetapoolTxn}`); + const newMetapoolTokenAccountAmounts = ( + await Promise.all( + newMetapoolData.tokenKeys.map(async (key) => { + return await splToken.account.token.fetch(key); + }), + ) + ).map((account) => account.amount.toString()); + + console.info(` + newMetapoolTokenAccountAmounts: ${newMetapoolTokenAccountAmounts.toString()} + `); + // seed new metapool + // await twoPoolProgram.methods.add().accounts({}).rpc(); + }); + + it("Can update a token number map", async () => { + const [tokenNumberMapAddr] = await getToTokenNumberMapAddr( + propeller, + metapoolMint1ToTokenNumber, + propellerProgram.programId, + ); + const tokenNumberMapDataBefore = + await propellerProgram.account.tokenNumberMap.fetch(tokenNumberMapAddr); + expect(tokenNumberMapDataBefore.toTokenNumber).toEqual( + metapoolMint1ToTokenNumber, + ); + expect(tokenNumberMapDataBefore.poolTokenMint).toEqual( + metapoolMintKeypairs[1].publicKey, + ); + expect(tokenNumberMapDataBefore.pool).toEqual(metapool); + expect(tokenNumberMapDataBefore.poolTokenIndex).toEqual(1); + const poolTokenIndex = 1; + const poolTokenMint = metapoolMint1Keypair.publicKey; + const toTokenStep = { swapExactInput: {} }; + await propellerProgram.methods + .updateTokenNumberMap( + metapoolMint1ToTokenNumber, + poolTokenIndex, + poolTokenMint, + toTokenStep, + ) + .accounts({ + propeller, + governanceKey: propellerGovernanceKey.publicKey, + payer: payer.publicKey, + // rent: web3.SYSVAR_RENT_PUBKEY, + pool: newMetapool, + twoPoolProgram: twoPoolProgram.programId, + }) + .signers([propellerGovernanceKey]) + .rpc(); + // create new metapool with same metapoolMint1 + + const tokenNumberMapDataAfter = + await propellerProgram.account.tokenNumberMap.fetch(tokenNumberMapAddr); + expect(tokenNumberMapDataAfter.toTokenNumber).toEqual( + metapoolMint1ToTokenNumber, + ); + expect(tokenNumberMapDataAfter.poolTokenMint).toEqual( + metapoolMintKeypairs[1].publicKey, + ); + expect(tokenNumberMapDataAfter.pool).toEqual(newMetapool); + expect(tokenNumberMapDataAfter.poolTokenIndex).toEqual(1); + + const oldMetapoolTokenAccountBalancesBefore = + await getPoolTokenAccountBalances(metapool, twoPoolProgram, splToken); + + const newMetapoolTokenAccountBalancesBefore = + await getPoolTokenAccountBalances( + newMetapool, + twoPoolProgram, + splToken, + ); + + // + + const targetTokenId = metapoolMint1ToTokenNumber; + const memoStr = createMemoId(); + + const swimPayload = { + version: swimPayloadVersion, + owner: provider.publicKey.toBuffer(), + }; + + const amount = parseUnits("1", mintDecimal); + console.info(`amount: ${amount.toString()}`); + /** + * this is encoding a token transfer from eth routing contract + * with a swimUSD token address that originated on solana + * the same as initializing a `TransferWrappedWithPayload` from eth + */ + + const nonce = createNonce().readUInt32LE(0); + // const tokenTransferWithPayloadSignedVaa = signAndEncodeVaa( + // 0, + // nonce, + // CHAIN_ID_ETH as number, + // ethTokenBridge, + // BigInt(++ethTokenBridgeSequence), + // encodeTokenTransferWithPayload( + // amount.toString(), + // swimUsdKeypair.publicKey.toBuffer(), + // CHAIN_ID_SOLANA, + // propellerProgram.programId, + // ethRoutingContract, + // encodeSwimPayload(swimPayload), + // ), + // ); + const tokenTransferWithPayloadSignedVaa = + dummyEthTokenBridge.createSignedTokenTransferWithSwimPayloadVAA( + amount.toString(), + swimPayload, + ); + const propellerRedeemerEscrowAccountBefore = ( + await splToken.account.token.fetch(propellerRedeemerEscrowAccount) + ).amount; + + const newMetapoolUserAtaBalancesBefore = await getUserAtaBalancesForPool( + dummyUser.publicKey, + newMetapool, + twoPoolProgram, + splToken, + ); + + await postVaaSolanaWithRetry( + connection, + async (tx) => { + return provider.wallet.signTransaction(tx); + }, + WORMHOLE_CORE_BRIDGE.toBase58(), + payer.publicKey.toBase58(), + tokenTransferWithPayloadSignedVaa, + 10, + ); + + const [wormholeMessage] = await deriveMessagePda( + tokenTransferWithPayloadSignedVaa, + WORMHOLE_CORE_BRIDGE, + ); + + const [endpointAccount] = await deriveEndpointPda( + CHAIN_ID_ETH, + ethTokenBridge, + // parsedVaa.emitterChain, + // parsedVaa.emitterAddress, + WORMHOLE_TOKEN_BRIDGE, + ); + console.info(`endpointAccount: ${endpointAccount.toBase58()}`); + const wormholeClaim = await getClaimAddressSolana( + WORMHOLE_TOKEN_BRIDGE.toBase58(), + tokenTransferWithPayloadSignedVaa, + ); + + const completeNativeWithPayloadMetapoolIxs = propellerProgram.methods + .completeNativeWithPayload() + .accounts({ + propeller, + payer: payer.publicKey, + tokenBridgeConfig, + // userTokenBridgeAccount: userLpTokenAccount.address, + message: wormholeMessage, + claim: wormholeClaim, + endpoint: endpointAccount, + redeemerEscrow: propellerRedeemerEscrowAccount, + redeemer: propellerRedeemer, + feeVault: propellerFeeVault, + custody: custody, + swimUsdMint: swimUsdMint, + custodySigner, + rent: web3.SYSVAR_RENT_PUBKEY, + systemProgram: web3.SystemProgram.programId, + wormhole, + tokenProgram: splToken.programId, + tokenBridge, + }) + .preInstructions([setComputeUnitLimitIx]) + .postInstructions([createMemoInstruction(memoStr.toString("hex"))]); + + const completeNativeWithPayloadMetapoolPubkeys = + await completeNativeWithPayloadMetapoolIxs.pubkeys(); + + if (!completeNativeWithPayloadMetapoolPubkeys.swimPayloadMessage) { + throw new Error("swimPayloadMessage key not derived"); + } + + const swimPayloadMessage = + completeNativeWithPayloadMetapoolPubkeys.swimPayloadMessage; + + const completeNativeWithPayloadMetapoolTxn = + await completeNativeWithPayloadMetapoolIxs.transaction(); + const transferNativeTxnSig = await provider.sendAndConfirm( + completeNativeWithPayloadMetapoolTxn, + [payer], + { + skipPreflight: true, + }, + ); + + const swimPayloadMessageAccount = + await propellerProgram.account.swimPayloadMessage.fetch( + completeNativeWithPayloadMetapoolPubkeys.swimPayloadMessage, + ); + + await connection.confirmTransaction({ + signature: transferNativeTxnSig, + ...(await connection.getLatestBlockhash()), + }); + + const propellerRedeemerEscrowAccountAfter = ( + await splToken.account.token.fetch(propellerRedeemerEscrowAccount) + ).amount; + + expect( + propellerRedeemerEscrowAccountAfter.gt( + propellerRedeemerEscrowAccountBefore, + ), + ).toEqual(true); + await checkTxnLogsForMemo(transferNativeTxnSig, memoStr); + + const minOutputAmount = new BN(0); + await propellerProgram.methods + .processSwimPayload(targetTokenId, minOutputAmount) + .accounts({ + propeller, + payer: payer.publicKey, + claim: wormholeClaim, + swimPayloadMessage, + swimPayloadMessagePayer: + swimPayloadMessageAccount.swimPayloadMessagePayer, + redeemer: propellerRedeemer, + redeemerEscrow: propellerRedeemerEscrowAccount, + // tokenIdMap: ? + pool: newMetapool, + poolTokenAccount0: newMetapoolToken0Ata, + poolTokenAccount1: newMetapoolToken1Ata, + lpMint: newMetapoolLpMintKeypair.publicKey, + governanceFee: newMetapoolGovernanceFeeAta, + userTokenAccount0: userMetapoolTokenAccount0, + userTokenAccount1: userMetapoolTokenAccount1, + userLpTokenAccount: userNewMetapoolLpTokenAta, + tokenProgram: splToken.programId, + twoPoolProgram: twoPoolProgram.programId, + systemProgram: web3.SystemProgram.programId, + }) + .preInstructions([setComputeUnitLimitIx]) + .postInstructions([createMemoInstruction(memoStr.toString("hex"))]) + .rpc(); + + const oldMetapoolTokenAccountBalancesAfter = + await getPoolTokenAccountBalances(metapool, twoPoolProgram, splToken); + + const newMetapoolTokenAccountBalancesAfter = + await getPoolTokenAccountBalances( + newMetapool, + twoPoolProgram, + splToken, + ); + + expect( + oldMetapoolTokenAccountBalancesAfter[0].eq( + oldMetapoolTokenAccountBalancesBefore[0], + ), + ).toBeTruthy(); + expect( + oldMetapoolTokenAccountBalancesAfter[1].eq( + oldMetapoolTokenAccountBalancesBefore[1], + ), + ).toBeTruthy(); + expect( + newMetapoolTokenAccountBalancesAfter[0].gt( + newMetapoolTokenAccountBalancesBefore[0], + ), + ).toBeTruthy(); + expect( + newMetapoolTokenAccountBalancesAfter[1].lt( + newMetapoolTokenAccountBalancesBefore[1], + ), + ).toBeTruthy(); + + const newMetapoolUserAtaBalancesAfter = await getUserAtaBalancesForPool( + dummyUser.publicKey, + newMetapool, + twoPoolProgram, + splToken, + ); + + expect( + newMetapoolUserAtaBalancesBefore[0].eq( + newMetapoolUserAtaBalancesAfter[0], + ), + ).toBeTruthy(); + // should have received metapool token 1 + expect( + newMetapoolUserAtaBalancesBefore[1].lt( + newMetapoolUserAtaBalancesAfter[1], + ), + ).toBeTruthy(); + expect( + newMetapoolUserAtaBalancesBefore[2].eq( + newMetapoolUserAtaBalancesAfter[2], + ), + ).toBeTruthy(); + }); + it("Can close and re-create a token number map", async () => { + const [tokenNumberMapAddr] = await getToTokenNumberMapAddr( + propeller, + metapoolMint1ToTokenNumber, + propellerProgram.programId, + ); + await propellerProgram.methods + .closeTokenNumberMap(metapoolMint1ToTokenNumber) + .accounts({ + propeller, + governanceKey: propellerGovernanceKey.publicKey, + payer: payer.publicKey, + tokenNumberMap: tokenNumberMapAddr, + }) + .signers([propellerGovernanceKey]) + .rpc(); + const tokenNumberMapDataAfterClose = + await propellerProgram.account.tokenNumberMap.fetchNullable( + tokenNumberMapAddr, + ); + expect(tokenNumberMapDataAfterClose).toBeNull(); + + const swimPayload = { + version: swimPayloadVersion, + owner: provider.publicKey.toBuffer(), + }; + + const amount = parseUnits("1", mintDecimal); + console.info(`amount: ${amount.toString()}`); + + const tokenTransferWithPayloadSignedVaa = + dummyEthTokenBridge.createSignedTokenTransferWithSwimPayloadVAA( + amount.toString(), + swimPayload, + ); + await postVaaToSolana( + tokenTransferWithPayloadSignedVaa, + provider, + wormhole, + ); + + const completeNativeWithPayloadTxn = + await getCompleteNativeWithPayloadTxn( + tokenTransferWithPayloadSignedVaa, + wormhole, + tokenBridge, + propellerProgram, + ); + await provider.sendAndConfirm(completeNativeWithPayloadTxn); + + await expect(() => { + return getProcessSwimPayloadTxn( + metapoolMint1ToTokenNumber, + new BN(0), + tokenTransferWithPayloadSignedVaa, + wormhole, + tokenBridge, + propellerProgram, + twoPoolProgram, + splToken, + ); + }).rejects.toThrow("Token number map does not exist"); + + //recreate tokenNumberMap with original metapool + const metapoolMint1TokenNumberMap = { + pool: metapool, + poolTokenIndex: metapoolMint1PoolTokenIndex, + poolTokenMint: metapoolMint1Keypair.publicKey, + toTokenStep: { swapExactInput: {} }, + }; + + await propellerProgram.methods + .createTokenNumberMap( + metapoolMint1ToTokenNumber, + metapoolMint1TokenNumberMap.pool, + metapoolMint1TokenNumberMap.poolTokenIndex, + metapoolMint1TokenNumberMap.poolTokenMint, + metapoolMint1TokenNumberMap.toTokenStep, + ) + .accounts({ + propeller, + governanceKey: propellerGovernanceKey.publicKey, + payer: payer.publicKey, + systemProgram: web3.SystemProgram.programId, + // rent: web3.SYSVAR_RENT_PUBKEY, + pool: metapool, + twoPoolProgram: twoPoolProgram.programId, + }) + .signers([propellerGovernanceKey]) + .rpc(); + + const tokenNumberMapDataAfterRecreate = + await propellerProgram.account.tokenNumberMap.fetch(tokenNumberMapAddr); + expect(tokenNumberMapDataAfterRecreate.pool.toBase58()).toEqual( + metapool.toBase58(), + ); + const originalMetapoolAtaBalancesBefore = + await getPoolTokenAccountBalances(metapool, twoPoolProgram, splToken); + const processSwimPayloadTxn = await getProcessSwimPayloadTxn( + metapoolMint1ToTokenNumber, + new BN(0), + tokenTransferWithPayloadSignedVaa, + wormhole, + tokenBridge, + propellerProgram, + twoPoolProgram, + splToken, + ); + await provider.sendAndConfirm(processSwimPayloadTxn); + const originalMetapoolAtaBalancesAfter = + await getPoolTokenAccountBalances(metapool, twoPoolProgram, splToken); + //original metapool token[0] (swimUSD) balance should go up + expect( + originalMetapoolAtaBalancesAfter[0].gt( + originalMetapoolAtaBalancesBefore[0], + ), + ).toBeTruthy(); + // pool token[1] balance should go down + expect( + originalMetapoolAtaBalancesAfter[1].lt( + originalMetapoolAtaBalancesBefore[1], + ), + ).toBeTruthy(); + }); // end of close/recreate token number map test + }); + async function checkTxnLogsForMemo( txSig: string, - memoStr: Buffer, + memoBuffer: Buffer, exists = true, ) { console.info(`txSig: ${txSig}`); @@ -4040,7 +4414,7 @@ describe("propeller", () => { const memoLogFound = txnLogs.some( (log) => log.startsWith("Program log: Memo") && - log.includes(memoStr.toString("hex")), + log.includes(memoBuffer.toString("hex")), ); expect(memoLogFound).toEqual(exists); } diff --git a/packages/solana-contracts/src/__tests__/propeller/propellerUtils.ts b/packages/solana-contracts/src/__tests__/propeller/propellerUtils.ts index 06464d03c..cbf161da1 100644 --- a/packages/solana-contracts/src/__tests__/propeller/propellerUtils.ts +++ b/packages/solana-contracts/src/__tests__/propeller/propellerUtils.ts @@ -2,16 +2,20 @@ import type { ChainId, ChainName } from "@certusone/wormhole-sdk"; import { CHAIN_ID_BSC, CHAIN_ID_ETH, + CHAIN_ID_SOLANA, + createNonce, getClaimAddressSolana, - tryHexToNativeString, + postVaaSolanaWithRetry, + tryNativeToHexString, tryUint8ArrayToNative, } from "@certusone/wormhole-sdk"; -import type { Program, SplToken } from "@project-serum/anchor"; +import type { AnchorProvider, Program, SplToken } from "@project-serum/anchor"; import { BN, web3 } from "@project-serum/anchor"; import { MEMO_PROGRAM_ID, createMemoInstruction } from "@solana/spl-memo"; import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, + createAssociatedTokenAccountInstruction, getAssociatedTokenAddress, } from "@solana/spl-token"; import { BigNumber } from "ethers"; @@ -19,6 +23,7 @@ import { BigNumber } from "ethers"; import type { Propeller } from "../../artifacts/propeller"; import type { TwoPool } from "../../artifacts/two_pool"; import { setComputeUnitLimitIx } from "../consts"; +import { getOwnerAtaAddrsForPool } from "../twoPool/poolTestUtils"; import type { ParsedTokenTransferPostedMessage, @@ -27,11 +32,13 @@ import type { import { deriveEndpointPda, deriveMessagePda, + encodeTokenTransferWithPayload, formatParsedTokenTransferPostedMessage, formatParsedTokenTransferSignedVaa, parseTokenTransferPostedMessage, parseTokenTransferSignedVaa, } from "./tokenBridgeUtils"; +import { signAndEncodeVaa } from "./wormholeUtils"; export async function getPropellerPda( mint: web3.PublicKey, @@ -433,14 +440,20 @@ export const formatSwimPayload = ( swimPayload: ParsedSwimPayload, chain: ChainId | ChainName, ) => { - return { + const formattedBaseSwimPayload = { ...swimPayload, - // minOutputAmount: swimPayload.minOutputAmount.toString(), - memo: swimPayload.memo !== undefined ? swimPayload.memo.toString() : "", - // minThreshold: swimPayload.minThreshold.toString(), owner: tryUint8ArrayToNative(swimPayload.owner, chain), - maxFee: - swimPayload.maxFee !== undefined ? swimPayload.maxFee.toString() : "", + }; + const formattedMemo = swimPayload.memo + ? { memo: swimPayload.memo.toString() } + : {}; + const formattedMaxFee = swimPayload.maxFee + ? { maxFee: swimPayload.maxFee.toString() } + : {}; + return { + ...formattedBaseSwimPayload, + ...formattedMemo, + ...formattedMaxFee, }; }; @@ -478,17 +491,17 @@ export const formatParsedTokenTransferWithSwimPayloadPostedMessage = ( }; }; -export const getTargetTokenIdMapAddr = async ( - propeller: web3.PublicKey, - targetTokenId: number, +export const getToTokenNumberMapAddr = async ( + propellerState: web3.PublicKey, + toTokenNumber: number, propellerProgramId: web3.PublicKey, ) => { return await web3.PublicKey.findProgramAddress( [ Buffer.from("propeller"), Buffer.from("token_id"), - propeller.toBuffer(), - new BN(targetTokenId).toArrayLike(Buffer, "le", 2), + propellerState.toBuffer(), + new BN(toTokenNumber).toArrayLike(Buffer, "le", 2), ], propellerProgramId, ); @@ -644,20 +657,6 @@ export const generatePropellerEngineTxns = async ( tokenTransferWithPayloadSignedVaa, ); - //TODO: https://solanacookbook.com/references/basic-transactions.html#how-to-change-compute-budget-fee-priority-for-a-transaction - // const modifyComputeUnits = web3.ComputeBudgetProgram.setComputeUnitLimit({ - // units: 900000 - // }); - // - // const addPriorityFee = web3.ComputeBudgetProgram.setComputeUnitPrice({ - // microLamports: 1 - // }); - - const requestUnitsIx = web3.ComputeBudgetProgram.requestUnits({ - // units: 420690, - units: 900000, - additionalFee: 0, - }); const propellerData = await propellerProgram.account.propeller.fetch( propeller, ); @@ -713,7 +712,7 @@ export const generatePropellerEngineTxns = async ( twoPoolProgram: twoPoolProgram.programId, memo: MEMO_PROGRAM_ID, }) - .preInstructions([requestUnitsIx]) + .preInstructions([setComputeUnitLimitIx]) .signers([payer]); const completeNativeWithPayloadPubkeys = @@ -729,17 +728,19 @@ export const generatePropellerEngineTxns = async ( const completeNativeWithPayloadTxn = await completeNativeWithPayloadIxs.transaction(); txns = [completeNativeWithPayloadTxn]; - const targetTokenId = swimPayload.targetTokenId; - if (!targetTokenId) { + const toTokenNumber = swimPayload.targetTokenId; + if (!toTokenNumber) { throw new Error("No target token id"); } - const [tokenIdMapAddr] = await getTargetTokenIdMapAddr( + const [tokenNumberMapAddr] = await getToTokenNumberMapAddr( propeller, - targetTokenId, + toTokenNumber, propellerProgram.programId, ); - const tokenIdMapData = - await propellerProgram.account.tokenIdMap.fetchNullable(tokenIdMapAddr); + const tokenNumberMapData = + await propellerProgram.account.tokenNumberMap.fetchNullable( + tokenNumberMapAddr, + ); const owner = new web3.PublicKey(swimPayload.owner); // const userTransferAuthority = web3.Keypair.generate(); const [swimClaim] = await getSwimClaimPda( @@ -747,9 +748,9 @@ export const generatePropellerEngineTxns = async ( propellerProgram.programId, ); - if (!tokenIdMapData) { + if (!tokenNumberMapData) { console.info( - `invalid tokenIdMap. targetTokenId: ${targetTokenId.toString()}. Generating fallback transactions`, + `invalid tokenNumberMap. toTokenNumber: ${toTokenNumber.toString()}. Generating fallback transactions`, ); const userSwimUsdAta: web3.PublicKey = await getAssociatedTokenAddress( @@ -771,7 +772,7 @@ export const generatePropellerEngineTxns = async ( feeTracker: propellerEngineFeeTracker, claim: wormholeClaim, swimPayloadMessage, - tokenIdMap: tokenIdMapAddr, + tokenIdMap: tokenNumberMapAddr, swimUsdMint: swimUsdMint, owner, ownerSwimUsdAta: userSwimUsdAta, @@ -803,7 +804,7 @@ export const generatePropellerEngineTxns = async ( swimPayloadMessagePayer: payer.publicKey, redeemer: propellerRedeemer, redeemerEscrow: propellerRedeemerEscrowAccount, - tokenIdMap: tokenIdMapAddr, + tokenNumberMap: tokenNumberMapAddr, userSwimUsdAta: userSwimUsdAta, tokenProgram: splToken.programId, memo: MEMO_PROGRAM_ID, @@ -818,12 +819,12 @@ export const generatePropellerEngineTxns = async ( marginalPricePoolLpMint: marginalPricePoolInfo.lpMint, owner, }) - .preInstructions([requestUnitsIx]) + .preInstructions([setComputeUnitLimitIx]) .signers([payer]) .transaction(); txns = [...txns, propellerProcessSwimPayloadFallbackTxn]; } else { - const tokenIdMapPoolAddr = tokenIdMapData.pool; + const tokenIdMapPoolAddr = tokenNumberMapData.pool; const tokenIdMapPoolData = await twoPoolProgram.account.twoPool.fetch( tokenIdMapPoolAddr, ); @@ -840,7 +841,7 @@ export const generatePropellerEngineTxns = async ( // return await getAssociatedTokenAddress(mint, owner); // }), // ); - const ownerAtaAddrs = await getOwnerTokenAccountsForPool( + const ownerAtaAddrs = await getOwnerAtaAddrsForPool( tokenIdMapPoolAddr, owner, twoPoolProgram, @@ -871,7 +872,7 @@ export const generatePropellerEngineTxns = async ( feeTracker: propellerEngineFeeTracker, claim: wormholeClaim, swimPayloadMessage, - tokenIdMap: tokenIdMapAddr, + tokenNumberMap: tokenNumberMapAddr, pool: tokenIdMapPoolInfo.pool, poolToken0Mint: tokenIdMapPoolInfo.tokenMints[0], poolToken1Mint: tokenIdMapPoolInfo.tokenMints[1], @@ -891,12 +892,12 @@ export const generatePropellerEngineTxns = async ( marginalPricePoolLpMint: marginalPricePoolInfo.lpMint, twoPoolProgram: twoPoolProgram.programId, }) - .preInstructions([requestUnitsIx]) + .preInstructions([setComputeUnitLimitIx]) .transaction(); txns = [...txns, createOwnerAtasTxn]; } const processSwimPayloadPubkeys = await propellerProgram.methods - .processSwimPayload(targetTokenId, new BN(0)) + .processSwimPayload(toTokenNumber, new BN(0)) .accounts({ propeller, payer: payer.publicKey, @@ -921,35 +922,8 @@ export const generatePropellerEngineTxns = async ( }) .pubkeys(); const propellerProcessSwimPayloadTxn = await propellerProgram.methods - .propellerProcessSwimPayload(targetTokenId) + .propellerProcessSwimPayload(toTokenNumber) .accounts({ - // processSwimPayload: { - // propeller, - // payer: payer.publicKey, - // message: wormholeMessage, - // claim: wormholeClaim, - // swimPayloadMessage, - // swimClaim, - // redeemer: propellerRedeemer, - // redeemerEscrow: propellerRedeemerEscrowAccount, - // // tokenIdMap: ? - // pool: tokenIdMapPoolInfo.pool, - // poolTokenAccount0: tokenIdMapPoolInfo.tokenAccounts[0], - // poolTokenAccount1: tokenIdMapPoolInfo.tokenAccounts[1], - // lpMint: tokenIdMapPoolInfo.lpMint, - // governanceFee: tokenIdMapPoolInfo.governanceFeeAcct, - // userTransferAuthority: userTransferAuthority.publicKey, - // // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - // userTokenAccount0: ownerAtaAddrs[0], - // // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - // userTokenAccount1: ownerAtaAddrs[1], - // // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - // userLpTokenAccount: ownerAtaAddrs[2], - // tokenProgram: splToken.programId, - // memo: MEMO_PROGRAM_ID, - // twoPoolProgram: twoPoolProgram.programId, - // systemProgram: web3.SystemProgram.programId, - // }, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore processSwimPayload: processSwimPayloadPubkeys, @@ -963,7 +937,7 @@ export const generatePropellerEngineTxns = async ( owner, memo: MEMO_PROGRAM_ID, }) - .preInstructions([requestUnitsIx]) + .preInstructions([setComputeUnitLimitIx]) .signers([payer]) .transaction(); txns = [...txns, propellerProcessSwimPayloadTxn]; @@ -972,7 +946,7 @@ export const generatePropellerEngineTxns = async ( return txns; }; -export const getCompleteNativeWithPayloadAccounts = async ( +export const getCompleteNativeWithPayloadAccounts3 = async ( tokenTransferWithPayloadSignedVaa: Buffer, wormholeAddresses: WormholeAddresses, propellerProgram: Program, @@ -1049,10 +1023,253 @@ export const getCompleteNativeWithPayloadAccounts = async ( tokenBridge: wormholeAddresses.tokenBridge, }); }; + +export const getCompleteNativeWithPayloadAccounts = async ( + tokenTransferWithPayloadSignedVaa: Buffer, + wormhole: web3.PublicKey, + tokenBridge: web3.PublicKey, + propellerProgram: Program, +) => { + const { tokenTransferVaa } = parseTokenTransferWithSwimPayloadSignedVaa( + tokenTransferWithPayloadSignedVaa, + ); + const [wormholeMessage] = await deriveMessagePda( + tokenTransferWithPayloadSignedVaa, + wormhole, + ); + const wormholeClaim = await getClaimAddressSolana( + tokenBridge.toBase58(), + tokenTransferWithPayloadSignedVaa, + ); + const [swimPayloadMessage] = await getSwimPayloadMessagePda( + wormholeClaim, + propellerProgram.programId, + ); + + const [endpoint] = await deriveEndpointPda( + tokenTransferVaa.core.emitterChain, + tokenTransferVaa.core.emitterAddress, + tokenBridge, + ); + const swimUsdMint = new web3.PublicKey( + tryUint8ArrayToNative( + tokenTransferVaa.tokenTransfer.tokenAddress, + tokenTransferVaa.tokenTransfer.tokenChain, + ), + ); + const [custody] = await web3.PublicKey.findProgramAddress( + [swimUsdMint.toBytes()], + tokenBridge, + ); + + const [tokenBridgeConfig] = await web3.PublicKey.findProgramAddress( + [Buffer.from("config")], + tokenBridge, + ); + const [custodySigner] = await web3.PublicKey.findProgramAddress( + [Buffer.from("custody_signer")], + tokenBridge, + ); + + const propeller = await getPropellerPda( + swimUsdMint, + propellerProgram.programId, + ); + + const propellerData = await propellerProgram.account.propeller.fetch( + propeller, + ); + const propellerFeeVault = propellerData.feeVault; + + const propellerRedeemer = await getPropellerRedeemerPda( + propellerProgram.programId, + ); + + const propellerRedeemerEscrowAccount = await getAssociatedTokenAddress( + swimUsdMint, + propellerRedeemer, + true, + ); + + return propellerProgram.methods + .completeNativeWithPayload() + .accounts({ + propeller, + payer: propellerProgram.provider.publicKey, + tokenBridgeConfig, + message: wormholeMessage, + claim: wormholeClaim, + swimPayloadMessage: swimPayloadMessage, + endpoint: endpoint, + redeemerEscrow: propellerRedeemerEscrowAccount, + redeemer: propellerRedeemer, + feeVault: propellerFeeVault, + custody: custody, + swimUsdMint: swimUsdMint, + custodySigner, + rent: web3.SYSVAR_RENT_PUBKEY, + systemProgram: web3.SystemProgram.programId, + wormhole: wormhole, + tokenProgram: TOKEN_PROGRAM_ID, + tokenBridge: tokenBridge, + }) + .pubkeys(); +}; + export const getCompleteNativeWithPayloadTxn = async ( + tokenTransferWithPayloadSignedVaa: Buffer, + wormhole: web3.PublicKey, + tokenBridge: web3.PublicKey, + propellerProgram: Program, + memo?: Buffer, +): Promise => { + const completeNativeWithPayloadAccounts = + await getCompleteNativeWithPayloadAccounts( + tokenTransferWithPayloadSignedVaa, + wormhole, + tokenBridge, + propellerProgram, + ); + console.info(` + completeNativeWithPayloadAccounts: ${JSON.stringify( + completeNativeWithPayloadAccounts, + null, + 2, + )} + `); + const postIxs = + typeof memo !== "undefined" + ? [createMemoInstruction(memo.toString("hex"))] + : []; + return await propellerProgram.methods + .completeNativeWithPayload() + .accounts({ + ...completeNativeWithPayloadAccounts, + }) + .preInstructions([setComputeUnitLimitIx]) + .postInstructions(postIxs) + .transaction(); +}; + +export const getProcessSwimPayloadTxn = async ( + toTokenNumber: number, + minOutputAmount: BN, + tokenTransferWithPayloadSignedVaa: Buffer, + wormhole: web3.PublicKey, + tokenBridge: web3.PublicKey, + propellerProgram: Program, + twoPoolProgram: Program, + splToken: Program, + memo?: Buffer, +) => { + const { tokenTransferVaa } = parseTokenTransferWithSwimPayloadSignedVaa( + tokenTransferWithPayloadSignedVaa, + ); + const wormholeClaim = await getClaimAddressSolana( + tokenBridge.toBase58(), + tokenTransferWithPayloadSignedVaa, + ); + const [swimPayloadMessage] = await getSwimPayloadMessagePda( + wormholeClaim, + propellerProgram.programId, + ); + + const swimUsdMint = new web3.PublicKey( + tryUint8ArrayToNative( + tokenTransferVaa.tokenTransfer.tokenAddress, + tokenTransferVaa.tokenTransfer.tokenChain, + ), + ); + const propeller = await getPropellerPda( + swimUsdMint, + propellerProgram.programId, + ); + const swimPayloadMessageData = + await propellerProgram.account.swimPayloadMessage.fetch(swimPayloadMessage); + + const propellerRedeemer = await getPropellerRedeemerPda( + propellerProgram.programId, + ); + + const propellerRedeemerEscrowAccount = await getAssociatedTokenAddress( + swimUsdMint, + propellerRedeemer, + true, + ); + const [tokenNumberMapAddr] = await getToTokenNumberMapAddr( + propeller, + toTokenNumber, + propellerProgram.programId, + ); + const tokenNumberMapData = + await propellerProgram.account.tokenNumberMap.fetchNullable( + tokenNumberMapAddr, + ); + //TODO: variant for fallback should be handled in propellerProcessSwimPayload? + if (!tokenNumberMapData) { + throw new Error("Token number map does not exist"); + } + const tokenNumberMapPool = tokenNumberMapData.pool; + const poolData = await twoPoolProgram.account.twoPool.fetch( + tokenNumberMapPool, + ); + const poolMints = [...poolData.tokenMintKeys, poolData.lpMintKey]; + const userAtasForPool = await Promise.all( + poolMints.map(async (mint) => { + const userAta = await getAssociatedTokenAddress( + mint, + swimPayloadMessageData.owner, + ); + const userAtaData = await splToken.account.token.fetchNullable(userAta); + return { + mint, + userAta, + userAtaData, + }; + }), + ); + + const preIxs = userAtasForPool + .filter(({ userAtaData }) => userAtaData === null) + .map(({ mint, userAta }) => { + return createAssociatedTokenAccountInstruction( + propellerProgram.provider.publicKey, + userAta, + swimPayloadMessageData.owner, + mint, + ); + }); + return propellerProgram.methods + .processSwimPayload(toTokenNumber, minOutputAmount) + .accounts({ + propeller, + payer: propellerProgram.provider.publicKey, + claim: wormholeClaim, + swimPayloadMessage, + swimPayloadMessagePayer: swimPayloadMessageData.swimPayloadMessagePayer, + redeemer: propellerRedeemer, + redeemerEscrow: propellerRedeemerEscrowAccount, + // tokenIdMap: ? + pool: tokenNumberMapPool, + poolTokenAccount0: poolData.tokenKeys[0], + poolTokenAccount1: poolData.tokenKeys[1], + lpMint: poolData.lpMintKey, + governanceFee: poolData.governanceFeeKey, + userTokenAccount0: userAtasForPool[0].userAta, + userTokenAccount1: userAtasForPool[1].userAta, + userLpTokenAccount: userAtasForPool[2].userAta, + tokenProgram: splToken.programId, + twoPoolProgram: twoPoolProgram.programId, + systemProgram: web3.SystemProgram.programId, + }) + .preInstructions(preIxs) + .postInstructions(memo ? [createMemoInstruction(memo.toString("hex"))] : []) + .transaction(); +}; + +export const getPropellerCompleteNativeWithPayloadTxn = async ( tokenTransferWithPayloadSignedVaa: Buffer, wormholeAddresses: WormholeAddresses, - propellerEnabled: boolean, memoStr: string | null, propellerProgram: Program, twoPoolProgram: Program, @@ -1133,52 +1350,87 @@ export const getCompleteNativeWithPayloadTxn = async ( const completeNativeWithPayload = await getCompleteNativeWithPayloadAccounts( tokenTransferWithPayloadSignedVaa, - wormholeAddresses, + wormholeAddresses.wormhole, + wormholeAddresses.tokenBridge, + // wormholeAddresses, propellerProgram, ); - if (!propellerEnabled) { - return completeNativeWithPayload - .preInstructions([setComputeUnitLimitIx]) - .postInstructions( - memoStr !== null ? [createMemoInstruction(memoStr)] : [], - ) - .transaction(); - } else { - const completeNativeWithPayloadPubkeys = - await completeNativeWithPayload.pubkeys(); - const [feeTracker] = await getFeeTrackerPda( - swimUsdMint, - propellerProgram.provider.publicKey, - propellerProgram.programId, - ); - const aggregator = propellerData.aggregator; - const marginalPricePool = propellerData.marginalPricePool; - const marginalPricePoolData = await twoPoolProgram.account.twoPool.fetch( - marginalPricePool, - ); - const marginalPricePoolToken0Account = marginalPricePoolData.tokenKeys[0]; - const marginalPricePoolToken1Account = marginalPricePoolData.tokenKeys[1]; - const marginalPricePoolLpMint = marginalPricePoolData.lpMintKey; + // if (!propellerEnabled) { + // return completeNativeWithPayload + // .preInstructions([setComputeUnitLimitIx]) + // .postInstructions( + // memoStr !== null ? [createMemoInstruction(memoStr)] : [], + // ) + // .transaction(); + // } else { + // const completeNativeWithPayloadPubkeys = + // await completeNativeWithPayload.pubkeys(); + // const [feeTracker] = await getFeeTrackerPda( + // swimUsdMint, + // propellerProgram.provider.publicKey!, + // propellerProgram.programId, + // ); + // const aggregator = propellerData.aggregator; + // const marginalPricePool = propellerData.marginalPricePool; + // const marginalPricePoolData = await twoPoolProgram.account.twoPool.fetch( + // marginalPricePool, + // ); + // const marginalPricePoolToken0Account = marginalPricePoolData.tokenKeys[0]; + // const marginalPricePoolToken1Account = marginalPricePoolData.tokenKeys[1]; + // const marginalPricePoolLpMint = marginalPricePoolData.lpMintKey; + // + // return propellerProgram.methods + // .propellerCompleteNativeWithPayload() + // .accounts({ + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-ignore + // completeNativeWithPayload: completeNativeWithPayloadPubkeys, + // feeTracker: feeTracker, + // aggregator, + // marginalPricePool: marginalPricePool, + // marginalPricePoolToken0Account: marginalPricePoolToken0Account, + // marginalPricePoolToken1Account: marginalPricePoolToken1Account, + // marginalPricePoolLpMint: marginalPricePoolLpMint, + // twoPoolProgram: twoPoolProgram.programId, + // memo: MEMO_PROGRAM_ID, + // }) + // .preInstructions([setComputeUnitLimitIx]) + // .transaction(); + // } + const completeNativeWithPayloadPubkeys = + await completeNativeWithPayload.pubkeys(); + const [feeTracker] = await getFeeTrackerPda( + swimUsdMint, + propellerProgram.provider.publicKey!, + propellerProgram.programId, + ); + const aggregator = propellerData.aggregator; + const marginalPricePool = propellerData.marginalPricePool; + const marginalPricePoolData = await twoPoolProgram.account.twoPool.fetch( + marginalPricePool, + ); + const marginalPricePoolToken0Account = marginalPricePoolData.tokenKeys[0]; + const marginalPricePoolToken1Account = marginalPricePoolData.tokenKeys[1]; + const marginalPricePoolLpMint = marginalPricePoolData.lpMintKey; - return propellerProgram.methods - .propellerCompleteNativeWithPayload() - .accounts({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - completeNativeWithPayload: completeNativeWithPayloadPubkeys, - feeTracker: feeTracker, - aggregator, - marginalPricePool: marginalPricePool, - marginalPricePoolToken0Account: marginalPricePoolToken0Account, - marginalPricePoolToken1Account: marginalPricePoolToken1Account, - marginalPricePoolLpMint: marginalPricePoolLpMint, - twoPoolProgram: twoPoolProgram.programId, - memo: MEMO_PROGRAM_ID, - }) - .preInstructions([setComputeUnitLimitIx]) - .transaction(); - } + return propellerProgram.methods + .propellerCompleteNativeWithPayload() + .accounts({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + completeNativeWithPayload: completeNativeWithPayloadPubkeys, + feeTracker: feeTracker, + aggregator, + marginalPricePool: marginalPricePool, + marginalPricePoolToken0Account: marginalPricePoolToken0Account, + marginalPricePoolToken1Account: marginalPricePoolToken1Account, + marginalPricePoolLpMint: marginalPricePoolLpMint, + twoPoolProgram: twoPoolProgram.programId, + memo: MEMO_PROGRAM_ID, + }) + .preInstructions([setComputeUnitLimitIx]) + .transaction(); }; const getMarginalPricePoolInfo = async ( @@ -1273,23 +1525,89 @@ export const getWormholeAddressesForMint = async ( }; }; -export const getOwnerTokenAccountsForPool = async ( - pool: web3.PublicKey, - owner: web3.PublicKey, - twoPoolProgram: Program, -): Promise => { - const tokenIdMapPoolData = await twoPoolProgram.account.twoPool.fetch(pool); - const tokenIdMapPoolInfo = { - pool, - tokenMints: tokenIdMapPoolData.tokenMintKeys, - tokenAccounts: tokenIdMapPoolData.tokenKeys, - lpMint: tokenIdMapPoolData.lpMintKey, - governanceFeeAcct: tokenIdMapPoolData.governanceFeeKey, - }; - const mints = [...tokenIdMapPoolInfo.tokenMints, tokenIdMapPoolInfo.lpMint]; - return await Promise.all( - mints.map(async (mint) => { - return await getAssociatedTokenAddress(mint, owner); - }), +export const postVaaToSolana = async ( + tokenTransferWithPayloadSignedVaa: Buffer, + provider: AnchorProvider, + wormhole: web3.PublicKey, +) => { + await postVaaSolanaWithRetry( + provider.connection, + async (tx) => { + return provider.wallet.signTransaction(tx); + }, + wormhole.toBase58(), + provider.publicKey.toBase58(), + tokenTransferWithPayloadSignedVaa, + 10, ); }; + +export class DummyForeignRoutingProgram { + private _sequence: number; + private readonly _emitterChain: ChainId; + private readonly _emitterAddress: Buffer; + private readonly tokenAddress: Buffer; + private readonly to: web3.PublicKey; + private readonly from: Buffer; + + public constructor( + address: string, + emitterChain: ChainId, + startingSequence = 0, + tokenAddress: Buffer, + to: web3.PublicKey, + from: Buffer, + ) { + this._emitterChain = emitterChain; + this._emitterAddress = Buffer.from( + tryNativeToHexString(address, this._emitterChain), + "hex", + ); + + // uptick this + this._sequence = startingSequence; + this.tokenAddress = tokenAddress; + this.to = to; + this.from = from; + } + + public get emitterChain(): ChainId { + return this._emitterChain; + } + + public get emitterAddress(): Buffer { + return this._emitterAddress; + } + + public get sequence(): number { + return this._sequence; + } + + /** + * It creates a signed token transfer with a swim payload from this to + * solana. + * @param {string} amount - The amount of tokens to transfer. + * @param {Buffer} swimPayload - This is the payload that will be sent to the SWIM server. + * @returns A signed token transfer with a swim payload. + */ + public createSignedTokenTransferWithSwimPayloadVAA( + amount: string, + swimPayload: ParsedSwimPayload, + ) { + return signAndEncodeVaa( + 0, + createNonce().readUInt32LE(0), + this._emitterChain as number, + this._emitterAddress, + BigInt(++this._sequence), + encodeTokenTransferWithPayload( + amount.toString(), + this.tokenAddress, + CHAIN_ID_SOLANA, + this.to, + this.from, + encodeSwimPayload(swimPayload), + ), + ); + } +} diff --git a/packages/solana-contracts/src/__tests__/propeller/tokenBridgeUtils.ts b/packages/solana-contracts/src/__tests__/propeller/tokenBridgeUtils.ts index ab8e83508..a6f82766d 100644 --- a/packages/solana-contracts/src/__tests__/propeller/tokenBridgeUtils.ts +++ b/packages/solana-contracts/src/__tests__/propeller/tokenBridgeUtils.ts @@ -1,30 +1,22 @@ import type { ChainId } from "@certusone/wormhole-sdk"; -import { - CHAIN_ID_SOLANA, - toChainName, - tryNativeToHexString, - tryUint8ArrayToNative, -} from "@certusone/wormhole-sdk"; +import { CHAIN_ID_SOLANA, toChainName, tryNativeToHexString, tryUint8ArrayToNative } from "@certusone/wormhole-sdk"; import { BN, web3 } from "@project-serum/anchor"; // eslint-disable-next-line import/order import * as byteify from "byteify"; // import { toBigNumberHex } from "./utils"; - import type { BigNumberish } from "ethers"; import { BigNumber } from "ethers"; // import { PostVaaMethod } from "./types"; import keccak256 from "keccak256"; - +import type { ParsedPostedMessage, ParsedVaa } from "./wormholeUtils"; import { - WORMHOLE_TOKEN_BRIDGE, formatParsedVaa, formatPostedMessage, parsePostedMessage, parseVaa, - // signAndEncodeVaa, + WORMHOLE_TOKEN_BRIDGE, } from "./wormholeUtils"; -import type { ParsedPostedMessage, ParsedVaa } from "./wormholeUtils"; export function toBigNumberHex(value: BigNumberish, numBytes: number): string { return BigNumber.from(value) @@ -285,7 +277,6 @@ export const deriveMessagePda = async ( signedVaa: Buffer, programId: web3.PublicKey, ) => { - const hash = hashVaa(signedVaa); // const hexHash = await getSignedVAAHash(signedVaa); // const hash2 = Buffer.from(hexToUint8Array(hexHash)); // console.info(` @@ -302,6 +293,7 @@ export const deriveMessagePda = async ( // hash2: ${hash2} // hash2BufferHex: ${Buffer.from(hash2).toString("hex")} // `); + const hash = hashVaa(signedVaa); return await web3.PublicKey.findProgramAddress( [Buffer.from("PostedVAA"), hash], programId, @@ -402,6 +394,7 @@ export async function parseTokenTransferPostedMessage( postedMessage: Buffer, ): Promise { const parsed = await parsePostedMessage(postedMessage); + console.info(`finished parsePostedMessage`); const data = parsed.data; const tokenTransfer = parseTokenTransfer(data); return { @@ -673,3 +666,4 @@ export async function getMintMetaPdas(mintKey: web3.PublicKey) { // ); // } // } + diff --git a/packages/solana-contracts/src/__tests__/twoPool/poolTestUtils.ts b/packages/solana-contracts/src/__tests__/twoPool/poolTestUtils.ts index 9c935a4ee..ca99bc93f 100644 --- a/packages/solana-contracts/src/__tests__/twoPool/poolTestUtils.ts +++ b/packages/solana-contracts/src/__tests__/twoPool/poolTestUtils.ts @@ -1,4 +1,4 @@ -import type { BN, Program, SplToken } from "@project-serum/anchor"; +import type { BN, Program, Program, SplToken } from "@project-serum/anchor"; import { web3 } from "@project-serum/anchor"; import { getAssociatedTokenAddress, @@ -6,7 +6,7 @@ import { } from "@solana/spl-token"; import type { Commitment, ConfirmOptions } from "@solana/web3.js"; -import type { TwoPool } from "../../artifacts/two_pool"; +import type { TwoPool, TwoPool } from "../../artifacts/two_pool"; /** * It initializes the mints for the tokens that will be used in the pool, and then *CALCULATES* the pool token accounts and @@ -375,3 +375,101 @@ export function printBeforeAndAfterPoolUserBalances( after: ${previousDepthAfter.toString()} `); } + +export const getPoolTokenKeys = async ( + pool: web3.PublicKey, + twoPoolProgram: Program, +) => { + const poolInfo = await twoPoolProgram.account.twoPool.fetch(pool); + return poolInfo.tokenKeys; +}; + +export const getPoolTokenAccountBalances = async ( + pool: web3.PublicKey, + twoPoolProgram: Program, + splToken: Program, +): Promise => { + const poolTokenKeys = await getPoolTokenKeys(pool, twoPoolProgram); + const poolAtaData = await Promise.all( + poolTokenKeys.map(async (tokenKey) => { + return splToken.account.token.fetch(tokenKey); + }), + ); + return poolAtaData.map((ata) => ata.amount); +}; + +export const getOwnerAtaAddrsForPool = async ( + pool: web3.PublicKey, + owner: web3.PublicKey, + twoPoolProgram: Program, +): Promise => { + const tokenIdMapPoolData = await twoPoolProgram.account.twoPool.fetch(pool); + const tokenIdMapPoolInfo = { + pool, + tokenMints: tokenIdMapPoolData.tokenMintKeys, + tokenAccounts: tokenIdMapPoolData.tokenKeys, + lpMint: tokenIdMapPoolData.lpMintKey, + governanceFeeAcct: tokenIdMapPoolData.governanceFeeKey, + }; + const mints = [...tokenIdMapPoolInfo.tokenMints, tokenIdMapPoolInfo.lpMint]; + return await Promise.all( + mints.map(async (mint) => { + return await getAssociatedTokenAddress(mint, owner); + }), + ); +}; + +export const getUserAtaDataForPool = async ( + user: web3.PublicKey, + pool: web3.PublicKey, + twoPoolProgram: Program, + splToken: Program, +) => { + const userAtaAddrs = await getOwnerAtaAddrsForPool( + pool, + user, + twoPoolProgram, + ); + return await Promise.all( + userAtaAddrs.map(async (ataAddr) => { + return splToken.account.token.fetch(ataAddr); + }), + ); +}; + +export const getNullableUserAtaDataForPool = async ( + user: web3.PublicKey, + pool: web3.PublicKey, + twoPoolProgram: Program, + splToken: Program, +) => { + const userAtaAddrs = await getOwnerAtaAddrsForPool( + pool, + user, + twoPoolProgram, + ); + return await Promise.all( + userAtaAddrs.map(async (ataAddr) => { + const data = await splToken.account.token.fetchNullable(ataAddr); + return { + ataAddr, + data, + }; + }), + ); +}; + +export const getUserAtaBalancesForPool = async ( + user: web3.PublicKey, + pool: web3.PublicKey, + twoPoolProgram: Program, + splToken: Program, +) => { + const userTokenAccounts = await getUserAtaDataForPool( + user, + pool, + twoPoolProgram, + splToken, + ); + return userTokenAccounts.map((ata) => ata.amount); +}; diff --git a/packages/solana-contracts/src/artifacts/propeller.json b/packages/solana-contracts/src/artifacts/propeller.json index e2c3ebdc4..fdb6483d2 100644 --- a/packages/solana-contracts/src/artifacts/propeller.json +++ b/packages/solana-contracts/src/artifacts/propeller.json @@ -64,7 +64,12 @@ "isSigner": false }, { - "name": "admin", + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "pauseKey", "isMut": false, "isSigner": true }, @@ -171,7 +176,79 @@ ] }, { - "name": "createTokenIdMap", + "name": "setPaused", + "accounts": [ + { + "name": "propeller", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "pauseKey", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "paused", + "type": "bool" + } + ] + }, + { + "name": "changePauseKey", + "accounts": [ + { + "name": "propeller", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "newPauseKey", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "prepareGovernanceTransition", "accounts": [ { "name": "propeller", @@ -194,7 +271,69 @@ } }, { - "name": "admin", + "name": "governanceKey", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "enactGovernanceTransition", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "createTokenNumberMap", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", "isMut": false, "isSigner": true }, @@ -217,12 +356,12 @@ { "kind": "arg", "type": "publicKey", - "path": "pool" + "path": "pool.token_mint_keys [0]" }, { "kind": "arg", "type": "publicKey", - "path": "pool" + "path": "pool.token_mint_keys [1]" }, { "kind": "arg", @@ -238,7 +377,7 @@ } }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": true, "isSigner": false, "pda": { @@ -262,7 +401,7 @@ { "kind": "arg", "type": "u16", - "path": "target_token_index" + "path": "to_token_number" } ] } @@ -280,7 +419,7 @@ ], "args": [ { - "name": "targetTokenIndex", + "name": "toTokenNumber", "type": "u16" }, { @@ -296,15 +435,184 @@ "type": "publicKey" }, { - "name": "poolIx", + "name": "toTokenStep", + "type": { + "defined": "ToTokenStep" + } + } + ] + }, + { + "name": "updateTokenNumberMap", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "pool", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenNumberMap", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", + "type": "string", + "value": "token_id" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller" + }, + { + "kind": "arg", + "type": "u16", + "path": "to_token_number" + } + ] + } + }, + { + "name": "twoPoolProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "toTokenNumber", + "type": "u16" + }, + { + "name": "poolTokenIndex", + "type": "u8" + }, + { + "name": "poolTokenMint", + "type": "publicKey" + }, + { + "name": "toTokenStep", "type": { - "defined": "PoolInstruction" + "defined": "ToTokenStep" + } + } + ] + }, + { + "name": "closeTokenNumberMap", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "tokenNumberMap", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", + "type": "string", + "value": "token_id" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller" + }, + { + "kind": "arg", + "type": "u16", + "path": "to_token_number" + } + ] } } + ], + "args": [ + { + "name": "toTokenNumber", + "type": "u16" + } ] }, { "name": "createTargetChainMap", + "docs": [ + "Target Chain Map *" + ], "accounts": [ { "name": "propeller", @@ -327,7 +635,7 @@ } }, { - "name": "admin", + "name": "governanceKey", "isMut": false, "isSigner": true }, @@ -407,7 +715,7 @@ } }, { - "name": "admin", + "name": "governanceKey", "isMut": false, "isSigner": true }, @@ -441,11 +749,6 @@ } ] } - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false } ], "args": [ @@ -460,6 +763,73 @@ } ] }, + { + "name": "targetChainMapSetPaused", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "pauseKey", + "isMut": false, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "targetChainMap", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller" + }, + { + "kind": "account", + "type": "u16", + "account": "TargetChainMap", + "path": "target_chain_map.target_chain" + } + ] + } + } + ], + "args": [ + { + "name": "isPaused", + "type": "bool" + } + ] + }, { "name": "initializeFeeTracker", "accounts": [ @@ -1406,6 +1776,10 @@ } ], "args": [ + { + "name": "nonce", + "type": "u32" + }, { "name": "amount", "type": "u64" @@ -1716,6 +2090,10 @@ } ], "args": [ + { + "name": "nonce", + "type": "u32" + }, { "name": "amount", "type": "u64" @@ -1796,8 +2174,7 @@ "programId": { "kind": "account", "type": "publicKey", - "account": "Propeller", - "path": "propeller" + "path": "token_bridge" } } }, @@ -1867,13 +2244,53 @@ "isSigner": false, "docs": [ "this is \"to_fees\"", - "recipient of fees for executing complete transfer (e.g. relayer)" + "recipient of fees for executing complete transfer (e.g. relayer)", + "this is only used in `propellerCompleteNativeWithPayload`." ] }, + { + "name": "swimPayloadMessage", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", + "type": "string", + "value": "swim_payload" + }, + { + "kind": "account", + "type": "publicKey", + "path": "claim" + } + ] + } + }, { "name": "custody", "isMut": true, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "swim_usd_mint" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { "name": "swimUsdMint", @@ -1883,56 +2300,46 @@ { "name": "custodySigner", "isMut": false, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "custody_signer" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { - "name": "rent", + "name": "wormhole", "isMut": false, "isSigner": false }, { - "name": "systemProgram", + "name": "tokenProgram", "isMut": false, "isSigner": false }, { - "name": "wormhole", + "name": "tokenBridge", "isMut": false, "isSigner": false }, { - "name": "tokenProgram", + "name": "systemProgram", "isMut": false, "isSigner": false }, { - "name": "tokenBridge", + "name": "rent", "isMut": false, "isSigner": false - }, - { - "name": "swimPayloadMessage", - "isMut": true, - "isSigner": false, - "pda": { - "seeds": [ - { - "kind": "const", - "type": "string", - "value": "propeller" - }, - { - "kind": "const", - "type": "string", - "value": "swim_payload" - }, - { - "kind": "account", - "type": "publicKey", - "path": "claim" - } - ] - } } ], "args": [] @@ -2083,7 +2490,7 @@ "isSigner": false }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "pda": { @@ -2107,7 +2514,7 @@ { "kind": "arg", "type": "u16", - "path": "target_token_id" + "path": "to_token_number" } ] } @@ -2202,7 +2609,7 @@ ], "args": [ { - "name": "targetTokenId", + "name": "toTokenNumber", "type": "u16" }, { @@ -2258,8 +2665,7 @@ "programId": { "kind": "account", "type": "publicKey", - "account": "Propeller", - "path": "propeller" + "path": "token_bridge" } } }, @@ -2329,13 +2735,53 @@ "isSigner": false, "docs": [ "this is \"to_fees\"", - "recipient of fees for executing complete transfer (e.g. relayer)" + "recipient of fees for executing complete transfer (e.g. relayer)", + "this is only used in `propellerCompleteNativeWithPayload`." ] }, + { + "name": "swimPayloadMessage", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", + "type": "string", + "value": "swim_payload" + }, + { + "kind": "account", + "type": "publicKey", + "path": "claim" + } + ] + } + }, { "name": "custody", "isMut": true, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "swim_usd_mint" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { "name": "swimUsdMint", @@ -2345,56 +2791,46 @@ { "name": "custodySigner", "isMut": false, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "custody_signer" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { - "name": "rent", + "name": "wormhole", "isMut": false, "isSigner": false }, { - "name": "systemProgram", + "name": "tokenProgram", "isMut": false, "isSigner": false }, { - "name": "wormhole", + "name": "tokenBridge", "isMut": false, "isSigner": false }, { - "name": "tokenProgram", + "name": "systemProgram", "isMut": false, "isSigner": false }, { - "name": "tokenBridge", + "name": "rent", "isMut": false, "isSigner": false - }, - { - "name": "swimPayloadMessage", - "isMut": true, - "isSigner": false, - "pda": { - "seeds": [ - { - "kind": "const", - "type": "string", - "value": "propeller" - }, - { - "kind": "const", - "type": "string", - "value": "swim_payload" - }, - { - "kind": "account", - "type": "publicKey", - "path": "claim" - } - ] - } } ] }, @@ -2658,7 +3094,7 @@ } }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "pda": { @@ -2998,7 +3434,7 @@ "isSigner": false }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "pda": { @@ -3022,7 +3458,7 @@ { "kind": "arg", "type": "u16", - "path": "target_token_id" + "path": "to_token_number" } ] } @@ -3238,7 +3674,7 @@ ], "args": [ { - "name": "targetTokenId", + "name": "toTokenNumber", "type": "u16" } ], @@ -3694,11 +4130,11 @@ "isSigner": false }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "docs": [ - "deserialized as a `TokenIdMap`. if it does exist, then engine should have called", + "deserialized as a `TokenNumberMap`. if it does exist, then engine should have called", "propeller_create_owner_token_accounts instead" ], "pda": { @@ -3908,17 +4344,25 @@ 32 ] } + }, + { + "name": "isPaused", + "type": "bool" } ] } }, { - "name": "TokenIdMap", + "name": "TokenNumberMap", "type": { "kind": "struct", "fields": [ { - "name": "outputTokenIndex", + "name": "bump", + "type": "u8" + }, + { + "name": "toTokenNumber", "type": "u16" }, { @@ -3934,14 +4378,10 @@ "type": "publicKey" }, { - "name": "poolIx", + "name": "toTokenStep", "type": { - "defined": "PoolInstruction" + "defined": "ToTokenStep" } - }, - { - "name": "bump", - "type": "u8" } ] } @@ -3956,19 +4396,23 @@ "type": "u8" }, { - "name": "nonce", - "type": "u32" + "name": "isPaused", + "type": "bool" }, { - "name": "admin", + "name": "governanceKey", "type": "publicKey" }, { - "name": "wormhole", + "name": "preparedGovernanceKey", "type": "publicKey" }, { - "name": "tokenBridge", + "name": "governanceTransitionTs", + "type": "i64" + }, + { + "name": "pauseKey", "type": "publicKey" }, { @@ -4489,7 +4933,7 @@ } }, { - "name": "PoolInstruction", + "name": "ToTokenStep", "type": { "kind": "enum", "variants": [ @@ -4505,20 +4949,6 @@ ] } }, - { - "name": "SwimPayloadVersion", - "type": { - "kind": "enum", - "variants": [ - { - "name": "V0" - }, - { - "name": "V1" - } - ] - } - }, { "name": "ConsistencyLevel", "type": { @@ -4685,7 +5115,7 @@ { "code": 6023, "name": "StaleFeed", - "msg": "Switchboard feed has not been updated in 5 minutes" + "msg": "Switchboard feed value is stale " }, { "code": 6024, @@ -4699,133 +5129,163 @@ }, { "code": 6026, - "name": "InvalidClaimData", - "msg": "Invalid claim data" + "name": "InvalidWormholeClaimAccount", + "msg": "Invalid Wormhole Claim Account" }, { "code": 6027, - "name": "ClaimNotClaimed", - "msg": "Claim Account not claimed" + "name": "InvalidClaimData", + "msg": "Invalid claim data" }, { "code": 6028, - "name": "InvalidPropellerAdmin", - "msg": "Invalid Propeller Admin" + "name": "ClaimNotClaimed", + "msg": "Claim Account not claimed" }, { "code": 6029, - "name": "InvalidTokenIdMapPool", - "msg": "Invalid Pool for Token Id Map" + "name": "InvalidPropellerGovernanceKey", + "msg": "Invalid Propeller GovernanceKey" }, { "code": 6030, - "name": "InvalidOutputTokenIndex", - "msg": "Invalid Output Token Index" + "name": "InvalidPropellerPauseKey", + "msg": "Invalid Propeller Pause Key" }, { "code": 6031, - "name": "InvalidTokenIdMapPoolTokenIndex", - "msg": "Invalid Pool Token Index for Token Id Map" + "name": "InvalidTokenNumberMapPool", + "msg": "Invalid Pool for Token Number Map" }, { "code": 6032, - "name": "InvalidTokenIdMapPoolTokenMint", - "msg": "Invalid Pool Token Mint for Token Id Map" + "name": "InvalidOutputTokenIndex", + "msg": "Invalid Output Token Index" }, { "code": 6033, - "name": "InvalidTokenIdMapPoolIx", - "msg": "Invalid Pool Ix for Token Id Map" + "name": "InvalidTokenNumberMapPoolTokenIndex", + "msg": "Invalid Pool Token Index for Token Number Map" }, { "code": 6034, + "name": "InvalidTokenNumberMapPoolTokenMint", + "msg": "Invalid Pool Token Mint for Token Number Map" + }, + { + "code": 6035, + "name": "InvalidTokenNumberMapToTokenStep", + "msg": "Invalid To Token Step for Token Number Map" + }, + { + "code": 6036, "name": "InvalidSwimPayloadGasKickstart", "msg": "Invalid Gas Kickstart parameter in Swim Payload" }, { - "code": 6035, + "code": 6037, "name": "InvalidMarginalPricePool", "msg": "Invalid Marginal Price Pool" }, { - "code": 6036, + "code": 6038, "name": "InvalidMarginalPricePoolAccounts", "msg": "Invalid Marginal Price Pool Accounts" }, { - "code": 6037, + "code": 6039, "name": "NotPropellerEnabled", "msg": "Propeller Not Enabled in payload" }, { - "code": 6038, + "code": 6040, "name": "InvalidRoutingContractAddress", "msg": "Invalid Routing Contract Address" }, { - "code": 6039, + "code": 6041, "name": "IntegerOverflow", "msg": "Integer Overflow" }, { - "code": 6040, + "code": 6042, "name": "ConversionError", "msg": "Conversion Error" }, { - "code": 6041, + "code": 6043, "name": "UnableToRetrieveSwimUsdMintDecimals", "msg": "Unable to retrieve SwimUSD mint decimals from marginal price pool information" }, { - "code": 6042, + "code": 6044, "name": "InvalidMetapoolTokenMint", "msg": "Invalid Metapool Token Mint. token_mint[0] should == swim_usd_mint" }, { - "code": 6043, + "code": 6045, "name": "UnableToDeserializeTokenAccount", "msg": "Unable to deserialize account info as token account" }, { - "code": 6044, + "code": 6046, "name": "InvalidTokenAccountDataLen", "msg": "Invalid token account data length. != 0 && != TokenAccount::LEN" }, { - "code": 6045, + "code": 6047, "name": "PayerInsufficientFundsForGasKickstart", "msg": "Payer has insufficient funds for gas kickstart" }, { - "code": 6046, + "code": 6048, "name": "IncorrectOwnerForCreateTokenAccount", "msg": "Owner of token account != swimPayload.owner" }, { - "code": 6047, - "name": "TokenIdMapExists", - "msg": "TokenIdMap exists. Please use the correct instruction" - }, - { - "code": 6048, - "name": "InvalidTokenIdMapAccountAddress", - "msg": "Invalid address for TokenIdMap account" + "code": 6049, + "name": "TokenNumberMapExists", + "msg": "TokenNumberMap exists. Please use the correct instruction" }, { - "code": 6049, + "code": 6050, "name": "InvalidSwimPayloadVersion", "msg": "Invalid Swim Payload version" }, { - "code": 6050, + "code": 6051, "name": "InvalidAggregator", "msg": "Invalid Aggregator" }, { - "code": 6051, + "code": 6052, "name": "InvalidFeeVault", "msg": "Invalid Fee Vault" + }, + { + "code": 6053, + "name": "InvalidMemo", + "msg": "Invalid Memo" + }, + { + "code": 6054, + "name": "ToTokenNumberMismatch", + "msg": "ToTokenNumber does not match SwimPayload.to_tokenNumber" + }, + { + "code": 6055, + "name": "IsPaused", + "msg": "Routing Contract is paused" + }, + { + "code": 6056, + "name": "TargetChainIsPaused", + "msg": "Target Chain is paused" + }, + { + "code": 6057, + "name": "InvalidSwimPayloadMessagePayer", + "msg": "Invalid SwimPayloadMessagePayer" } ] } \ No newline at end of file diff --git a/packages/solana-contracts/src/artifacts/propeller.ts b/packages/solana-contracts/src/artifacts/propeller.ts index 86a32f67a..68c57e6f7 100644 --- a/packages/solana-contracts/src/artifacts/propeller.ts +++ b/packages/solana-contracts/src/artifacts/propeller.ts @@ -64,7 +64,12 @@ export type Propeller = { "isSigner": false }, { - "name": "admin", + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "pauseKey", "isMut": false, "isSigner": true }, @@ -171,7 +176,141 @@ export type Propeller = { ] }, { - "name": "createTokenIdMap", + "name": "setPaused", + "accounts": [ + { + "name": "propeller", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "pauseKey", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "paused", + "type": "bool" + } + ] + }, + { + "name": "changePauseKey", + "accounts": [ + { + "name": "propeller", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "newPauseKey", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "prepareGovernanceTransition", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "enactGovernanceTransition", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "createTokenNumberMap", "accounts": [ { "name": "propeller", @@ -194,7 +333,7 @@ export type Propeller = { } }, { - "name": "admin", + "name": "governanceKey", "isMut": false, "isSigner": true }, @@ -217,12 +356,12 @@ export type Propeller = { { "kind": "arg", "type": "publicKey", - "path": "pool" + "path": "pool.token_mint_keys [0]" }, { "kind": "arg", "type": "publicKey", - "path": "pool" + "path": "pool.token_mint_keys [1]" }, { "kind": "arg", @@ -238,7 +377,7 @@ export type Propeller = { } }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": true, "isSigner": false, "pda": { @@ -262,7 +401,7 @@ export type Propeller = { { "kind": "arg", "type": "u16", - "path": "target_token_index" + "path": "to_token_number" } ] } @@ -280,7 +419,7 @@ export type Propeller = { ], "args": [ { - "name": "targetTokenIndex", + "name": "toTokenNumber", "type": "u16" }, { @@ -296,15 +435,15 @@ export type Propeller = { "type": "publicKey" }, { - "name": "poolIx", + "name": "toTokenStep", "type": { - "defined": "PoolInstruction" + "defined": "ToTokenStep" } } ] }, { - "name": "createTargetChainMap", + "name": "updateTokenNumberMap", "accounts": [ { "name": "propeller", @@ -327,7 +466,7 @@ export type Propeller = { } }, { - "name": "admin", + "name": "governanceKey", "isMut": false, "isSigner": true }, @@ -337,7 +476,12 @@ export type Propeller = { "isSigner": true }, { - "name": "targetChainMap", + "name": "pool", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenNumberMap", "isMut": true, "isSigner": false, "pda": { @@ -347,6 +491,11 @@ export type Propeller = { "type": "string", "value": "propeller" }, + { + "kind": "const", + "type": "string", + "value": "token_id" + }, { "kind": "account", "type": "publicKey", @@ -356,35 +505,40 @@ export type Propeller = { { "kind": "arg", "type": "u16", - "path": "target_chain" + "path": "to_token_number" } ] } }, { - "name": "systemProgram", + "name": "twoPoolProgram", "isMut": false, "isSigner": false } ], "args": [ { - "name": "targetChain", + "name": "toTokenNumber", "type": "u16" }, { - "name": "targetAddress", + "name": "poolTokenIndex", + "type": "u8" + }, + { + "name": "poolTokenMint", + "type": "publicKey" + }, + { + "name": "toTokenStep", "type": { - "array": [ - "u8", - 32 - ] + "defined": "ToTokenStep" } } ] }, { - "name": "updateTargetChainMap", + "name": "closeTokenNumberMap", "accounts": [ { "name": "propeller", @@ -407,7 +561,7 @@ export type Propeller = { } }, { - "name": "admin", + "name": "governanceKey", "isMut": false, "isSigner": true }, @@ -417,7 +571,7 @@ export type Propeller = { "isSigner": true }, { - "name": "targetChainMap", + "name": "tokenNumberMap", "isMut": true, "isSigner": false, "pda": { @@ -427,6 +581,11 @@ export type Propeller = { "type": "string", "value": "propeller" }, + { + "kind": "const", + "type": "string", + "value": "token_id" + }, { "kind": "account", "type": "publicKey", @@ -434,34 +593,26 @@ export type Propeller = { "path": "propeller" }, { - "kind": "account", + "kind": "arg", "type": "u16", - "account": "TargetChainMap", - "path": "target_chain_map.target_chain" + "path": "to_token_number" } ] } - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false } ], "args": [ { - "name": "routingContract", - "type": { - "array": [ - "u8", - 32 - ] - } + "name": "toTokenNumber", + "type": "u16" } ] }, { - "name": "initializeFeeTracker", + "name": "createTargetChainMap", + "docs": [ + "Target Chain Map *" + ], "accounts": [ { "name": "propeller", @@ -477,14 +628,24 @@ export type Propeller = { { "kind": "account", "type": "publicKey", - "account": "Mint", - "path": "swim_usd_mint" + "account": "Propeller", + "path": "propeller.swim_usd_mint" } ] } }, { - "name": "feeTracker", + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "targetChainMap", "isMut": true, "isSigner": false, "pda": { @@ -494,45 +655,44 @@ export type Propeller = { "type": "string", "value": "propeller" }, - { - "kind": "const", - "type": "string", - "value": "fee" - }, { "kind": "account", "type": "publicKey", - "account": "Mint", - "path": "swim_usd_mint" + "account": "Propeller", + "path": "propeller" }, { - "kind": "account", - "type": "publicKey", - "path": "payer" + "kind": "arg", + "type": "u16", + "path": "target_chain" } ] } }, - { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "swimUsdMint", - "isMut": false, - "isSigner": false - }, { "name": "systemProgram", "isMut": false, "isSigner": false } ], - "args": [] + "args": [ + { + "name": "targetChain", + "type": "u16" + }, + { + "name": "targetAddress", + "type": { + "array": [ + "u8", + 32 + ] + } + } + ] }, { - "name": "claimFees", + "name": "updateTargetChainMap", "accounts": [ { "name": "propeller", @@ -555,7 +715,17 @@ export type Propeller = { } }, { - "name": "feeTracker", + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "targetChainMap", "isMut": true, "isSigner": false, "pda": { @@ -566,7 +736,207 @@ export type Propeller = { "value": "propeller" }, { - "kind": "const", + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller" + }, + { + "kind": "account", + "type": "u16", + "account": "TargetChainMap", + "path": "target_chain_map.target_chain" + } + ] + } + } + ], + "args": [ + { + "name": "routingContract", + "type": { + "array": [ + "u8", + 32 + ] + } + } + ] + }, + { + "name": "targetChainMapSetPaused", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "pauseKey", + "isMut": false, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "targetChainMap", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller" + }, + { + "kind": "account", + "type": "u16", + "account": "TargetChainMap", + "path": "target_chain_map.target_chain" + } + ] + } + } + ], + "args": [ + { + "name": "isPaused", + "type": "bool" + } + ] + }, + { + "name": "initializeFeeTracker", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "swim_usd_mint" + } + ] + } + }, + { + "name": "feeTracker", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", + "type": "string", + "value": "fee" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "swim_usd_mint" + }, + { + "kind": "account", + "type": "publicKey", + "path": "payer" + } + ] + } + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "swimUsdMint", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "claimFees", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "feeTracker", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", "type": "string", "value": "fee" }, @@ -1406,6 +1776,10 @@ export type Propeller = { } ], "args": [ + { + "name": "nonce", + "type": "u32" + }, { "name": "amount", "type": "u64" @@ -1716,6 +2090,10 @@ export type Propeller = { } ], "args": [ + { + "name": "nonce", + "type": "u32" + }, { "name": "amount", "type": "u64" @@ -1796,8 +2174,7 @@ export type Propeller = { "programId": { "kind": "account", "type": "publicKey", - "account": "Propeller", - "path": "propeller" + "path": "token_bridge" } } }, @@ -1867,49 +2244,10 @@ export type Propeller = { "isSigner": false, "docs": [ "this is \"to_fees\"", - "recipient of fees for executing complete transfer (e.g. relayer)" + "recipient of fees for executing complete transfer (e.g. relayer)", + "this is only used in `propellerCompleteNativeWithPayload`." ] }, - { - "name": "custody", - "isMut": true, - "isSigner": false - }, - { - "name": "swimUsdMint", - "isMut": false, - "isSigner": false - }, - { - "name": "custodySigner", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "wormhole", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenBridge", - "isMut": false, - "isSigner": false - }, { "name": "swimPayloadMessage", "isMut": true, @@ -1933,16 +2271,85 @@ export type Propeller = { } ] } - } - ], - "args": [] - }, - { - "name": "processSwimPayload", - "accounts": [ + }, { - "name": "propeller", - "isMut": false, + "name": "custody", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "swim_usd_mint" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } + }, + { + "name": "swimUsdMint", + "isMut": false, + "isSigner": false + }, + { + "name": "custodySigner", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "custody_signer" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } + }, + { + "name": "wormhole", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenBridge", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "processSwimPayload", + "accounts": [ + { + "name": "propeller", + "isMut": false, "isSigner": false, "pda": { "seeds": [ @@ -2083,7 +2490,7 @@ export type Propeller = { "isSigner": false }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "pda": { @@ -2107,7 +2514,7 @@ export type Propeller = { { "kind": "arg", "type": "u16", - "path": "target_token_id" + "path": "to_token_number" } ] } @@ -2202,7 +2609,7 @@ export type Propeller = { ], "args": [ { - "name": "targetTokenId", + "name": "toTokenNumber", "type": "u16" }, { @@ -2258,8 +2665,7 @@ export type Propeller = { "programId": { "kind": "account", "type": "publicKey", - "account": "Propeller", - "path": "propeller" + "path": "token_bridge" } } }, @@ -2329,13 +2735,53 @@ export type Propeller = { "isSigner": false, "docs": [ "this is \"to_fees\"", - "recipient of fees for executing complete transfer (e.g. relayer)" + "recipient of fees for executing complete transfer (e.g. relayer)", + "this is only used in `propellerCompleteNativeWithPayload`." ] }, + { + "name": "swimPayloadMessage", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", + "type": "string", + "value": "swim_payload" + }, + { + "kind": "account", + "type": "publicKey", + "path": "claim" + } + ] + } + }, { "name": "custody", "isMut": true, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "swim_usd_mint" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { "name": "swimUsdMint", @@ -2345,56 +2791,46 @@ export type Propeller = { { "name": "custodySigner", "isMut": false, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "custody_signer" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { - "name": "rent", + "name": "wormhole", "isMut": false, "isSigner": false }, { - "name": "systemProgram", + "name": "tokenProgram", "isMut": false, "isSigner": false }, { - "name": "wormhole", + "name": "tokenBridge", "isMut": false, "isSigner": false }, { - "name": "tokenProgram", + "name": "systemProgram", "isMut": false, "isSigner": false }, { - "name": "tokenBridge", + "name": "rent", "isMut": false, "isSigner": false - }, - { - "name": "swimPayloadMessage", - "isMut": true, - "isSigner": false, - "pda": { - "seeds": [ - { - "kind": "const", - "type": "string", - "value": "propeller" - }, - { - "kind": "const", - "type": "string", - "value": "swim_payload" - }, - { - "kind": "account", - "type": "publicKey", - "path": "claim" - } - ] - } } ] }, @@ -2658,7 +3094,7 @@ export type Propeller = { } }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "pda": { @@ -2998,7 +3434,7 @@ export type Propeller = { "isSigner": false }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "pda": { @@ -3022,7 +3458,7 @@ export type Propeller = { { "kind": "arg", "type": "u16", - "path": "target_token_id" + "path": "to_token_number" } ] } @@ -3238,7 +3674,7 @@ export type Propeller = { ], "args": [ { - "name": "targetTokenId", + "name": "toTokenNumber", "type": "u16" } ], @@ -3694,11 +4130,11 @@ export type Propeller = { "isSigner": false }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "docs": [ - "deserialized as a `TokenIdMap`. if it does exist, then engine should have called", + "deserialized as a `TokenNumberMap`. if it does exist, then engine should have called", "propeller_create_owner_token_accounts instead" ], "pda": { @@ -3908,17 +4344,25 @@ export type Propeller = { 32 ] } + }, + { + "name": "isPaused", + "type": "bool" } ] } }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "type": { "kind": "struct", "fields": [ { - "name": "outputTokenIndex", + "name": "bump", + "type": "u8" + }, + { + "name": "toTokenNumber", "type": "u16" }, { @@ -3934,14 +4378,10 @@ export type Propeller = { "type": "publicKey" }, { - "name": "poolIx", + "name": "toTokenStep", "type": { - "defined": "PoolInstruction" + "defined": "ToTokenStep" } - }, - { - "name": "bump", - "type": "u8" } ] } @@ -3956,19 +4396,23 @@ export type Propeller = { "type": "u8" }, { - "name": "nonce", - "type": "u32" + "name": "isPaused", + "type": "bool" }, { - "name": "admin", + "name": "governanceKey", "type": "publicKey" }, { - "name": "wormhole", + "name": "preparedGovernanceKey", "type": "publicKey" }, { - "name": "tokenBridge", + "name": "governanceTransitionTs", + "type": "i64" + }, + { + "name": "pauseKey", "type": "publicKey" }, { @@ -4489,7 +4933,7 @@ export type Propeller = { } }, { - "name": "PoolInstruction", + "name": "ToTokenStep", "type": { "kind": "enum", "variants": [ @@ -4505,20 +4949,6 @@ export type Propeller = { ] } }, - { - "name": "SwimPayloadVersion", - "type": { - "kind": "enum", - "variants": [ - { - "name": "V0" - }, - { - "name": "V1" - } - ] - } - }, { "name": "ConsistencyLevel", "type": { @@ -4685,7 +5115,7 @@ export type Propeller = { { "code": 6023, "name": "StaleFeed", - "msg": "Switchboard feed has not been updated in 5 minutes" + "msg": "Switchboard feed value is stale " }, { "code": 6024, @@ -4699,137 +5129,167 @@ export type Propeller = { }, { "code": 6026, - "name": "InvalidClaimData", - "msg": "Invalid claim data" + "name": "InvalidWormholeClaimAccount", + "msg": "Invalid Wormhole Claim Account" }, { "code": 6027, - "name": "ClaimNotClaimed", - "msg": "Claim Account not claimed" + "name": "InvalidClaimData", + "msg": "Invalid claim data" }, { "code": 6028, - "name": "InvalidPropellerAdmin", - "msg": "Invalid Propeller Admin" + "name": "ClaimNotClaimed", + "msg": "Claim Account not claimed" }, { "code": 6029, - "name": "InvalidTokenIdMapPool", - "msg": "Invalid Pool for Token Id Map" + "name": "InvalidPropellerGovernanceKey", + "msg": "Invalid Propeller GovernanceKey" }, { "code": 6030, - "name": "InvalidOutputTokenIndex", - "msg": "Invalid Output Token Index" + "name": "InvalidPropellerPauseKey", + "msg": "Invalid Propeller Pause Key" }, { "code": 6031, - "name": "InvalidTokenIdMapPoolTokenIndex", - "msg": "Invalid Pool Token Index for Token Id Map" + "name": "InvalidTokenNumberMapPool", + "msg": "Invalid Pool for Token Number Map" }, { "code": 6032, - "name": "InvalidTokenIdMapPoolTokenMint", - "msg": "Invalid Pool Token Mint for Token Id Map" + "name": "InvalidOutputTokenIndex", + "msg": "Invalid Output Token Index" }, { "code": 6033, - "name": "InvalidTokenIdMapPoolIx", - "msg": "Invalid Pool Ix for Token Id Map" + "name": "InvalidTokenNumberMapPoolTokenIndex", + "msg": "Invalid Pool Token Index for Token Number Map" }, { "code": 6034, + "name": "InvalidTokenNumberMapPoolTokenMint", + "msg": "Invalid Pool Token Mint for Token Number Map" + }, + { + "code": 6035, + "name": "InvalidTokenNumberMapToTokenStep", + "msg": "Invalid To Token Step for Token Number Map" + }, + { + "code": 6036, "name": "InvalidSwimPayloadGasKickstart", "msg": "Invalid Gas Kickstart parameter in Swim Payload" }, { - "code": 6035, + "code": 6037, "name": "InvalidMarginalPricePool", "msg": "Invalid Marginal Price Pool" }, { - "code": 6036, + "code": 6038, "name": "InvalidMarginalPricePoolAccounts", "msg": "Invalid Marginal Price Pool Accounts" }, { - "code": 6037, + "code": 6039, "name": "NotPropellerEnabled", "msg": "Propeller Not Enabled in payload" }, { - "code": 6038, + "code": 6040, "name": "InvalidRoutingContractAddress", "msg": "Invalid Routing Contract Address" }, { - "code": 6039, + "code": 6041, "name": "IntegerOverflow", "msg": "Integer Overflow" }, { - "code": 6040, + "code": 6042, "name": "ConversionError", "msg": "Conversion Error" }, { - "code": 6041, + "code": 6043, "name": "UnableToRetrieveSwimUsdMintDecimals", "msg": "Unable to retrieve SwimUSD mint decimals from marginal price pool information" }, { - "code": 6042, + "code": 6044, "name": "InvalidMetapoolTokenMint", "msg": "Invalid Metapool Token Mint. token_mint[0] should == swim_usd_mint" }, { - "code": 6043, + "code": 6045, "name": "UnableToDeserializeTokenAccount", "msg": "Unable to deserialize account info as token account" }, { - "code": 6044, + "code": 6046, "name": "InvalidTokenAccountDataLen", "msg": "Invalid token account data length. != 0 && != TokenAccount::LEN" }, { - "code": 6045, + "code": 6047, "name": "PayerInsufficientFundsForGasKickstart", "msg": "Payer has insufficient funds for gas kickstart" }, { - "code": 6046, + "code": 6048, "name": "IncorrectOwnerForCreateTokenAccount", "msg": "Owner of token account != swimPayload.owner" }, { - "code": 6047, - "name": "TokenIdMapExists", - "msg": "TokenIdMap exists. Please use the correct instruction" - }, - { - "code": 6048, - "name": "InvalidTokenIdMapAccountAddress", - "msg": "Invalid address for TokenIdMap account" + "code": 6049, + "name": "TokenNumberMapExists", + "msg": "TokenNumberMap exists. Please use the correct instruction" }, { - "code": 6049, + "code": 6050, "name": "InvalidSwimPayloadVersion", "msg": "Invalid Swim Payload version" }, { - "code": 6050, + "code": 6051, "name": "InvalidAggregator", "msg": "Invalid Aggregator" }, { - "code": 6051, + "code": 6052, "name": "InvalidFeeVault", "msg": "Invalid Fee Vault" - } - ] -}; - + }, + { + "code": 6053, + "name": "InvalidMemo", + "msg": "Invalid Memo" + }, + { + "code": 6054, + "name": "ToTokenNumberMismatch", + "msg": "ToTokenNumber does not match SwimPayload.to_tokenNumber" + }, + { + "code": 6055, + "name": "IsPaused", + "msg": "Routing Contract is paused" + }, + { + "code": 6056, + "name": "TargetChainIsPaused", + "msg": "Target Chain is paused" + }, + { + "code": 6057, + "name": "InvalidSwimPayloadMessagePayer", + "msg": "Invalid SwimPayloadMessagePayer" + } + ] +}; + export const IDL: Propeller = { "version": "0.1.0", "name": "propeller", @@ -4872,7 +5332,413 @@ export const IDL: Propeller = { } }, { - "name": "propellerRedeemer", + "name": "propellerRedeemer", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "redeemer" + } + ] + } + }, + { + "name": "propellerRedeemerEscrow", + "isMut": true, + "isSigner": false + }, + { + "name": "propellerFeeVault", + "isMut": true, + "isSigner": false + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "pauseKey", + "isMut": false, + "isSigner": true + }, + { + "name": "swimUsdMint", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "pool", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "two_pool" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "pool_token_mint_0" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "pool_token_mint_1" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "lp_mint" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "two_pool_program" + } + } + }, + { + "name": "poolTokenMint0", + "isMut": false, + "isSigner": false + }, + { + "name": "poolTokenMint1", + "isMut": false, + "isSigner": false + }, + { + "name": "lpMint", + "isMut": false, + "isSigner": false + }, + { + "name": "twoPoolProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "aggregator", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "associatedTokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "InitializeParams" + } + } + ] + }, + { + "name": "setPaused", + "accounts": [ + { + "name": "propeller", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "pauseKey", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "paused", + "type": "bool" + } + ] + }, + { + "name": "changePauseKey", + "accounts": [ + { + "name": "propeller", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "newPauseKey", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "prepareGovernanceTransition", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "enactGovernanceTransition", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, + { + "name": "createTokenNumberMap", + "accounts": [ + { + "name": "propeller", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "pool", + "isMut": false, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "two_pool" + }, + { + "kind": "arg", + "type": "publicKey", + "path": "pool.token_mint_keys [0]" + }, + { + "kind": "arg", + "type": "publicKey", + "path": "pool.token_mint_keys [1]" + }, + { + "kind": "arg", + "type": "publicKey", + "path": "pool.lp_mint_key" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "two_pool_program" + } + } + }, + { + "name": "tokenNumberMap", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", + "type": "string", + "value": "token_id" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller" + }, + { + "kind": "arg", + "type": "u16", + "path": "to_token_number" + } + ] + } + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "twoPoolProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "toTokenNumber", + "type": "u16" + }, + { + "name": "pool", + "type": "publicKey" + }, + { + "name": "poolTokenIndex", + "type": "u8" + }, + { + "name": "poolTokenMint", + "type": "publicKey" + }, + { + "name": "toTokenStep", + "type": { + "defined": "ToTokenStep" + } + } + ] + }, + { + "name": "updateTokenNumberMap", + "accounts": [ + { + "name": "propeller", "isMut": false, "isSigner": false, "pda": { @@ -4880,31 +5746,22 @@ export const IDL: Propeller = { { "kind": "const", "type": "string", - "value": "redeemer" + "value": "propeller" + }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" } ] } }, { - "name": "propellerRedeemerEscrow", - "isMut": true, - "isSigner": false - }, - { - "name": "propellerFeeVault", - "isMut": true, - "isSigner": false - }, - { - "name": "admin", + "name": "governanceKey", "isMut": false, "isSigner": true }, - { - "name": "swimUsdMint", - "isMut": false, - "isSigner": false - }, { "name": "payer", "isMut": true, @@ -4912,6 +5769,11 @@ export const IDL: Propeller = { }, { "name": "pool", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenNumberMap", "isMut": true, "isSigner": false, "pda": { @@ -4919,91 +5781,56 @@ export const IDL: Propeller = { { "kind": "const", "type": "string", - "value": "two_pool" + "value": "propeller" }, { - "kind": "account", - "type": "publicKey", - "account": "Mint", - "path": "pool_token_mint_0" + "kind": "const", + "type": "string", + "value": "token_id" }, { "kind": "account", "type": "publicKey", - "account": "Mint", - "path": "pool_token_mint_1" + "account": "Propeller", + "path": "propeller" }, { - "kind": "account", - "type": "publicKey", - "account": "Mint", - "path": "lp_mint" + "kind": "arg", + "type": "u16", + "path": "to_token_number" } - ], - "programId": { - "kind": "account", - "type": "publicKey", - "path": "two_pool_program" - } + ] } }, - { - "name": "poolTokenMint0", - "isMut": false, - "isSigner": false - }, - { - "name": "poolTokenMint1", - "isMut": false, - "isSigner": false - }, - { - "name": "lpMint", - "isMut": false, - "isSigner": false - }, { "name": "twoPoolProgram", "isMut": false, "isSigner": false - }, - { - "name": "aggregator", - "isMut": false, - "isSigner": false - }, + } + ], + "args": [ { - "name": "tokenProgram", - "isMut": false, - "isSigner": false + "name": "toTokenNumber", + "type": "u16" }, { - "name": "associatedTokenProgram", - "isMut": false, - "isSigner": false + "name": "poolTokenIndex", + "type": "u8" }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + "name": "poolTokenMint", + "type": "publicKey" }, { - "name": "rent", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "params", + "name": "toTokenStep", "type": { - "defined": "InitializeParams" + "defined": "ToTokenStep" } } ] }, { - "name": "createTokenIdMap", + "name": "closeTokenNumberMap", "accounts": [ { "name": "propeller", @@ -5026,7 +5853,7 @@ export const IDL: Propeller = { } }, { - "name": "admin", + "name": "governanceKey", "isMut": false, "isSigner": true }, @@ -5036,42 +5863,52 @@ export const IDL: Propeller = { "isSigner": true }, { - "name": "pool", - "isMut": false, + "name": "tokenNumberMap", + "isMut": true, "isSigner": false, "pda": { "seeds": [ { "kind": "const", "type": "string", - "value": "two_pool" + "value": "propeller" }, { - "kind": "arg", - "type": "publicKey", - "path": "pool" + "kind": "const", + "type": "string", + "value": "token_id" }, { - "kind": "arg", + "kind": "account", "type": "publicKey", - "path": "pool" + "account": "Propeller", + "path": "propeller" }, { "kind": "arg", - "type": "publicKey", - "path": "pool.lp_mint_key" + "type": "u16", + "path": "to_token_number" } - ], - "programId": { - "kind": "account", - "type": "publicKey", - "path": "two_pool_program" - } + ] } - }, + } + ], + "args": [ { - "name": "tokenIdMap", - "isMut": true, + "name": "toTokenNumber", + "type": "u16" + } + ] + }, + { + "name": "createTargetChainMap", + "docs": [ + "Target Chain Map *" + ], + "accounts": [ + { + "name": "propeller", + "isMut": false, "isSigner": false, "pda": { "seeds": [ @@ -5080,10 +5917,35 @@ export const IDL: Propeller = { "type": "string", "value": "propeller" }, + { + "kind": "account", + "type": "publicKey", + "account": "Propeller", + "path": "propeller.swim_usd_mint" + } + ] + } + }, + { + "name": "governanceKey", + "isMut": false, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "targetChainMap", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ { "kind": "const", "type": "string", - "value": "token_id" + "value": "propeller" }, { "kind": "account", @@ -5094,7 +5956,7 @@ export const IDL: Propeller = { { "kind": "arg", "type": "u16", - "path": "target_token_index" + "path": "target_chain" } ] } @@ -5103,40 +5965,26 @@ export const IDL: Propeller = { "name": "systemProgram", "isMut": false, "isSigner": false - }, - { - "name": "twoPoolProgram", - "isMut": false, - "isSigner": false } ], "args": [ { - "name": "targetTokenIndex", + "name": "targetChain", "type": "u16" }, { - "name": "pool", - "type": "publicKey" - }, - { - "name": "poolTokenIndex", - "type": "u8" - }, - { - "name": "poolTokenMint", - "type": "publicKey" - }, - { - "name": "poolIx", + "name": "targetAddress", "type": { - "defined": "PoolInstruction" + "array": [ + "u8", + 32 + ] } } ] }, { - "name": "createTargetChainMap", + "name": "updateTargetChainMap", "accounts": [ { "name": "propeller", @@ -5159,7 +6007,7 @@ export const IDL: Propeller = { } }, { - "name": "admin", + "name": "governanceKey", "isMut": false, "isSigner": true }, @@ -5186,26 +6034,18 @@ export const IDL: Propeller = { "path": "propeller" }, { - "kind": "arg", + "kind": "account", "type": "u16", - "path": "target_chain" + "account": "TargetChainMap", + "path": "target_chain_map.target_chain" } ] } - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false } ], "args": [ { - "name": "targetChain", - "type": "u16" - }, - { - "name": "targetAddress", + "name": "routingContract", "type": { "array": [ "u8", @@ -5216,7 +6056,7 @@ export const IDL: Propeller = { ] }, { - "name": "updateTargetChainMap", + "name": "targetChainMapSetPaused", "accounts": [ { "name": "propeller", @@ -5239,7 +6079,7 @@ export const IDL: Propeller = { } }, { - "name": "admin", + "name": "pauseKey", "isMut": false, "isSigner": true }, @@ -5273,22 +6113,12 @@ export const IDL: Propeller = { } ] } - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false } ], "args": [ { - "name": "routingContract", - "type": { - "array": [ - "u8", - 32 - ] - } + "name": "isPaused", + "type": "bool" } ] }, @@ -6238,6 +7068,10 @@ export const IDL: Propeller = { } ], "args": [ + { + "name": "nonce", + "type": "u32" + }, { "name": "amount", "type": "u64" @@ -6548,6 +7382,10 @@ export const IDL: Propeller = { } ], "args": [ + { + "name": "nonce", + "type": "u32" + }, { "name": "amount", "type": "u64" @@ -6628,8 +7466,7 @@ export const IDL: Propeller = { "programId": { "kind": "account", "type": "publicKey", - "account": "Propeller", - "path": "propeller" + "path": "token_bridge" } } }, @@ -6699,13 +7536,53 @@ export const IDL: Propeller = { "isSigner": false, "docs": [ "this is \"to_fees\"", - "recipient of fees for executing complete transfer (e.g. relayer)" + "recipient of fees for executing complete transfer (e.g. relayer)", + "this is only used in `propellerCompleteNativeWithPayload`." ] }, + { + "name": "swimPayloadMessage", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", + "type": "string", + "value": "swim_payload" + }, + { + "kind": "account", + "type": "publicKey", + "path": "claim" + } + ] + } + }, { "name": "custody", "isMut": true, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "swim_usd_mint" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { "name": "swimUsdMint", @@ -6715,56 +7592,46 @@ export const IDL: Propeller = { { "name": "custodySigner", "isMut": false, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "custody_signer" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { - "name": "rent", + "name": "wormhole", "isMut": false, "isSigner": false }, { - "name": "systemProgram", + "name": "tokenProgram", "isMut": false, "isSigner": false }, { - "name": "wormhole", + "name": "tokenBridge", "isMut": false, "isSigner": false }, { - "name": "tokenProgram", + "name": "systemProgram", "isMut": false, "isSigner": false }, { - "name": "tokenBridge", + "name": "rent", "isMut": false, "isSigner": false - }, - { - "name": "swimPayloadMessage", - "isMut": true, - "isSigner": false, - "pda": { - "seeds": [ - { - "kind": "const", - "type": "string", - "value": "propeller" - }, - { - "kind": "const", - "type": "string", - "value": "swim_payload" - }, - { - "kind": "account", - "type": "publicKey", - "path": "claim" - } - ] - } } ], "args": [] @@ -6915,7 +7782,7 @@ export const IDL: Propeller = { "isSigner": false }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "pda": { @@ -6939,7 +7806,7 @@ export const IDL: Propeller = { { "kind": "arg", "type": "u16", - "path": "target_token_id" + "path": "to_token_number" } ] } @@ -7034,7 +7901,7 @@ export const IDL: Propeller = { ], "args": [ { - "name": "targetTokenId", + "name": "toTokenNumber", "type": "u16" }, { @@ -7090,8 +7957,7 @@ export const IDL: Propeller = { "programId": { "kind": "account", "type": "publicKey", - "account": "Propeller", - "path": "propeller" + "path": "token_bridge" } } }, @@ -7161,13 +8027,53 @@ export const IDL: Propeller = { "isSigner": false, "docs": [ "this is \"to_fees\"", - "recipient of fees for executing complete transfer (e.g. relayer)" + "recipient of fees for executing complete transfer (e.g. relayer)", + "this is only used in `propellerCompleteNativeWithPayload`." ] }, + { + "name": "swimPayloadMessage", + "isMut": true, + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "propeller" + }, + { + "kind": "const", + "type": "string", + "value": "swim_payload" + }, + { + "kind": "account", + "type": "publicKey", + "path": "claim" + } + ] + } + }, { "name": "custody", "isMut": true, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "account", + "type": "publicKey", + "account": "Mint", + "path": "swim_usd_mint" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { "name": "swimUsdMint", @@ -7177,56 +8083,46 @@ export const IDL: Propeller = { { "name": "custodySigner", "isMut": false, - "isSigner": false + "isSigner": false, + "pda": { + "seeds": [ + { + "kind": "const", + "type": "string", + "value": "custody_signer" + } + ], + "programId": { + "kind": "account", + "type": "publicKey", + "path": "token_bridge" + } + } }, { - "name": "rent", + "name": "wormhole", "isMut": false, "isSigner": false }, { - "name": "systemProgram", + "name": "tokenProgram", "isMut": false, "isSigner": false }, { - "name": "wormhole", + "name": "tokenBridge", "isMut": false, "isSigner": false }, { - "name": "tokenProgram", + "name": "systemProgram", "isMut": false, "isSigner": false }, { - "name": "tokenBridge", + "name": "rent", "isMut": false, "isSigner": false - }, - { - "name": "swimPayloadMessage", - "isMut": true, - "isSigner": false, - "pda": { - "seeds": [ - { - "kind": "const", - "type": "string", - "value": "propeller" - }, - { - "kind": "const", - "type": "string", - "value": "swim_payload" - }, - { - "kind": "account", - "type": "publicKey", - "path": "claim" - } - ] - } } ] }, @@ -7490,7 +8386,7 @@ export const IDL: Propeller = { } }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "pda": { @@ -7830,7 +8726,7 @@ export const IDL: Propeller = { "isSigner": false }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "pda": { @@ -7854,7 +8750,7 @@ export const IDL: Propeller = { { "kind": "arg", "type": "u16", - "path": "target_token_id" + "path": "to_token_number" } ] } @@ -8070,7 +8966,7 @@ export const IDL: Propeller = { ], "args": [ { - "name": "targetTokenId", + "name": "toTokenNumber", "type": "u16" } ], @@ -8526,11 +9422,11 @@ export const IDL: Propeller = { "isSigner": false }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "isMut": false, "isSigner": false, "docs": [ - "deserialized as a `TokenIdMap`. if it does exist, then engine should have called", + "deserialized as a `TokenNumberMap`. if it does exist, then engine should have called", "propeller_create_owner_token_accounts instead" ], "pda": { @@ -8740,17 +9636,25 @@ export const IDL: Propeller = { 32 ] } + }, + { + "name": "isPaused", + "type": "bool" } ] } }, { - "name": "tokenIdMap", + "name": "tokenNumberMap", "type": { "kind": "struct", "fields": [ { - "name": "outputTokenIndex", + "name": "bump", + "type": "u8" + }, + { + "name": "toTokenNumber", "type": "u16" }, { @@ -8766,14 +9670,10 @@ export const IDL: Propeller = { "type": "publicKey" }, { - "name": "poolIx", + "name": "toTokenStep", "type": { - "defined": "PoolInstruction" + "defined": "ToTokenStep" } - }, - { - "name": "bump", - "type": "u8" } ] } @@ -8788,19 +9688,23 @@ export const IDL: Propeller = { "type": "u8" }, { - "name": "nonce", - "type": "u32" + "name": "isPaused", + "type": "bool" }, { - "name": "admin", + "name": "governanceKey", "type": "publicKey" }, { - "name": "wormhole", + "name": "preparedGovernanceKey", "type": "publicKey" }, { - "name": "tokenBridge", + "name": "governanceTransitionTs", + "type": "i64" + }, + { + "name": "pauseKey", "type": "publicKey" }, { @@ -9321,7 +10225,7 @@ export const IDL: Propeller = { } }, { - "name": "PoolInstruction", + "name": "ToTokenStep", "type": { "kind": "enum", "variants": [ @@ -9337,20 +10241,6 @@ export const IDL: Propeller = { ] } }, - { - "name": "SwimPayloadVersion", - "type": { - "kind": "enum", - "variants": [ - { - "name": "V0" - }, - { - "name": "V1" - } - ] - } - }, { "name": "ConsistencyLevel", "type": { @@ -9517,7 +10407,7 @@ export const IDL: Propeller = { { "code": 6023, "name": "StaleFeed", - "msg": "Switchboard feed has not been updated in 5 minutes" + "msg": "Switchboard feed value is stale " }, { "code": 6024, @@ -9531,133 +10421,163 @@ export const IDL: Propeller = { }, { "code": 6026, - "name": "InvalidClaimData", - "msg": "Invalid claim data" + "name": "InvalidWormholeClaimAccount", + "msg": "Invalid Wormhole Claim Account" }, { "code": 6027, - "name": "ClaimNotClaimed", - "msg": "Claim Account not claimed" + "name": "InvalidClaimData", + "msg": "Invalid claim data" }, { "code": 6028, - "name": "InvalidPropellerAdmin", - "msg": "Invalid Propeller Admin" + "name": "ClaimNotClaimed", + "msg": "Claim Account not claimed" }, { "code": 6029, - "name": "InvalidTokenIdMapPool", - "msg": "Invalid Pool for Token Id Map" + "name": "InvalidPropellerGovernanceKey", + "msg": "Invalid Propeller GovernanceKey" }, { "code": 6030, - "name": "InvalidOutputTokenIndex", - "msg": "Invalid Output Token Index" + "name": "InvalidPropellerPauseKey", + "msg": "Invalid Propeller Pause Key" }, { "code": 6031, - "name": "InvalidTokenIdMapPoolTokenIndex", - "msg": "Invalid Pool Token Index for Token Id Map" + "name": "InvalidTokenNumberMapPool", + "msg": "Invalid Pool for Token Number Map" }, { "code": 6032, - "name": "InvalidTokenIdMapPoolTokenMint", - "msg": "Invalid Pool Token Mint for Token Id Map" + "name": "InvalidOutputTokenIndex", + "msg": "Invalid Output Token Index" }, { "code": 6033, - "name": "InvalidTokenIdMapPoolIx", - "msg": "Invalid Pool Ix for Token Id Map" + "name": "InvalidTokenNumberMapPoolTokenIndex", + "msg": "Invalid Pool Token Index for Token Number Map" }, { "code": 6034, + "name": "InvalidTokenNumberMapPoolTokenMint", + "msg": "Invalid Pool Token Mint for Token Number Map" + }, + { + "code": 6035, + "name": "InvalidTokenNumberMapToTokenStep", + "msg": "Invalid To Token Step for Token Number Map" + }, + { + "code": 6036, "name": "InvalidSwimPayloadGasKickstart", "msg": "Invalid Gas Kickstart parameter in Swim Payload" }, { - "code": 6035, + "code": 6037, "name": "InvalidMarginalPricePool", "msg": "Invalid Marginal Price Pool" }, { - "code": 6036, + "code": 6038, "name": "InvalidMarginalPricePoolAccounts", "msg": "Invalid Marginal Price Pool Accounts" }, { - "code": 6037, + "code": 6039, "name": "NotPropellerEnabled", "msg": "Propeller Not Enabled in payload" }, { - "code": 6038, + "code": 6040, "name": "InvalidRoutingContractAddress", "msg": "Invalid Routing Contract Address" }, { - "code": 6039, + "code": 6041, "name": "IntegerOverflow", "msg": "Integer Overflow" }, { - "code": 6040, + "code": 6042, "name": "ConversionError", "msg": "Conversion Error" }, { - "code": 6041, + "code": 6043, "name": "UnableToRetrieveSwimUsdMintDecimals", "msg": "Unable to retrieve SwimUSD mint decimals from marginal price pool information" }, { - "code": 6042, + "code": 6044, "name": "InvalidMetapoolTokenMint", "msg": "Invalid Metapool Token Mint. token_mint[0] should == swim_usd_mint" }, { - "code": 6043, + "code": 6045, "name": "UnableToDeserializeTokenAccount", "msg": "Unable to deserialize account info as token account" }, { - "code": 6044, + "code": 6046, "name": "InvalidTokenAccountDataLen", "msg": "Invalid token account data length. != 0 && != TokenAccount::LEN" }, { - "code": 6045, + "code": 6047, "name": "PayerInsufficientFundsForGasKickstart", "msg": "Payer has insufficient funds for gas kickstart" }, { - "code": 6046, + "code": 6048, "name": "IncorrectOwnerForCreateTokenAccount", "msg": "Owner of token account != swimPayload.owner" }, { - "code": 6047, - "name": "TokenIdMapExists", - "msg": "TokenIdMap exists. Please use the correct instruction" - }, - { - "code": 6048, - "name": "InvalidTokenIdMapAccountAddress", - "msg": "Invalid address for TokenIdMap account" + "code": 6049, + "name": "TokenNumberMapExists", + "msg": "TokenNumberMap exists. Please use the correct instruction" }, { - "code": 6049, + "code": 6050, "name": "InvalidSwimPayloadVersion", "msg": "Invalid Swim Payload version" }, { - "code": 6050, + "code": 6051, "name": "InvalidAggregator", "msg": "Invalid Aggregator" }, { - "code": 6051, + "code": 6052, "name": "InvalidFeeVault", "msg": "Invalid Fee Vault" + }, + { + "code": 6053, + "name": "InvalidMemo", + "msg": "Invalid Memo" + }, + { + "code": 6054, + "name": "ToTokenNumberMismatch", + "msg": "ToTokenNumber does not match SwimPayload.to_tokenNumber" + }, + { + "code": 6055, + "name": "IsPaused", + "msg": "Routing Contract is paused" + }, + { + "code": 6056, + "name": "TargetChainIsPaused", + "msg": "Target Chain is paused" + }, + { + "code": 6057, + "name": "InvalidSwimPayloadMessagePayer", + "msg": "Invalid SwimPayloadMessagePayer" } ] };