diff --git a/programs/svm-spoke/src/error.rs b/programs/svm-spoke/src/error.rs index 02fac19f7..acf794bc9 100644 --- a/programs/svm-spoke/src/error.rs +++ b/programs/svm-spoke/src/error.rs @@ -7,7 +7,7 @@ pub enum CommonError { DisabledRoute, #[msg("Invalid quote timestamp!")] InvalidQuoteTimestamp, - #[msg("Invalid fill deadline!")] + #[msg("Ivalid fill deadline!")] InvalidFillDeadline, #[msg("Caller is not the exclusive relayer and exclusivity deadline has not passed!")] NotExclusiveRelayer, @@ -74,8 +74,6 @@ pub enum SvmError { InvalidProductionSeed, #[msg("Invalid remaining accounts for ATA creation!")] InvalidATACreationAccounts, - #[msg("Invalid delegate PDA!")] - InvalidDelegatePda, } // CCTP specific errors. diff --git a/programs/svm-spoke/src/instructions/deposit.rs b/programs/svm-spoke/src/instructions/deposit.rs index 55dae6e08..8f84baee1 100644 --- a/programs/svm-spoke/src/instructions/deposit.rs +++ b/programs/svm-spoke/src/instructions/deposit.rs @@ -11,9 +11,7 @@ use crate::{ error::{CommonError, SvmError}, event::FundsDeposited, state::{Route, State}, - utils::{ - derive_seed_hash, get_current_time, get_unsafe_deposit_id, transfer_from, DepositNowSeedData, DepositSeedData, - }, + utils::{get_current_time, get_unsafe_deposit_id, transfer_from}, }; #[event_cpi] @@ -25,7 +23,7 @@ use crate::{ output_token: Pubkey, input_amount: u64, output_amount: u64, - destination_chain_id: u64 + destination_chain_id: u64, )] pub struct Deposit<'info> { #[account(mut)] @@ -38,9 +36,6 @@ pub struct Deposit<'info> { )] pub state: Account<'info, State>, - /// CHECK: PDA derived with seeds ["delegate", seed_hash]; used as a CPI signer. - pub delegate: UncheckedAccount<'info>, - #[account( seeds = [b"route", input_token.as_ref(), state.seed.to_le_bytes().as_ref(), destination_chain_id.to_le_bytes().as_ref()], bump, @@ -88,14 +83,15 @@ pub fn _deposit( fill_deadline: u32, exclusivity_parameter: u32, message: Vec, - delegate_seed_hash: [u8; 32], ) -> Result<()> { let state = &mut ctx.accounts.state; + let current_time = get_current_time(state)?; if current_time.checked_sub(quote_timestamp).unwrap_or(u32::MAX) > state.deposit_quote_time_buffer { return err!(CommonError::InvalidQuoteTimestamp); } + if fill_deadline > current_time + state.fill_deadline_buffer { return err!(CommonError::InvalidFillDeadline); } @@ -105,20 +101,21 @@ pub fn _deposit( if exclusivity_deadline <= MAX_EXCLUSIVITY_PERIOD_SECONDS { exclusivity_deadline += current_time; } + if exclusive_relayer == Pubkey::default() { return err!(CommonError::InvalidExclusiveRelayer); } } - // Depositor must have delegated input_amount to the delegate PDA + // Depositor must have delegated input_amount to the state PDA. transfer_from( &ctx.accounts.depositor_token_account, &ctx.accounts.vault, input_amount, - &ctx.accounts.delegate, + state, + ctx.bumps.state, &ctx.accounts.mint, &ctx.accounts.token_program, - delegate_seed_hash, )?; let mut applied_deposit_id = deposit_id; @@ -162,22 +159,6 @@ pub fn deposit( exclusivity_parameter: u32, message: Vec, ) -> Result<()> { - let seed_hash = derive_seed_hash( - &(DepositSeedData { - depositor, - recipient, - input_token, - output_token, - input_amount, - output_amount, - destination_chain_id, - exclusive_relayer, - quote_timestamp, - fill_deadline, - exclusivity_parameter, - message: &message, - }), - ); _deposit( ctx, depositor, @@ -193,7 +174,6 @@ pub fn deposit( fill_deadline, exclusivity_parameter, message, - seed_hash, )?; Ok(()) @@ -215,22 +195,7 @@ pub fn deposit_now( ) -> Result<()> { let state = &mut ctx.accounts.state; let current_time = get_current_time(state)?; - let seed_hash = derive_seed_hash( - &(DepositNowSeedData { - depositor, - recipient, - input_token, - output_token, - input_amount, - output_amount, - destination_chain_id, - exclusive_relayer, - fill_deadline_offset, - exclusivity_period, - message: &message, - }), - ); - _deposit( + deposit( ctx, depositor, recipient, @@ -240,12 +205,10 @@ pub fn deposit_now( output_amount, destination_chain_id, exclusive_relayer, - ZERO_DEPOSIT_ID, // ZERO_DEPOSIT_ID informs internal function to use state.number_of_deposits as id. current_time, current_time + fill_deadline_offset, exclusivity_period, message, - seed_hash, )?; Ok(()) @@ -269,22 +232,6 @@ pub fn unsafe_deposit( ) -> Result<()> { // Calculate the unsafe deposit ID as a [u8; 32] let deposit_id = get_unsafe_deposit_id(ctx.accounts.signer.key(), depositor, deposit_nonce); - let seed_hash = derive_seed_hash( - &(DepositSeedData { - depositor, - recipient, - input_token, - output_token, - input_amount, - output_amount, - destination_chain_id, - exclusive_relayer, - quote_timestamp, - fill_deadline, - exclusivity_parameter, - message: &message, - }), - ); _deposit( ctx, depositor, @@ -300,7 +247,6 @@ pub fn unsafe_deposit( fill_deadline, exclusivity_parameter, message, - seed_hash, )?; Ok(()) diff --git a/programs/svm-spoke/src/instructions/fill.rs b/programs/svm-spoke/src/instructions/fill.rs index d8f965d81..248947388 100644 --- a/programs/svm-spoke/src/instructions/fill.rs +++ b/programs/svm-spoke/src/instructions/fill.rs @@ -11,7 +11,7 @@ use crate::{ error::{CommonError, SvmError}, event::{FillType, FilledRelay, RelayExecutionEventInfo}, state::{FillRelayParams, FillStatus, FillStatusAccount, State}, - utils::{derive_seed_hash, get_current_time, hash_non_empty_message, invoke_handler, transfer_from, FillSeedData}, + utils::{get_current_time, hash_non_empty_message, invoke_handler, transfer_from}, }; #[event_cpi] @@ -25,12 +25,13 @@ pub struct FillRelay<'info> { #[account(mut, seeds = [b"instruction_params", signer.key().as_ref()], bump, close = signer)] pub instruction_params: Option>, - #[account(seeds = [b"state", state.seed.to_le_bytes().as_ref()], bump)] + #[account( + seeds = [b"state", state.seed.to_le_bytes().as_ref()], + bump, + constraint = !state.paused_fills @ CommonError::FillsArePaused + )] pub state: Account<'info, State>, - /// CHECK: PDA derived with seeds ["delegate", seed_hash]; used as a CPI signer. - pub delegate: UncheckedAccount<'info>, - #[account( mint::token_program = token_program, address = relay_data @@ -80,15 +81,10 @@ pub struct FillRelay<'info> { pub fn fill_relay<'info>( ctx: Context<'_, '_, '_, 'info, FillRelay<'info>>, - relay_hash: [u8; 32], relay_data: Option, repayment_chain_id: Option, repayment_address: Option, ) -> Result<()> { - // This type of constraint normally would be checked in the context, but had to move it here in the handler to avoid - // exceeding maximum stack offset. - require!(!ctx.accounts.state.paused_fills, CommonError::FillsArePaused); - let FillRelayParams { relay_data, repayment_chain_id, repayment_address } = unwrap_fill_relay_params(relay_data, repayment_chain_id, repayment_address, &ctx.accounts.instruction_params); @@ -118,17 +114,15 @@ pub fn fill_relay<'info>( _ => FillType::FastFill, }; - let seed_hash = derive_seed_hash(&(FillSeedData { relay_hash, repayment_chain_id, repayment_address })); - - // Relayer must have delegated output_amount to the delegate PDA + // Relayer must have delegated output_amount to the state PDA transfer_from( &ctx.accounts.relayer_token_account, &ctx.accounts.recipient_token_account, relay_data.output_amount, - &ctx.accounts.delegate, + state, + ctx.bumps.state, &ctx.accounts.mint, &ctx.accounts.token_program, - seed_hash, )?; // Update the fill status to Filled, set the relayer and fill deadline diff --git a/programs/svm-spoke/src/lib.rs b/programs/svm-spoke/src/lib.rs index 9031ad26b..efcfc2edb 100644 --- a/programs/svm-spoke/src/lib.rs +++ b/programs/svm-spoke/src/lib.rs @@ -235,7 +235,6 @@ pub mod svm_spoke { /// Authority must be the state. /// - mint (Account): The mint account for the input token. /// - token_program (Interface): The token program. - /// - delegate (Account): The account used to delegate the input amount of the input token. /// /// ### Parameters /// - depositor: The account credited with the deposit. Can be different from the signer. @@ -412,10 +411,9 @@ pub mod svm_spoke { /// - token_program (Interface): The token program. /// - associated_token_program (Interface): The associated token program. /// - system_program (Interface): The system program. - /// - delegate (Account): The account used to delegate the output amount of the output token. /// /// ### Parameters: - /// - relay_hash: The hash identifying the deposit to be filled. Caller must pass this in. Computed as hash of + /// - _relay_hash: The hash identifying the deposit to be filled. Caller must pass this in. Computed as hash of /// the flattened relay_data & destination_chain_id. /// - relay_data: Struct containing all the data needed to identify the deposit to be filled. Should match /// all the same-named parameters emitted in the origin chain FundsDeposited event. @@ -442,12 +440,12 @@ pub mod svm_spoke { /// is passed, the caller must load them via the instruction_params account. pub fn fill_relay<'info>( ctx: Context<'_, '_, '_, 'info, FillRelay<'info>>, - relay_hash: [u8; 32], + _relay_hash: [u8; 32], relay_data: Option, repayment_chain_id: Option, repayment_address: Option, ) -> Result<()> { - instructions::fill_relay(ctx, relay_hash, relay_data, repayment_chain_id, repayment_address) + instructions::fill_relay(ctx, relay_data, repayment_chain_id, repayment_address) } /// Closes the FillStatusAccount PDA to reclaim relayer rent. diff --git a/programs/svm-spoke/src/utils/delegate_utils.rs b/programs/svm-spoke/src/utils/delegate_utils.rs deleted file mode 100644 index 550e09bfc..000000000 --- a/programs/svm-spoke/src/utils/delegate_utils.rs +++ /dev/null @@ -1,45 +0,0 @@ -use anchor_lang::{prelude::*, solana_program::keccak}; - -pub fn derive_seed_hash(seed: &T) -> [u8; 32] { - let mut data = Vec::new(); - AnchorSerialize::serialize(seed, &mut data).unwrap(); - keccak::hash(&data).to_bytes() -} - -#[derive(AnchorSerialize)] -pub struct DepositSeedData<'a> { - pub depositor: Pubkey, - pub recipient: Pubkey, - pub input_token: Pubkey, - pub output_token: Pubkey, - pub input_amount: u64, - pub output_amount: u64, - pub destination_chain_id: u64, - pub exclusive_relayer: Pubkey, - pub quote_timestamp: u32, - pub fill_deadline: u32, - pub exclusivity_parameter: u32, - pub message: &'a Vec, -} - -#[derive(AnchorSerialize)] -pub struct DepositNowSeedData<'a> { - pub depositor: Pubkey, - pub recipient: Pubkey, - pub input_token: Pubkey, - pub output_token: Pubkey, - pub input_amount: u64, - pub output_amount: u64, - pub destination_chain_id: u64, - pub exclusive_relayer: Pubkey, - pub fill_deadline_offset: u32, - pub exclusivity_period: u32, - pub message: &'a Vec, -} - -#[derive(AnchorSerialize)] -pub struct FillSeedData { - pub relay_hash: [u8; 32], - pub repayment_chain_id: u64, - pub repayment_address: Pubkey, -} diff --git a/programs/svm-spoke/src/utils/mod.rs b/programs/svm-spoke/src/utils/mod.rs index c2ed59ca1..4792201f2 100644 --- a/programs/svm-spoke/src/utils/mod.rs +++ b/programs/svm-spoke/src/utils/mod.rs @@ -1,6 +1,5 @@ pub mod bitmap_utils; pub mod cctp_utils; -pub mod delegate_utils; pub mod deposit_utils; pub mod merkle_proof_utils; pub mod message_utils; @@ -9,7 +8,6 @@ pub mod transfer_utils; pub use bitmap_utils::*; pub use cctp_utils::*; -pub use delegate_utils::*; pub use deposit_utils::*; pub use merkle_proof_utils::*; pub use message_utils::*; diff --git a/programs/svm-spoke/src/utils/transfer_utils.rs b/programs/svm-spoke/src/utils/transfer_utils.rs index 4e1b24a88..c8c9f5c76 100644 --- a/programs/svm-spoke/src/utils/transfer_utils.rs +++ b/programs/svm-spoke/src/utils/transfer_utils.rs @@ -1,28 +1,28 @@ -use crate::{error::SvmError, program::SvmSpoke}; use anchor_lang::prelude::*; use anchor_spl::token_interface::{transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked}; +use crate::State; + pub fn transfer_from<'info>( from: &InterfaceAccount<'info, TokenAccount>, to: &InterfaceAccount<'info, TokenAccount>, amount: u64, - delegate: &UncheckedAccount<'info>, + state: &Account<'info, State>, + state_bump: u8, mint: &InterfaceAccount<'info, Mint>, token_program: &Interface<'info, TokenInterface>, - delegate_seed_hash: [u8; 32], ) -> Result<()> { - let (pda, bump) = Pubkey::find_program_address(&[b"delegate", &delegate_seed_hash], &SvmSpoke::id()); - if pda != delegate.key() { - return err!(SvmError::InvalidDelegatePda); - } - let seeds: &[&[u8]] = &[b"delegate".as_ref(), &delegate_seed_hash, &[bump]]; - let signer_seeds: &[&[&[u8]]] = &[seeds]; let transfer_accounts = TransferChecked { from: from.to_account_info(), mint: mint.to_account_info(), to: to.to_account_info(), - authority: delegate.to_account_info(), + authority: state.to_account_info(), }; + + let state_seed_bytes = state.seed.to_le_bytes(); + let seeds = &[b"state", state_seed_bytes.as_ref(), &[state_bump]]; + let signer_seeds = &[&seeds[..]]; + let cpi_context = CpiContext::new_with_signer(token_program.to_account_info(), transfer_accounts, signer_seeds); transfer_checked(cpi_context, amount, mint.decimals) diff --git a/scripts/svm/fakeFillWithRandomDistribution.ts b/scripts/svm/fakeFillWithRandomDistribution.ts index a734283e2..4d1b433c0 100644 --- a/scripts/svm/fakeFillWithRandomDistribution.ts +++ b/scripts/svm/fakeFillWithRandomDistribution.ts @@ -18,7 +18,6 @@ import { AcrossPlusMessageCoder, MulticallHandlerCoder, calculateRelayHashUint8Array, - getFillRelayDelegatePda, getSpokePoolProgram, loadFillRelayParams, sendTransactionWithLookupTable, @@ -199,7 +198,6 @@ async function fillV3RelayToRandom(): Promise { const fillAccounts = { state: statePda, signer: signer.publicKey, - delegate: getFillRelayDelegatePda(relayHashUint8Array, repaymentChain, repaymentAddress, program.programId).pda, instructionParams, mint: outputToken, relayerTokenAccount, diff --git a/scripts/svm/simpleFill.ts b/scripts/svm/simpleFill.ts index fb78ef98b..b301fc28b 100644 --- a/scripts/svm/simpleFill.ts +++ b/scripts/svm/simpleFill.ts @@ -14,12 +14,7 @@ import { import { PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } from "@solana/web3.js"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; -import { - calculateRelayHashUint8Array, - getFillRelayDelegatePda, - getSpokePoolProgram, - intToU8Array32, -} from "../../src/svm/web3-v1"; +import { calculateRelayHashUint8Array, getSpokePoolProgram, intToU8Array32 } from "../../src/svm/web3-v1"; import { FillDataValues } from "../../src/types/svm"; // Set up the provider @@ -166,7 +161,6 @@ async function fillRelay(): Promise { const fillAccounts = { state: statePda, signer: signer.publicKey, - delegate: getFillRelayDelegatePda(relayHashUint8Array, chainId, signer.publicKey, program.programId).pda, instructionParams: program.programId, mint: outputToken, relayerTokenAccount: relayerTokenAccount, diff --git a/src/svm/web3-v1/helpers.ts b/src/svm/web3-v1/helpers.ts index 0f61fe035..7b737280d 100644 --- a/src/svm/web3-v1/helpers.ts +++ b/src/svm/web3-v1/helpers.ts @@ -1,10 +1,6 @@ -import { AnchorProvider, BN } from "@coral-xyz/anchor"; +import { AnchorProvider } from "@coral-xyz/anchor"; import { BigNumber } from "@ethersproject/bignumber"; import { ethers } from "ethers"; -import { DepositData } from "../../types/svm"; -import { PublicKey } from "@solana/web3.js"; -import { serialize } from "borsh"; -import { keccak256 } from "ethers/lib/utils"; /** * Returns the chainId for a given solana cluster. @@ -24,278 +20,3 @@ export const isSolanaDevnet = (provider: AnchorProvider): boolean => { else if (solanaRpcEndpoint.includes("mainnet")) return false; else throw new Error(`Unsupported solanaCluster endpoint: ${solanaRpcEndpoint}`); }; - -/** - * Generic helper: serialize + keccak256 → 32‑byte Uint8Array - */ -function deriveSeedHash(schema: Map, seedObj: T): Uint8Array { - const serialized = serialize(schema, seedObj); - const hashHex = keccak256(serialized); - return Buffer.from(hashHex.slice(2), "hex"); -} - -/** - * “Absolute‐deadline” deposit data - */ -export class DepositSeedData { - depositor!: Uint8Array; - recipient!: Uint8Array; - inputToken!: Uint8Array; - outputToken!: Uint8Array; - inputAmount!: BN; - outputAmount!: BN; - destinationChainId!: BN; - exclusiveRelayer!: Uint8Array; - quoteTimestamp!: BN; - fillDeadline!: BN; - exclusivityParameter!: BN; - message!: Uint8Array; - - constructor(fields: { - depositor: Uint8Array; - recipient: Uint8Array; - inputToken: Uint8Array; - outputToken: Uint8Array; - inputAmount: BN; - outputAmount: BN; - destinationChainId: BN; - exclusiveRelayer: Uint8Array; - quoteTimestamp: BN; - fillDeadline: BN; - exclusivityParameter: BN; - message: Uint8Array; - }) { - Object.assign(this, fields); - } -} - -const depositSeedSchema = new Map([ - [ - DepositSeedData, - { - kind: "struct", - fields: [ - ["depositor", [32]], - ["recipient", [32]], - ["inputToken", [32]], - ["outputToken", [32]], - ["inputAmount", "u64"], - ["outputAmount", "u64"], - ["destinationChainId", "u64"], - ["exclusiveRelayer", [32]], - ["quoteTimestamp", "u32"], - ["fillDeadline", "u32"], - ["exclusivityParameter", "u32"], - ["message", ["u8"]], - ], - }, - ], -]); - -/** - * Hash for the standard `deposit(...)` flow - */ -export function getDepositSeedHash(depositData: { - depositor: PublicKey; - recipient: PublicKey; - inputToken: PublicKey; - outputToken: PublicKey; - inputAmount: BN; - outputAmount: BN; - destinationChainId: BN; - exclusiveRelayer: PublicKey; - quoteTimestamp: BN; - fillDeadline: BN; - exclusivityParameter: BN; - message: Uint8Array; -}): Uint8Array { - const ds = new DepositSeedData({ - depositor: depositData.depositor.toBuffer(), - recipient: depositData.recipient.toBuffer(), - inputToken: depositData.inputToken.toBuffer(), - outputToken: depositData.outputToken.toBuffer(), - inputAmount: depositData.inputAmount, - outputAmount: depositData.outputAmount, - destinationChainId: depositData.destinationChainId, - exclusiveRelayer: depositData.exclusiveRelayer.toBuffer(), - quoteTimestamp: depositData.quoteTimestamp, - fillDeadline: depositData.fillDeadline, - exclusivityParameter: depositData.exclusivityParameter, - message: depositData.message, - }); - - return deriveSeedHash(depositSeedSchema, ds); -} - -/** - * Returns the delegate PDA for `deposit(...)` - */ -export function getDepositPda(depositData: Parameters[0], programId: PublicKey): PublicKey { - const seedHash = getDepositSeedHash(depositData); - const [pda] = PublicKey.findProgramAddressSync([Buffer.from("delegate"), seedHash], programId); - return pda; -} - -/** - * “Offset/now” deposit data - */ -export class DepositNowSeedData { - depositor!: Uint8Array; - recipient!: Uint8Array; - inputToken!: Uint8Array; - outputToken!: Uint8Array; - inputAmount!: BN; - outputAmount!: BN; - destinationChainId!: BN; - exclusiveRelayer!: Uint8Array; - fillDeadlineOffset!: BN; - exclusivityPeriod!: BN; - message!: Uint8Array; - - constructor(fields: { - depositor: Uint8Array; - recipient: Uint8Array; - inputToken: Uint8Array; - outputToken: Uint8Array; - inputAmount: BN; - outputAmount: BN; - destinationChainId: BN; - exclusiveRelayer: Uint8Array; - fillDeadlineOffset: BN; - exclusivityPeriod: BN; - message: Uint8Array; - }) { - Object.assign(this, fields); - } -} - -const depositNowSeedSchema = new Map([ - [ - DepositNowSeedData, - { - kind: "struct", - fields: [ - ["depositor", [32]], - ["recipient", [32]], - ["inputToken", [32]], - ["outputToken", [32]], - ["inputAmount", "u64"], - ["outputAmount", "u64"], - ["destinationChainId", "u64"], - ["exclusiveRelayer", [32]], - ["fillDeadlineOffset", "u32"], - ["exclusivityPeriod", "u32"], - ["message", ["u8"]], - ], - }, - ], -]); - -/** - * Hash for the `deposit_now(...)` flow - */ -export function getDepositNowSeedHash(depositData: { - depositor: PublicKey; - recipient: PublicKey; - inputToken: PublicKey; - outputToken: PublicKey; - inputAmount: BN; - outputAmount: BN; - destinationChainId: BN; - exclusiveRelayer: PublicKey; - fillDeadlineOffset: BN; - exclusivityPeriod: BN; - message: Uint8Array; -}): Uint8Array { - const dns = new DepositNowSeedData({ - depositor: depositData.depositor.toBuffer(), - recipient: depositData.recipient.toBuffer(), - inputToken: depositData.inputToken.toBuffer(), - outputToken: depositData.outputToken.toBuffer(), - inputAmount: depositData.inputAmount, - outputAmount: depositData.outputAmount, - destinationChainId: depositData.destinationChainId, - exclusiveRelayer: depositData.exclusiveRelayer.toBuffer(), - fillDeadlineOffset: depositData.fillDeadlineOffset, - exclusivityPeriod: depositData.exclusivityPeriod, - message: depositData.message, - }); - - return deriveSeedHash(depositNowSeedSchema, dns); -} - -/** - * Returns the delegate PDA for `deposit_now(...)` - */ -export function getDepositNowPda( - depositData: Parameters[0], - programId: PublicKey -): PublicKey { - const seedHash = getDepositNowSeedHash(depositData); - const [pda] = PublicKey.findProgramAddressSync([Buffer.from("delegate"), seedHash], programId); - return pda; -} - -/** - * Fill Delegate Seed Data - */ -class FillDelegateSeedData { - relayHash: Uint8Array; - repaymentChainId: BN; - repaymentAddress: Uint8Array; - constructor(fields: { relayHash: Uint8Array; repaymentChainId: BN; repaymentAddress: Uint8Array }) { - this.relayHash = fields.relayHash; - this.repaymentChainId = fields.repaymentChainId; - this.repaymentAddress = fields.repaymentAddress; - } -} - -/** - * Borsh schema for FillDelegateSeedData - */ -const fillDelegateSeedSchema = new Map([ - [ - FillDelegateSeedData, - { - kind: "struct", - fields: [ - ["relayHash", [32]], - ["repaymentChainId", "u64"], - ["repaymentAddress", [32]], - ], - }, - ], -]); - -/** - * Returns the fill delegate seed hash. - */ - -export function getFillRelayDelegateSeedHash( - relayHash: Uint8Array, - repaymentChainId: BN, - repaymentAddress: PublicKey -): Uint8Array { - const ds = new FillDelegateSeedData({ - relayHash, - repaymentChainId, - repaymentAddress: repaymentAddress.toBuffer(), - }); - - return deriveSeedHash(fillDelegateSeedSchema, ds); -} - -/** - * Returns the fill delegate PDA. - */ -export function getFillRelayDelegatePda( - relayHash: Uint8Array, - repaymentChainId: BN, - repaymentAddress: PublicKey, - programId: PublicKey -): { seedHash: Uint8Array; pda: PublicKey } { - const seedHash = getFillRelayDelegateSeedHash(relayHash, repaymentChainId, repaymentAddress); - const [pda] = PublicKey.findProgramAddressSync([Buffer.from("delegate"), seedHash], programId); - - return { seedHash, pda }; -} diff --git a/test/svm/SvmSpoke.Deposit.ts b/test/svm/SvmSpoke.Deposit.ts index bfb258298..2290ff84a 100644 --- a/test/svm/SvmSpoke.Deposit.ts +++ b/test/svm/SvmSpoke.Deposit.ts @@ -28,47 +28,22 @@ import { Keypair, PublicKey, Transaction, sendAndConfirmTransaction } from "@sol import { BigNumber, ethers } from "ethers"; import { SvmSpokeClient } from "../../src/svm"; import { DepositInput } from "../../src/svm/clients/SvmSpoke"; -import { - getDepositNowPda, - getDepositNowSeedHash, - getDepositPda, - getDepositSeedHash, - intToU8Array32, - readEventsUntilFound, - u8Array32ToBigNumber, - u8Array32ToInt, -} from "../../src/svm/web3-v1"; -import { DepositData, DepositDataValues } from "../../src/types/svm"; +import { intToU8Array32, readEventsUntilFound, u8Array32ToBigNumber, u8Array32ToInt } from "../../src/svm/web3-v1"; +import { DepositDataValues } from "../../src/types/svm"; import { MAX_EXCLUSIVITY_OFFSET_SECONDS } from "../../test-utils"; import { common } from "./SvmSpoke.common"; import { createDefaultSolanaClient, createDefaultTransaction, signAndSendTransaction } from "./utils"; -const { - createRoutePda, - getVaultAta, - assertSE, - assert, - getCurrentTime, - depositQuoteTimeBuffer, - fillDeadlineBuffer, - provider, - connection, - program, - owner, - seedBalance, - initializeState, - depositData, -} = common; +const { provider, connection, program, owner, seedBalance, initializeState, depositData } = common; +const { createRoutePda, getVaultAta, assertSE, assert, getCurrentTime, depositQuoteTimeBuffer, fillDeadlineBuffer } = + common; const maxExclusivityOffsetSeconds = new BN(MAX_EXCLUSIVITY_OFFSET_SECONDS); // 1 year in seconds -type DepositDataSeed = Parameters[0]; -type DepositNowDataSeed = Parameters[0]; - describe("svm_spoke.deposit", () => { anchor.setProvider(provider); const depositor = Keypair.generate(); - const { payer } = anchor.AnchorProvider.env().wallet as anchor.Wallet; + const payer = (anchor.AnchorProvider.env().wallet as anchor.Wallet).payer; const tokenDecimals = 6; let state: PublicKey, inputToken: PublicKey, depositorTA: PublicKey, vault: PublicKey, tokenProgram: PublicKey; @@ -77,7 +52,6 @@ describe("svm_spoke.deposit", () => { // Re-used between tests to simplify props. type DepositAccounts = { state: PublicKey; - delegate: PublicKey; route: PublicKey; signer: PublicKey; depositorTokenAccount: PublicKey; @@ -133,7 +107,6 @@ describe("svm_spoke.deposit", () => { depositAccounts = { state, - delegate: getDepositPda(depositData as DepositDataSeed, program.programId), route, signer: depositor.publicKey, depositorTokenAccount: depositorTA, @@ -145,17 +118,14 @@ describe("svm_spoke.deposit", () => { }; const approvedDeposit = async ( - depositData: DepositData, + depositDataValues: DepositDataValues, calledDepositAccounts: DepositAccounts = depositAccounts ) => { - const delegatePda = getDepositPda(depositData as DepositDataSeed, program.programId); - calledDepositAccounts.delegate = delegatePda; - - // Delegate delegate PDA to pull depositor tokens. + // Delegate state PDA to pull depositor tokens. const approveIx = await createApproveCheckedInstruction( calledDepositAccounts.depositorTokenAccount, calledDepositAccounts.mint, - delegatePda, + calledDepositAccounts.state, depositor.publicKey, BigInt(depositData.inputAmount.toString()), tokenDecimals, @@ -163,24 +133,12 @@ describe("svm_spoke.deposit", () => { tokenProgram ); const depositIx = await program.methods - .deposit( - depositData.depositor!, - depositData.recipient, - depositData.inputToken!, - depositData.outputToken, - depositData.inputAmount, - depositData.outputAmount, - depositData.destinationChainId, - depositData.exclusiveRelayer, - depositData.quoteTimestamp.toNumber(), - depositData.fillDeadline.toNumber(), - depositData.exclusivityParameter.toNumber(), - depositData.message - ) + .deposit(...depositDataValues) .accounts(calledDepositAccounts) .instruction(); const depositTx = new Transaction().add(approveIx, depositIx); - return sendAndConfirmTransaction(connection, depositTx, [payer, depositor]); + const tx = await sendAndConfirmTransaction(connection, depositTx, [payer, depositor]); + return tx; }; beforeEach(async () => { @@ -188,6 +146,7 @@ describe("svm_spoke.deposit", () => { tokenProgram = TOKEN_PROGRAM_ID; // Some tests might override this. await setupInputToken(); + await enableRoute(); }); it("Deposits tokens via deposit function and checks balances", async () => { @@ -196,7 +155,8 @@ describe("svm_spoke.deposit", () => { assertSE(vaultAccount.amount, "0", "Vault balance should be zero before the deposit"); // Execute the deposit call - await approvedDeposit(depositData); + let depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); // Verify tokens leave the depositor's account let depositorAccount = await getAccount(connection, depositorTA); @@ -214,7 +174,9 @@ describe("svm_spoke.deposit", () => { const secondInputAmount = new BN(300000); // Execute the second deposit call - await approvedDeposit({ ...depositData, inputAmount: secondInputAmount }); + + depositDataValues = Object.values({ ...depositData, inputAmount: secondInputAmount }) as DepositDataValues; + await approvedDeposit(depositDataValues); // Verify tokens leave the depositor's account again depositorAccount = await getAccount(connection, depositorTA); @@ -237,7 +199,8 @@ describe("svm_spoke.deposit", () => { depositData.inputAmount = depositData.inputAmount.add(new BN(69)); // Execute the first deposit call - const tx = await approvedDeposit(depositData); + let depositDataValues = Object.values(depositData) as DepositDataValues; + const tx = await approvedDeposit(depositDataValues); let events = await readEventsUntilFound(connection, tx, [program]); let event = events[0].data; // 0th event is the latest event @@ -251,8 +214,8 @@ describe("svm_spoke.deposit", () => { assertSE(u8Array32ToInt(event.depositId), 1, `depositId should recover to 1`); assertSE(u8Array32ToBigNumber(event.depositId), BigNumber.from(1), `depositId should recover to 1`); - // Execute the second deposit call - const tx2 = await approvedDeposit(depositData); + // Execute the second deposit_v3 call + const tx2 = await approvedDeposit(depositDataValues); events = await readEventsUntilFound(connection, tx2, [program]); event = events[0].data; // 0th event is the latest event. @@ -275,7 +238,8 @@ describe("svm_spoke.deposit", () => { depositData.fillDeadline = new BN(fillDeadline); depositData.quoteTimestamp = new BN(currentTime - 1); // 1 second before current time on the contract to reset. - const tx = await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + const tx = await approvedDeposit(depositDataValues); const events = await readEventsUntilFound(connection, tx, [program]); const event = events[0].data; // 0th event is the latest event. @@ -292,10 +256,11 @@ describe("svm_spoke.deposit", () => { depositAccounts.route = differentRoutePda; try { - await approvedDeposit({ + const depositDataValues = Object.values({ ...depositData, destinationChainId: differentChainId, - }); + }) as DepositDataValues; + await approvedDeposit(depositDataValues); assert.fail("Deposit should have failed for a route that is not initialized"); } catch (err: any) { assert.include(err.toString(), "AccountNotInitialized", "Expected AccountNotInitialized error"); @@ -310,7 +275,8 @@ describe("svm_spoke.deposit", () => { .rpc(); try { - await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); assert.fail("Deposit should have failed for a route that is explicitly disabled"); } catch (err: any) { assert.include(err.toString(), "DisabledRoute", "Expected DisabledRoute error"); @@ -326,7 +292,8 @@ describe("svm_spoke.deposit", () => { // Try to deposit. This should fail because deposits are paused. try { - await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); assert.fail("Should not be able to process deposit when deposits are paused"); } catch (err: any) { assert.include(err.toString(), "Error Code: DepositsArePaused", "Expected DepositsArePaused error"); @@ -340,7 +307,8 @@ describe("svm_spoke.deposit", () => { depositData.quoteTimestamp = futureQuoteTimestamp; try { - await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); assert.fail("Deposit should have failed due to InvalidQuoteTimestamp"); } catch (err: any) { assert.include(err.toString(), "Error Code: InvalidQuoteTimestamp", "Expected InvalidQuoteTimestamp error"); @@ -354,7 +322,8 @@ describe("svm_spoke.deposit", () => { depositData.quoteTimestamp = futureQuoteTimestamp; try { - await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); assert.fail("Deposit should have failed due to InvalidQuoteTimestamp"); } catch (err: any) { assert.include(err.toString(), "Error Code: InvalidQuoteTimestamp", "Expected InvalidQuoteTimestamp error"); @@ -370,7 +339,8 @@ describe("svm_spoke.deposit", () => { depositData.quoteTimestamp = new BN(currentTime); try { - await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); assert.fail("Deposit should have failed due to InvalidFillDeadline (future deadline)"); } catch (err: any) { assert.include(err.toString(), "InvalidFillDeadline", "Expected InvalidFillDeadline error for future deadline"); @@ -390,7 +360,8 @@ describe("svm_spoke.deposit", () => { const malformedDepositData = { ...depositData, inputToken: firstInputToken }; const malformedDepositAccounts = { ...depositAccounts, route: firstDepositAccounts.route }; try { - await approvedDeposit(malformedDepositData, malformedDepositAccounts); + const depositDataValues = Object.values(malformedDepositData) as DepositDataValues; + await approvedDeposit(depositDataValues, malformedDepositAccounts); assert.fail("Should not be able to process deposit for inconsistent mint"); } catch (err: any) { assert.include(err.toString(), "Error Code: InvalidMint", "Expected InvalidMint error"); @@ -423,7 +394,6 @@ describe("svm_spoke.deposit", () => { const fakeDepositAccounts = { state: fakeState.state, - delegate: getDepositPda(depositData as DepositDataSeed, program.programId), route: fakeRoutePda, signer: depositor.publicKey, depositorTokenAccount: depositorTA, @@ -434,13 +404,11 @@ describe("svm_spoke.deposit", () => { }; // Deposit with the fake state and route PDA should succeed. - const tx = await approvedDeposit( - { - ...depositData, - destinationChainId: fakeRouteChainId, - }, - fakeDepositAccounts - ); + const depositDataValues = Object.values({ + ...depositData, + destinationChainId: fakeRouteChainId, + }) as DepositDataValues; + const tx = await approvedDeposit(depositDataValues, fakeDepositAccounts); let events = await readEventsUntilFound(connection, tx, [program]); let event = events[0].data; // 0th event is the latest event. @@ -463,15 +431,10 @@ describe("svm_spoke.deposit", () => { // Deposit with the fake route in the original program state should fail. try { - await approvedDeposit( - { - ...{ ...depositData, destinationChainId: fakeRouteChainId }, - }, - { - ...depositAccounts, - route: fakeRoutePda, - } - ); + const depositDataValues = Object.values({ + ...{ ...depositData, destinationChainId: fakeRouteChainId }, + }) as DepositDataValues; + await approvedDeposit(depositDataValues, { ...depositAccounts, route: fakeRoutePda }); assert.fail("Deposit should have failed for a fake route PDA"); } catch (err: any) { assert.include(err.toString(), "A seeds constraint was violated"); @@ -488,18 +451,11 @@ describe("svm_spoke.deposit", () => { // Equally, depositV3Now does not have `quoteTimestamp`. this is set to the current time from the program. const fillDeadlineOffset = 60; // 60 seconds offset - const depositNowData = { - ...depositData, - fillDeadlineOffset: new BN(fillDeadlineOffset), - exclusivityPeriod: new BN(0), - }; - - const delegatePda = getDepositNowPda(depositNowData as DepositNowDataSeed, program.programId); // Delegate state PDA to pull depositor tokens. const approveIx = await createApproveCheckedInstruction( depositAccounts.depositorTokenAccount, depositAccounts.mint, - delegatePda, + depositAccounts.state, depositor.publicKey, BigInt(depositData.inputAmount.toString()), tokenDecimals, @@ -510,19 +466,19 @@ describe("svm_spoke.deposit", () => { // Execute the deposit_now call. Remove the quoteTimestamp from the depositData as not needed for this method. const depositIx = await program.methods .depositNow( - depositNowData.depositor!, - depositNowData.recipient!, - depositNowData.inputToken!, - depositNowData.outputToken!, - depositNowData.inputAmount, - depositNowData.outputAmount, - depositNowData.destinationChainId, - depositNowData.exclusiveRelayer!, + depositData.depositor!, + depositData.recipient!, + depositData.inputToken!, + depositData.outputToken!, + depositData.inputAmount, + depositData.outputAmount, + depositData.destinationChainId, + depositData.exclusiveRelayer!, fillDeadlineOffset, 0, - depositNowData.message + depositData.message ) - .accounts({ ...depositAccounts, delegate: delegatePda }) + .accounts(depositAccounts) .instruction(); const depositTx = new Transaction().add(approveIx, depositIx); const tx = await sendAndConfirmTransaction(connection, depositTx, [payer, depositor]); @@ -552,7 +508,8 @@ describe("svm_spoke.deposit", () => { depositData.exclusiveRelayer = new PublicKey("11111111111111111111111111111111"); depositData.exclusivityParameter = new BN(1); try { - await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); assert.fail("Should have failed due to InvalidExclusiveRelayer"); } catch (err: any) { assert.include(err.toString(), "InvalidExclusiveRelayer"); @@ -569,7 +526,8 @@ describe("svm_spoke.deposit", () => { for (const exclusivityDeadline of invalidExclusivityDeadlines) { depositData.exclusivityParameter = exclusivityDeadline; try { - await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); assert.fail("Should have failed due to InvalidExclusiveRelayer"); } catch (err: any) { assert.include(err.toString(), "InvalidExclusiveRelayer"); @@ -578,7 +536,8 @@ describe("svm_spoke.deposit", () => { // Test with exclusivityDeadline set to 0 depositData.exclusivityParameter = new BN(0); - await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); }); it("Exclusivity param is used as an offset", async () => { @@ -588,7 +547,8 @@ describe("svm_spoke.deposit", () => { depositData.exclusiveRelayer = depositor.publicKey; depositData.exclusivityParameter = maxExclusivityOffsetSeconds; - const tx = await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + const tx = await approvedDeposit(depositDataValues); const events = await readEventsUntilFound(connection, tx, [program]); const event = events[0].data; // 0th event is the latest event @@ -607,7 +567,8 @@ describe("svm_spoke.deposit", () => { depositData.exclusiveRelayer = depositor.publicKey; depositData.exclusivityParameter = exclusivityDeadlineTimestamp; - const tx = await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + const tx = await approvedDeposit(depositDataValues); const events = await readEventsUntilFound(connection, tx, [program]); const event = events[0].data; // 0th event is the latest event; @@ -623,7 +584,8 @@ describe("svm_spoke.deposit", () => { depositData.exclusiveRelayer = depositor.publicKey; depositData.exclusivityParameter = zeroExclusivity; - const tx = await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + const tx = await approvedDeposit(depositDataValues); const events = await readEventsUntilFound(connection, tx, [program]); const event = events[0].data; // 0th event is the latest event; @@ -657,7 +619,7 @@ describe("svm_spoke.deposit", () => { const approveIx = await createApproveCheckedInstruction( depositAccounts.depositorTokenAccount, depositAccounts.mint, - getDepositPda(depositData as DepositDataSeed, program.programId), + depositAccounts.state, depositor.publicKey, BigInt(depositData.inputAmount.toString()), tokenDecimals, @@ -720,7 +682,8 @@ describe("svm_spoke.deposit", () => { assertSE(vaultAccount.amount, "0", "Vault balance should be zero before the deposit"); // Execute the deposit call - await approvedDeposit(depositData); + const depositDataValues = Object.values(depositData) as DepositDataValues; + await approvedDeposit(depositDataValues); // Verify tokens leave the depositor's account const depositorAccount = await getAccount(connection, depositorTA, undefined, tokenProgram); @@ -775,7 +738,7 @@ describe("svm_spoke.deposit", () => { const approveIx = getApproveCheckedInstruction({ source: address(depositAccounts.depositorTokenAccount.toString()), mint: address(depositAccounts.mint.toString()), - delegate: address(getDepositPda(depositData as DepositDataSeed, program.programId).toString()), + delegate: address(depositAccounts.state.toString()), owner: address(depositor.publicKey.toString()), amount: BigInt(depositData.inputAmount.toString()), decimals: tokenDecimals, @@ -798,7 +761,6 @@ describe("svm_spoke.deposit", () => { const formattedAccounts = { state: address(depositAccounts.state.toString()), - delegate: address(getDepositPda(depositData as DepositDataSeed, program.programId).toString()), route: address(depositAccounts.route.toString()), depositorTokenAccount: address(depositAccounts.depositorTokenAccount.toString()), mint: address(depositAccounts.mint.toString()), diff --git a/test/svm/SvmSpoke.Fill.AcrossPlus.ts b/test/svm/SvmSpoke.Fill.AcrossPlus.ts index 68ee7c2c0..dc858cedf 100644 --- a/test/svm/SvmSpoke.Fill.AcrossPlus.ts +++ b/test/svm/SvmSpoke.Fill.AcrossPlus.ts @@ -29,16 +29,16 @@ import { sendTransactionWithLookupTable, loadFillRelayParams, intToU8Array32, - getFillRelayDelegatePda, } from "../../src/svm/web3-v1"; import { MulticallHandler } from "../../target/types/multicall_handler"; import { common } from "./SvmSpoke.common"; import { FillDataParams, FillDataValues } from "../../src/types/svm"; -const { provider, connection, program, owner, chainId, seedBalance, initializeState, assertSE } = common; +const { provider, connection, program, owner, chainId, seedBalance } = common; +const { initializeState, assertSE } = common; describe("svm_spoke.fill.across_plus", () => { anchor.setProvider(provider); - const { payer } = anchor.AnchorProvider.env().wallet as anchor.Wallet; + const payer = (anchor.AnchorProvider.env().wallet as anchor.Wallet).payer; const relayer = Keypair.generate(); const handlerProgram = anchor.workspace.MulticallHandler as Program; @@ -49,15 +49,14 @@ describe("svm_spoke.fill.across_plus", () => { finalRecipientATA: PublicKey, state: PublicKey, mint: PublicKey, - relayerATA: PublicKey, - seed: BN; + relayerATA: PublicKey; const relayAmount = 500000; const mintDecimals = 6; let relayData: any; // reused relay data for all tests. let accounts: any; // Store accounts to simplify contract interactions. - const updateRelayData = (newRelayData: any) => { + function updateRelayData(newRelayData: any) { relayData = newRelayData; const relayHashUint8Array = calculateRelayHashUint8Array(relayData, chainId); const [fillStatusPDA] = PublicKey.findProgramAddressSync( @@ -67,7 +66,6 @@ describe("svm_spoke.fill.across_plus", () => { accounts = { state, - delegate: getFillRelayDelegatePda(relayHashUint8Array, new BN(1), relayer.publicKey, program.programId).pda, signer: relayer.publicKey, instructionParams: program.programId, mint: mint, @@ -78,17 +76,14 @@ describe("svm_spoke.fill.across_plus", () => { associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: anchor.web3.SystemProgram.programId, }; - }; - - const createApproveAndFillIx = async (multicallHandlerCoder: MulticallHandlerCoder, bufferParams = false) => { - const relayHashUint8Array = calculateRelayHashUint8Array(relayData, chainId); - const relayHash = Array.from(relayHashUint8Array); + } + async function createApproveAndFillIx(multicallHandlerCoder: MulticallHandlerCoder, bufferParams = false) { // Delegate state PDA to pull relayer tokens. const approveIx = await createApproveCheckedInstruction( accounts.relayerTokenAccount, accounts.mint, - getFillRelayDelegatePda(relayHashUint8Array, new BN(1), relayer.publicKey, program.programId).pda, + accounts.state, accounts.signer, BigInt(relayAmount), mintDecimals @@ -99,6 +94,8 @@ describe("svm_spoke.fill.across_plus", () => { ...multicallHandlerCoder.compiledKeyMetas, ]; + const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); + // Prepare fill instruction. const fillV3RelayValues: FillDataValues = [relayHash, relayData, new BN(1), relayer.publicKey]; if (bufferParams) { @@ -118,7 +115,7 @@ describe("svm_spoke.fill.across_plus", () => { .instruction(); return { approveIx, fillIx }; - }; + } before("Creates token mint and associated token accounts", async () => { mint = await createMint(connection, payer, owner, owner, mintDecimals); @@ -136,7 +133,7 @@ describe("svm_spoke.fill.across_plus", () => { finalRecipient = Keypair.generate().publicKey; finalRecipientATA = (await getOrCreateAssociatedTokenAccount(connection, payer, mint, finalRecipient)).address; - ({ state, seed } = await initializeState()); + ({ state } = await initializeState()); const initialRelayData = { depositor: finalRecipient, diff --git a/test/svm/SvmSpoke.Fill.ts b/test/svm/SvmSpoke.Fill.ts index 8c7612b5a..d99312a3c 100644 --- a/test/svm/SvmSpoke.Fill.ts +++ b/test/svm/SvmSpoke.Fill.ts @@ -31,7 +31,6 @@ import { SvmSpokeClient } from "../../src/svm"; import { FillRelayAsyncInput } from "../../src/svm/clients/SvmSpoke"; import { calculateRelayHashUint8Array, - getFillRelayDelegatePda, hashNonEmptyMessage, intToU8Array32, readEventsUntilFound, @@ -45,23 +44,12 @@ import { signAndSendTransaction, testAcrossPlusMessage, } from "./utils"; -const { - provider, - connection, - program, - owner, - chainId, - seedBalance, - recipient, - initializeState, - setCurrentTime, - assertSE, - assert, -} = common; +const { provider, connection, program, owner, chainId, seedBalance } = common; +const { recipient, initializeState, setCurrentTime, assertSE, assert } = common; describe("svm_spoke.fill", () => { anchor.setProvider(provider); - const { payer } = anchor.AnchorProvider.env().wallet as anchor.Wallet; + const payer = (anchor.AnchorProvider.env().wallet as anchor.Wallet).payer; const relayer = Keypair.generate(); const otherRelayer = Keypair.generate(); const { encodedMessage, fillRemainingAccounts } = testAcrossPlusMessage(); @@ -72,15 +60,13 @@ describe("svm_spoke.fill", () => { relayerTA: PublicKey, recipientTA: PublicKey, otherRelayerTA: PublicKey, - tokenProgram: PublicKey, - seed: BN; + tokenProgram: PublicKey; const relayAmount = 500000; let relayData: RelayData; // reused relay data for all tests. type FillAccounts = { state: PublicKey; - delegate: PublicKey; signer: PublicKey; instructionParams: PublicKey; mint: PublicKey; @@ -95,7 +81,7 @@ describe("svm_spoke.fill", () => { let accounts: FillAccounts; // Store accounts to simplify contract interactions. - const updateRelayData = (newRelayData: RelayData) => { + function updateRelayData(newRelayData: RelayData) { relayData = newRelayData; const relayHashUint8Array = calculateRelayHashUint8Array(relayData, chainId); const [fillStatusPDA] = PublicKey.findProgramAddressSync( @@ -103,19 +89,11 @@ describe("svm_spoke.fill", () => { program.programId ); - const { pda: delegatePda } = getFillRelayDelegatePda( - relayHashUint8Array, - new BN(1), - relayer.publicKey, - program.programId - ); - accounts = { state, - delegate: delegatePda, signer: relayer.publicKey, instructionParams: program.programId, - mint, + mint: mint, relayerTokenAccount: relayerTA, recipientTokenAccount: recipientTA, fillStatus: fillStatusPDA, @@ -124,39 +102,32 @@ describe("svm_spoke.fill", () => { systemProgram: anchor.web3.SystemProgram.programId, program: program.programId, }; - }; + } const approvedFillRelay = async ( fillDataValues: FillDataValues, calledFillAccounts: FillAccounts = accounts, callingRelayer: Keypair = relayer ): Promise => { - const relayHash = Uint8Array.from(fillDataValues[0]); - const { seedHash, pda: delegatePda } = getFillRelayDelegatePda( - relayHash, - fillDataValues[2], - fillDataValues[3], - program.programId - ); - + // Delegate state PDA to pull relayer tokens. const approveIx = await createApproveCheckedInstruction( calledFillAccounts.relayerTokenAccount, calledFillAccounts.mint, - delegatePda, + calledFillAccounts.state, calledFillAccounts.signer, BigInt(fillDataValues[1].outputAmount.toString()), tokenDecimals, undefined, tokenProgram ); - const fillIx = await program.methods .fillRelay(...fillDataValues) - .accounts({ ...calledFillAccounts, delegate: delegatePda }) + .accounts(calledFillAccounts) .remainingAccounts(fillRemainingAccounts) .instruction(); - - return sendAndConfirmTransaction(connection, new Transaction().add(approveIx, fillIx), [payer, callingRelayer]); + const fillTx = new Transaction().add(approveIx, fillIx); + const tx = await sendAndConfirmTransaction(connection, fillTx, [payer, callingRelayer]); + return tx; }; before("Funds relayer wallets", async () => { @@ -178,7 +149,7 @@ describe("svm_spoke.fill", () => { }); beforeEach(async () => { - ({ state, seed } = await initializeState()); + ({ state } = await initializeState()); tokenProgram = TOKEN_PROGRAM_ID; // Some tests might override this. const initialRelayData = { @@ -209,7 +180,7 @@ describe("svm_spoke.fill", () => { assertSE(relayerAccount.amount, seedBalance, "Relayer's balance should be equal to seed balance before the fill"); const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); - await approvedFillRelay([relayHash, relayData, chainId, relayer.publicKey]); + await approvedFillRelay([relayHash, relayData, new BN(1), relayer.publicKey]); // Verify relayer's balance after the fill relayerAccount = await getAccount(connection, relayerTA); @@ -237,9 +208,7 @@ describe("svm_spoke.fill", () => { Object.entries(relayData).forEach(([key, value]) => { if (key === "message") { assertSE(event.messageHash, hashNonEmptyMessage(value as Buffer), `MessageHash should match`); - } else { - assertSE(event[key], value, `${key.charAt(0).toUpperCase() + key.slice(1)} should match`); - } + } else assertSE(event[key], value, `${key.charAt(0).toUpperCase() + key.slice(1)} should match`); }); // RelayExecutionInfo should match. assertSE(event.relayExecutionInfo.updatedRecipient, relayData.recipient, "UpdatedRecipient should match"); @@ -480,8 +449,7 @@ describe("svm_spoke.fill", () => { }; updateRelayData(newRelayData); accounts.recipientTokenAccount = newRecipientATA; - const relayHashUint8Array = calculateRelayHashUint8Array(newRelayData, chainId); - const relayHash = Array.from(relayHashUint8Array); + const relayHash = Array.from(calculateRelayHashUint8Array(newRelayData, chainId)); try { await approvedFillRelay([relayHash, newRelayData, new BN(1), relayer.publicKey]); @@ -500,18 +468,11 @@ describe("svm_spoke.fill", () => { ]) .instruction(); - const { pda: delegatePda } = getFillRelayDelegatePda( - relayHashUint8Array, - new BN(1), - relayer.publicKey, - program.programId - ); - // Fill the deposit in the same transaction const approveInstruction = await createApproveCheckedInstruction( accounts.relayerTokenAccount, accounts.mint, - delegatePda, + accounts.state, accounts.signer, BigInt(newRelayData.outputAmount.toString()), tokenDecimals, @@ -520,7 +481,7 @@ describe("svm_spoke.fill", () => { ); const fillInstruction = await program.methods .fillRelay(relayHash, newRelayData, new BN(1), relayer.publicKey) - .accounts({ ...accounts, delegate: delegatePda }) + .accounts(accounts) .remainingAccounts(fillRemainingAccounts) .instruction(); @@ -556,7 +517,7 @@ describe("svm_spoke.fill", () => { // Build instructions for all fills let totalFillAmount = new BN(0); - const approveAndfillInstructions: TransactionInstruction[] = []; + const fillInstructions: TransactionInstruction[] = []; for (let i = 0; i < numberOfFills; i++) { const newRelayData = { ...relayData, @@ -566,40 +527,30 @@ describe("svm_spoke.fill", () => { totalFillAmount = totalFillAmount.add(newRelayData.outputAmount); updateRelayData(newRelayData); accounts.recipientTokenAccount = recipientAssociatedTokens[i]; - const relayHashUint8Array = calculateRelayHashUint8Array(newRelayData, chainId); - const relayHash = Array.from(relayHashUint8Array); - - const { pda: delegatePda } = getFillRelayDelegatePda( - relayHashUint8Array, - new BN(1), - relayer.publicKey, - program.programId - ); - - const approveInstruction = await createApproveCheckedInstruction( - accounts.relayerTokenAccount, - accounts.mint, - delegatePda, - accounts.signer, - BigInt(totalFillAmount.toString()), - tokenDecimals, - undefined, - tokenProgram - ); - approveAndfillInstructions.push(approveInstruction); - + const relayHash = Array.from(calculateRelayHashUint8Array(newRelayData, chainId)); const fillInstruction = await program.methods .fillRelay(relayHash, newRelayData, new BN(1), relayer.publicKey) - .accounts({ ...accounts, delegate: delegatePda }) + .accounts(accounts) .remainingAccounts(fillRemainingAccounts) .instruction(); - approveAndfillInstructions.push(fillInstruction); + fillInstructions.push(fillInstruction); } + const approveInstruction = await createApproveCheckedInstruction( + accounts.relayerTokenAccount, + accounts.mint, + accounts.state, + accounts.signer, + BigInt(totalFillAmount.toString()), + tokenDecimals, + undefined, + tokenProgram + ); + // Fill using the ALT. await sendTransactionWithLookupTable( connection, - [createTokenAccountsInstruction, ...approveAndfillInstructions], + [createTokenAccountsInstruction, approveInstruction, ...fillInstructions], relayer ); @@ -700,7 +651,7 @@ describe("svm_spoke.fill", () => { }); describe("codama client and solana kit", () => { - it("Fills a relay and verifies balances with codama client and solana kit", async () => { + it("Fills a V3 relay and verifies balances with codama client and solana kit", async () => { const rpcClient = createDefaultSolanaClient(); const signer = await createSignerFromKeyPair(await createKeyPairFromBytes(relayer.secretKey)); @@ -715,19 +666,10 @@ describe("svm_spoke.fill", () => { let relayerAccount = await getAccount(connection, relayerTA); assertSE(relayerAccount.amount, seedBalance, "Relayer's balance should be equal to seed balance before the fill"); - const relayHashUint8Array = calculateRelayHashUint8Array(relayData, chainId); - const relayHash = Array.from(relayHashUint8Array); - - const { pda: delegatePda } = getFillRelayDelegatePda( - relayHashUint8Array, - new BN(1), - relayer.publicKey, - program.programId - ); + const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); const formattedAccounts = { state: address(accounts.state.toString()), - delegate: address(delegatePda.toString()), instructionParams: address(program.programId.toString()), mint: address(mint.toString()), relayerTokenAccount: address(relayerTA.toString()), @@ -764,7 +706,7 @@ describe("svm_spoke.fill", () => { const approveIx = getApproveCheckedInstruction({ source: address(accounts.relayerTokenAccount.toString()), mint: address(accounts.mint.toString()), - delegate: address(delegatePda.toString()), + delegate: address(accounts.state.toString()), owner: address(accounts.signer.toString()), amount: BigInt(relayData.outputAmount.toString()), decimals: tokenDecimals, diff --git a/test/svm/SvmSpoke.SlowFill.ts b/test/svm/SvmSpoke.SlowFill.ts index ab78cb8a9..0b5116533 100644 --- a/test/svm/SvmSpoke.SlowFill.ts +++ b/test/svm/SvmSpoke.SlowFill.ts @@ -15,7 +15,6 @@ import { MerkleTree } from "@uma/common/dist/MerkleTree"; import { SlowFillLeaf } from "../../src/types/svm"; import { calculateRelayHashUint8Array, - getFillRelayDelegatePda, hashNonEmptyMessage, intToU8Array32, readEventsUntilFound, @@ -23,23 +22,12 @@ import { } from "../../src/svm/web3-v1"; import { testAcrossPlusMessage } from "./utils"; -const { - provider, - connection, - program, - owner, - chainId, - seedBalance, - initializeState, - recipient, - setCurrentTime, - assertSE, - assert, -} = common; +const { provider, connection, program, owner, chainId, seedBalance, initializeState } = common; +const { recipient, setCurrentTime, assertSE, assert } = common; describe("svm_spoke.slow_fill", () => { anchor.setProvider(provider); - const { payer } = anchor.AnchorProvider.env().wallet as anchor.Wallet; + const payer = (anchor.AnchorProvider.env().wallet as anchor.Wallet).payer; const relayer = Keypair.generate(); const otherRelayer = Keypair.generate(); const { encodedMessage, fillRemainingAccounts } = testAcrossPlusMessage(); @@ -61,7 +49,7 @@ describe("svm_spoke.slow_fill", () => { const initialMintAmount = 10_000_000_000; - const updateRelayData = async (newRelayData: SlowFillLeaf["relayData"]) => { + async function updateRelayData(newRelayData: SlowFillLeaf["relayData"]) { relayData = newRelayData; const relayHashUint8Array = calculateRelayHashUint8Array(relayData, chainId); [fillStatus] = PublicKey.findProgramAddressSync([Buffer.from("fills"), relayHashUint8Array], program.programId); @@ -80,7 +68,6 @@ describe("svm_spoke.slow_fill", () => { }; fillAccounts = { state, - delegate: getFillRelayDelegatePda(relayHashUint8Array, new BN(1), relayer.publicKey, program.programId).pda, signer: relayer.publicKey, instructionParams: program.programId, mint: mint, @@ -91,7 +78,7 @@ describe("svm_spoke.slow_fill", () => { associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: anchor.web3.SystemProgram.programId, }; - }; + } const relaySlowFillRootBundle = async ( slowRelayLeafRecipient = recipient, @@ -130,7 +117,7 @@ describe("svm_spoke.slow_fill", () => { const leaf = slowRelayLeafs[0]; let stateAccountData = await program.account.state.fetch(state); - const { rootBundleId } = stateAccountData; + const rootBundleId = stateAccountData.rootBundleId; const rootBundleIdBuffer = Buffer.alloc(4); rootBundleIdBuffer.writeUInt32LE(rootBundleId); @@ -225,21 +212,18 @@ describe("svm_spoke.slow_fill", () => { Object.entries(relayData).forEach(([key, value]) => { if (key === "message") { assertSE(event.messageHash, hashNonEmptyMessage(value as Buffer), `MessageHash should match`); - } else { - assertSE(event[key], value, `${key.charAt(0).toUpperCase() + key.slice(1)} should match`); - } + } else assertSE(event[key], value, `${key.charAt(0).toUpperCase() + key.slice(1)} should match`); }); }); it("Fails to request a V3 slow fill if the relay has already been filled", async () => { - const relayHashUint8Array = calculateRelayHashUint8Array(relayData, chainId); - const relayHash = Array.from(relayHashUint8Array); + const relayHash = Array.from(calculateRelayHashUint8Array(relayData, chainId)); // Fill the relay first const approveIx = await createApproveCheckedInstruction( fillAccounts.relayerTokenAccount, fillAccounts.mint, - getFillRelayDelegatePda(relayHashUint8Array, new BN(1), relayer.publicKey, program.programId).pda, + fillAccounts.state, fillAccounts.signer, BigInt(relayData.outputAmount.toString()), tokenDecimals @@ -400,9 +384,7 @@ describe("svm_spoke.slow_fill", () => { Object.entries(relayData).forEach(([key, value]) => { if (key === "message") { assertSE(event.messageHash, hashNonEmptyMessage(value as Buffer), `MessageHash should match`); - } else { - assertSE(event[key], value, `${key.charAt(0).toUpperCase() + key.slice(1)} should match`); - } + } else assertSE(event[key], value, `${key.charAt(0).toUpperCase() + key.slice(1)} should match`); }); // RelayExecutionInfo should match. assertSE(event.relayExecutionInfo.updatedRecipient, relayData.recipient, "UpdatedRecipient should match");