Skip to content

Commit

Permalink
test: add swap program tests (#323)
Browse files Browse the repository at this point in the history
* add instructions

* improve swap

* seems to work

* add helper function for decimal amonts

* works

* set non-zero platform fee

* add token quantity assertions

* add deadline test

* default setup

* default setup

* clippy

* checkpoitn

* checkpoint

* works

* works

* works

* use old lock

* missing assert

* add to CI

* add test

* pr comments: make split parameters explicit when asserting

* use initialize

* 10f64

* pr comments: rename to create mint

* make aidrop amount an arg

* go

* refactor into two functions

* remove f64 notation
  • Loading branch information
guibescos authored Jan 14, 2025
1 parent baffeb6 commit 8a2e9a3
Show file tree
Hide file tree
Showing 19 changed files with 1,006 additions and 124 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/test-svm-contract.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Test SVM contracts

on:
pull_request:
paths:
- contracts/svm/**
push:
branches:
- main
paths:
- contracts/svm/**

env:
CARGO_TERM_COLOR: always

jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: contracts/svm
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.75.0
override: true
- name: Install Solana
run: |
sh -c "$(curl -sSfL https://release.solana.com/v1.18.16/install)"
echo "/home/runner/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
- name: Build
run: cargo-build-sbf
- name: Run tests
run: cargo-test-sbf && cargo test -p testing
1 change: 1 addition & 0 deletions contracts/svm/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions contracts/svm/programs/express_relay/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ pub enum ErrorCode {
InsufficientSearcherFunds,
#[msg("Insufficient funds for rent")]
InsufficientRent,
#[msg("Invalid ATA provided")]
InvalidAta,
#[msg("A token account has the wrong mint")]
InvalidMint,
#[msg("A token account belongs to the wrong token program")]
InvalidTokenProgram,
#[msg("Invalid referral fee")]
InvalidReferralFee,
}
1 change: 1 addition & 0 deletions contracts/svm/testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"

[dependencies]
anchor-lang = { workspace = true }
anchor-spl = { workspace = true }
solana-sdk = { workspace = true }
express-relay = { path = "../programs/express_relay", features = ["no-entrypoint"] }
dummy = { path = "../programs/dummy", features = ["no-entrypoint"] }
Expand Down
2 changes: 1 addition & 1 deletion contracts/svm/testing/src/express_relay/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub fn get_express_relay_metadata_key() -> Pubkey {
Pubkey::find_program_address(&[SEED_METADATA], &express_relay::id()).0
}

pub fn get_express_relay_metadata(svm: litesvm::LiteSVM) -> ExpressRelayMetadata {
pub fn get_express_relay_metadata(svm: &mut litesvm::LiteSVM) -> ExpressRelayMetadata {
let express_relay_metadata_key = get_express_relay_metadata_key();
let express_relay_metadata_acc = svm
.get_account(&express_relay_metadata_key)
Expand Down
2 changes: 2 additions & 0 deletions contracts/svm/testing/src/express_relay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ pub mod set_admin;
pub mod set_relayer;
pub mod set_router_split;
pub mod set_splits;
pub mod set_swap_platform_fee;
pub mod submit_bid;
pub mod swap;
pub mod withdraw_fees;
38 changes: 38 additions & 0 deletions contracts/svm/testing/src/express_relay/set_swap_platform_fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use {
super::helpers::get_express_relay_metadata_key,
anchor_lang::{
InstructionData,
ToAccountMetas,
},
express_relay::{
accounts::SetSplits,
SetSwapPlatformFeeArgs,
},
solana_sdk::{
instruction::Instruction,
signature::Keypair,
signer::Signer,
},
};

pub fn set_swap_platform_fee_instruction(
admin: &Keypair,
swap_platform_fee_bps: u64,
) -> Instruction {
let express_relay_metadata = get_express_relay_metadata_key();

Instruction {
program_id: express_relay::id(),
data: express_relay::instruction::SetSwapPlatformFee {
data: SetSwapPlatformFeeArgs {
swap_platform_fee_bps,
},
}
.data(),
accounts: SetSplits {
admin: admin.pubkey(),
express_relay_metadata,
}
.to_account_metas(None),
}
}
181 changes: 181 additions & 0 deletions contracts/svm/testing/src/express_relay/swap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use {
super::helpers::get_express_relay_metadata_key,
anchor_lang::{
InstructionData,
ToAccountMetas,
},
anchor_spl::{
associated_token::{
get_associated_token_address_with_program_id,
spl_associated_token_account::instruction::create_associated_token_account_idempotent,
},
token::spl_token,
},
express_relay::{
accounts::{
self,
},
instruction::Swap,
FeeToken,
SwapArgs,
},
solana_sdk::{
instruction::Instruction,
pubkey::Pubkey,
},
};

#[allow(clippy::too_many_arguments)]
pub fn create_swap_instruction(
searcher: Pubkey,
trader: Pubkey,
searcher_input_ta: Option<Pubkey>,
searcher_output_ta: Option<Pubkey>,
router_fee_receiver_ta: Pubkey,
fee_receiver_relayer: Pubkey,
mint_input: Pubkey,
mint_output: Pubkey,
token_program_input: Option<Pubkey>,
token_program_output: Option<Pubkey>,
swap_args: SwapArgs,
) -> Instruction {
let express_relay_metadata = get_express_relay_metadata_key();

let mint_fee = match swap_args.fee_token {
FeeToken::Input => mint_input,
FeeToken::Output => mint_output,
};

let token_program_input = token_program_input.unwrap_or(spl_token::ID);
let token_program_output = token_program_output.unwrap_or(spl_token::ID);

let token_program_fee = match swap_args.fee_token {
FeeToken::Input => token_program_input,
FeeToken::Output => token_program_output,
};
let accounts_submit_bid = accounts::Swap {
searcher,
trader,
searcher_input_ta: searcher_input_ta.unwrap_or(
get_associated_token_address_with_program_id(
&searcher,
&mint_input,
&token_program_input,
),
),
searcher_output_ta: searcher_output_ta.unwrap_or(
get_associated_token_address_with_program_id(
&searcher,
&mint_output,
&token_program_output,
),
),
trader_input_ata: get_associated_token_address_with_program_id(
&trader,
&mint_input,
&token_program_input,
),
trader_output_ata: get_associated_token_address_with_program_id(
&trader,
&mint_output,
&token_program_output,
),
router_fee_receiver_ta,
relayer_fee_receiver_ata: get_associated_token_address_with_program_id(
&fee_receiver_relayer,
&mint_fee,
&token_program_fee,
),
express_relay_fee_receiver_ata: get_associated_token_address_with_program_id(
&express_relay_metadata,
&mint_fee,
&token_program_fee,
),
mint_input,
mint_output,
mint_fee,
token_program_input,
token_program_output,
token_program_fee,
express_relay_metadata,
}
.to_account_metas(None);

Instruction {
program_id: express_relay::ID,
accounts: accounts_submit_bid,
data: Swap { data: swap_args }.data(),
}
}

#[allow(clippy::too_many_arguments)]
pub fn build_swap_instructions(
searcher: Pubkey,
trader: Pubkey,
searcher_input_ta: Option<Pubkey>,
searcher_output_ta: Option<Pubkey>,
router_fee_receiver_ta: Pubkey,
fee_receiver_relayer: Pubkey,
mint_input: Pubkey,
mint_output: Pubkey,
token_program_input: Option<Pubkey>,
token_program_output: Option<Pubkey>,
swap_args: SwapArgs,
) -> Vec<Instruction> {
let mut instructions: Vec<Instruction> = vec![];

let token_program_input = token_program_input.unwrap_or(spl_token::ID);
let token_program_output = token_program_output.unwrap_or(spl_token::ID);
let mint_fee = match swap_args.fee_token {
FeeToken::Input => mint_input,
FeeToken::Output => mint_output,
};
let token_program_fee = match swap_args.fee_token {
FeeToken::Input => token_program_input,
FeeToken::Output => token_program_output,
};

if searcher_output_ta.is_none() {
instructions.push(create_associated_token_account_idempotent(
&searcher,
&searcher,
&mint_output,
&token_program_output,
));
}
instructions.push(create_associated_token_account_idempotent(
&searcher,
&trader,
&mint_input,
&token_program_input,
));
instructions.push(create_associated_token_account_idempotent(
&searcher,
&fee_receiver_relayer,
&mint_fee,
&token_program_fee,
));
instructions.push(create_associated_token_account_idempotent(
&searcher,
&get_express_relay_metadata_key(),
&mint_fee,
&token_program_fee,
));


instructions.push(create_swap_instruction(
searcher,
trader,
searcher_input_ta,
searcher_output_ta,
router_fee_receiver_ta,
fee_receiver_relayer,
mint_input,
mint_output,
Some(token_program_input),
Some(token_program_output),
swap_args,
));

instructions
}
2 changes: 1 addition & 1 deletion contracts/svm/testing/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use {
Instruction,
InstructionError::Custom,
},
native_token::LAMPORTS_PER_SOL,
pubkey::Pubkey,
signature::Keypair,
signer::Signer,
Expand All @@ -19,7 +20,6 @@ use {
},
};

pub const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
pub const TX_FEE: u64 = 10_000; // TODO: make this programmatic? FeeStructure is currently private field within LiteSVM

#[allow(clippy::result_large_err)]
Expand Down
16 changes: 14 additions & 2 deletions contracts/svm/testing/src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ use {
},
};

pub const SPLIT_ROUTER_DEFAULT: u64 = 4000;
pub const SPLIT_RELAYER: u64 = 2000;

pub struct SetupParams {
pub split_router_default: u64,
pub split_relayer: u64,
Expand All @@ -29,11 +32,20 @@ pub struct SetupResult {
pub searcher: Keypair,
}

pub fn setup(params: SetupParams) -> Result<SetupResult, TransactionError> {
impl Default for SetupParams {
fn default() -> Self {
Self {
split_router_default: SPLIT_ROUTER_DEFAULT,
split_relayer: SPLIT_RELAYER,
}
}
}

pub fn setup(params: Option<SetupParams>) -> Result<SetupResult, TransactionError> {
let SetupParams {
split_router_default,
split_relayer,
} = params;
} = params.unwrap_or_default();

let mut svm = litesvm::LiteSVM::new();
svm.add_program_from_file(express_relay::ID, "../target/deploy/express_relay.so")
Expand Down
Loading

0 comments on commit 8a2e9a3

Please sign in to comment.