token-swap: Add fuzzer for swap / withdraw / deposit (#875)
* token-swap: Add fuzzer for swap / withdraw / deposit * Run cargo fmt * Make end-to-end testing work * Fix test failures * Cleanup for review * Remove Clone trait for Initialize * Fix building fuzz targets * Fix fuzz withdraw logic to avoid ZeroTradingTokens error * Cargo fmt * Clippy / fmt again * Integrate fuzzer in main workspace to share clippy / fmt * Fix clippy * Switch to stable honggfuzz that could work with BPF * Update to most recent honggfuzz * Add build / run requirements * Add fuzz CI and cleanup for honggfuzz * Revert to using fuzz feature to integrate with workspace * Make fuzz script executable * Refactor and cleanup * Add nightly run * Fix workflow typo * Add runtime for pull request fuzz workflow * Fix unrelated new clippy errors
This commit is contained in:
parent
ac20c5d59a
commit
b40e0dd3fd
|
@ -0,0 +1,64 @@
|
|||
name: Fuzz Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 3 * * *'
|
||||
|
||||
jobs:
|
||||
fuzz-nightly:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
fuzz_target: [token-swap-instructions]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-fuzz-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
cargo-fuzz-
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/cargo-hfuzz
|
||||
~/.cargo/bin/cargo-honggfuzz
|
||||
key: cargo-fuzz-bins-${{ runner.os }}
|
||||
restore-keys: |
|
||||
cargo-fuzz-bins-${{ runner.os }}-
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
restore-keys: |
|
||||
solana-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Run fuzz target
|
||||
run: ./ci/fuzz.sh ${{ matrix.fuzz_target }} 18000 # 5 hours, jobs max out at 6
|
|
@ -15,6 +15,7 @@ jobs:
|
|||
- js-test-token
|
||||
- js-test-token-swap
|
||||
- js-test-token-lending
|
||||
- fuzz
|
||||
steps:
|
||||
- run: echo "Done"
|
||||
|
||||
|
@ -200,3 +201,60 @@ jobs:
|
|||
name: programs
|
||||
path: target/bpfel-unknown-unknown/release
|
||||
- run: ./ci/js-test-token-lending.sh
|
||||
|
||||
fuzz:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
fuzz_target: [token-swap-instructions]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-fuzz-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
cargo-fuzz-
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/cargo-hfuzz
|
||||
~/.cargo/bin/cargo-honggfuzz
|
||||
key: cargo-fuzz-bins-${{ runner.os }}
|
||||
restore-keys: |
|
||||
cargo-fuzz-bins-${{ runner.os }}-
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
restore-keys: |
|
||||
solana-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Run fuzz target
|
||||
run: ./ci/fuzz.sh ${{ matrix.fuzz_target }} 30 # 30 seconds, just to check everything is ok
|
||||
|
|
|
@ -5,3 +5,5 @@ bin
|
|||
config.json
|
||||
node_modules
|
||||
./package-lock.json
|
||||
hfuzz_target
|
||||
hfuzz_workspace
|
||||
|
|
|
@ -58,6 +58,15 @@ version = "1.0.34"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.6"
|
||||
|
@ -690,13 +699,12 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "2.1.0"
|
||||
source = "git+https://github.com/garious/curve25519-dalek?rev=60efef3553d6bf3d7f3b09b5f97acd54d72529ff#60efef3553d6bf3d7f3b09b5f97acd54d72529ff"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
|
||||
dependencies = [
|
||||
"borsh",
|
||||
"byteorder",
|
||||
"digest 0.8.1",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"subtle 2.2.3",
|
||||
"zeroize",
|
||||
]
|
||||
|
@ -704,12 +712,13 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
|
||||
source = "git+https://github.com/garious/curve25519-dalek?rev=60efef3553d6bf3d7f3b09b5f97acd54d72529ff#60efef3553d6bf3d7f3b09b5f97acd54d72529ff"
|
||||
dependencies = [
|
||||
"borsh",
|
||||
"byteorder",
|
||||
"digest 0.8.1",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"subtle 2.2.3",
|
||||
"zeroize",
|
||||
]
|
||||
|
@ -749,6 +758,17 @@ dependencies = [
|
|||
"syn 1.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a012b5e473dc912f0db0546a1c9c6a194ce8494feb66fa0237160926f9e0e6"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.6.2"
|
||||
|
@ -1258,6 +1278,17 @@ dependencies = [
|
|||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "honggfuzz"
|
||||
version = "0.5.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f085725a5828d7e959f014f624773094dfe20acc91be310ef106923c30594bc"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"lazy_static",
|
||||
"memmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.1"
|
||||
|
@ -3490,6 +3521,7 @@ dependencies = [
|
|||
name = "spl-token-swap"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"arrayref",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
|
@ -3499,6 +3531,17 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-token-swap-fuzz"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"honggfuzz",
|
||||
"solana-program",
|
||||
"spl-token 3.0.0",
|
||||
"spl-token-swap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-token-v3"
|
||||
version = "3.0.0"
|
||||
|
|
|
@ -15,6 +15,7 @@ members = [
|
|||
"themis/program_ristretto",
|
||||
"token-lending/program",
|
||||
"token-swap/program",
|
||||
"token-swap/program/fuzz",
|
||||
"token/cli",
|
||||
"token/perf-monitor",
|
||||
"token/program",
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
exitcode=0
|
||||
if [[ -n "$1" ]]; then
|
||||
exitcode=1
|
||||
echo "Error: $*"
|
||||
fi
|
||||
echo "Usage: $0 [fuzz-target] [run-time-in-seconds]"
|
||||
exit $exitcode
|
||||
}
|
||||
|
||||
fuzz_target=$1
|
||||
if [[ -z $fuzz_target ]]; then
|
||||
usage "No fuzz target provided"
|
||||
fi
|
||||
|
||||
run_time=$2
|
||||
if [[ -z $2 ]]; then
|
||||
usage "No runtime provided"
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
HFUZZ_RUN_ARGS="--run_time $run_time --exit_upon_crash" cargo hfuzz run $fuzz_target
|
||||
|
||||
# Until https://github.com/rust-fuzz/honggfuzz-rs/issues/16 is resolved,
|
||||
# hfuzz does not return an error code on crash, so look for a crash artifact
|
||||
for crash_file in ./hfuzz_workspace/"$fuzz_target"/*.fuzz; do
|
||||
# Check if the glob gets expanded to existing files.
|
||||
if [[ -e "$crash_file" ]]; then
|
||||
echo ".fuzz file $crash_file found, meaning some error occurred, exiting"
|
||||
exit 1
|
||||
fi
|
||||
# Break early -- we just need one iteration to see if a failure occurred
|
||||
break
|
||||
done
|
|
@ -10,4 +10,6 @@ sudo apt-get install -y openssl --allow-unauthenticated
|
|||
sudo apt-get install -y libssl-dev --allow-unauthenticated
|
||||
sudo apt-get install -y libssl1.1 --allow-unauthenticated
|
||||
sudo apt-get install -y libudev-dev
|
||||
sudo apt-get install -y binutils-dev
|
||||
sudo apt-get install -y libunwind-dev
|
||||
clang-7 --version
|
||||
|
|
|
@ -9,6 +9,7 @@ set -x
|
|||
|
||||
cargo --version
|
||||
cargo install rustfilt || true
|
||||
cargo install honggfuzz || true
|
||||
|
||||
export PATH="$HOME"/.local/share/solana/install/active_release/bin:"$PATH"
|
||||
solana --version
|
||||
|
|
|
@ -10,6 +10,7 @@ edition = "2018"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
production = []
|
||||
fuzz = ["arbitrary"]
|
||||
|
||||
[dependencies]
|
||||
arrayref = "0.3.6"
|
||||
|
@ -19,6 +20,7 @@ solana-program = "1.4.9"
|
|||
spl-token = { version = "3.0", path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||
thiserror = "1.0"
|
||||
uint = "0.8"
|
||||
arbitrary = { version = "0.4", features = ["derive"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-sdk = "1.4.9"
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "spl-token-swap-fuzz"
|
||||
version = "0.0.1"
|
||||
description = "Solana Program Library Token Swap Fuzzer"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana-program-library"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
honggfuzz = { version = "0.5" }
|
||||
arbitrary = { version = "0.4", features = ["derive"] }
|
||||
solana-program = "1.4.8"
|
||||
spl-token = { version = "3.0", path = "../../../token/program", features = [ "no-entrypoint" ] }
|
||||
spl-token-swap = { path = "..", features = ["fuzz", "no-entrypoint"] }
|
||||
|
||||
[[bin]]
|
||||
name = "token-swap-instructions"
|
||||
path = "src/instructions.rs"
|
||||
test = false
|
||||
doc = false
|
|
@ -0,0 +1,288 @@
|
|||
use spl_token_swap_fuzz::{
|
||||
native_account_data::NativeAccountData, native_token::get_token_balance,
|
||||
native_token_swap::NativeTokenSwap,
|
||||
};
|
||||
|
||||
use spl_token_swap::{
|
||||
curve::{
|
||||
base::{CurveType, SwapCurve},
|
||||
constant_product::ConstantProductCurve,
|
||||
},
|
||||
error::SwapError,
|
||||
instruction::{Deposit, Swap, Withdraw},
|
||||
};
|
||||
|
||||
use spl_token::error::TokenError;
|
||||
|
||||
use honggfuzz::fuzz;
|
||||
|
||||
use arbitrary::Arbitrary;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Debug, Arbitrary, Clone)]
|
||||
enum FuzzInstruction {
|
||||
Swap {
|
||||
token_a_id: AccountId,
|
||||
token_b_id: AccountId,
|
||||
trade_direction: TradeDirection,
|
||||
instruction: Swap,
|
||||
},
|
||||
Deposit {
|
||||
token_a_id: AccountId,
|
||||
token_b_id: AccountId,
|
||||
pool_token_id: AccountId,
|
||||
instruction: Deposit,
|
||||
},
|
||||
Withdraw {
|
||||
token_a_id: AccountId,
|
||||
token_b_id: AccountId,
|
||||
pool_token_id: AccountId,
|
||||
instruction: Withdraw,
|
||||
},
|
||||
}
|
||||
|
||||
/// Helper enum to tell which direction a swap is meant to go.
|
||||
#[derive(Debug, Arbitrary, Clone)]
|
||||
enum TradeDirection {
|
||||
AtoB,
|
||||
BtoA,
|
||||
}
|
||||
|
||||
/// Use u8 as an account id to simplify the address space and re-use accounts
|
||||
/// more often.
|
||||
type AccountId = u8;
|
||||
|
||||
const INITIAL_SWAP_TOKEN_A_AMOUNT: u64 = 100_000_000_000;
|
||||
const INITIAL_SWAP_TOKEN_B_AMOUNT: u64 = 300_000_000_000;
|
||||
|
||||
const INITIAL_USER_TOKEN_A_AMOUNT: u64 = 1_000_000_000;
|
||||
const INITIAL_USER_TOKEN_B_AMOUNT: u64 = 3_000_000_000;
|
||||
|
||||
fn main() {
|
||||
loop {
|
||||
fuzz!(|fuzz_instructions: Vec<FuzzInstruction>| {
|
||||
run_fuzz_instructions(fuzz_instructions)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn run_fuzz_instructions(fuzz_instructions: Vec<FuzzInstruction>) {
|
||||
let trade_fee_numerator = 25;
|
||||
let trade_fee_denominator = 10000;
|
||||
let owner_trade_fee_numerator = 5;
|
||||
let owner_trade_fee_denominator = 10000;
|
||||
let owner_withdraw_fee_numerator = 30;
|
||||
let owner_withdraw_fee_denominator = 10000;
|
||||
let host_fee_numerator = 1;
|
||||
let host_fee_denominator = 5;
|
||||
let swap_curve = SwapCurve {
|
||||
curve_type: CurveType::ConstantProduct,
|
||||
calculator: Box::new(ConstantProductCurve {
|
||||
trade_fee_numerator,
|
||||
trade_fee_denominator,
|
||||
owner_trade_fee_numerator,
|
||||
owner_trade_fee_denominator,
|
||||
owner_withdraw_fee_numerator,
|
||||
owner_withdraw_fee_denominator,
|
||||
host_fee_numerator,
|
||||
host_fee_denominator,
|
||||
}),
|
||||
};
|
||||
let mut token_swap = NativeTokenSwap::new(
|
||||
swap_curve,
|
||||
INITIAL_SWAP_TOKEN_A_AMOUNT,
|
||||
INITIAL_SWAP_TOKEN_B_AMOUNT,
|
||||
);
|
||||
|
||||
// keep track of all accounts, including swap accounts
|
||||
let mut token_a_accounts: HashMap<AccountId, NativeAccountData> = HashMap::new();
|
||||
let mut token_b_accounts: HashMap<AccountId, NativeAccountData> = HashMap::new();
|
||||
let mut pool_accounts: HashMap<AccountId, NativeAccountData> = HashMap::new();
|
||||
|
||||
// to ensure that we never create or remove base tokens
|
||||
let before_total_token_a =
|
||||
INITIAL_SWAP_TOKEN_A_AMOUNT + get_total_token_a_amount(&fuzz_instructions);
|
||||
let before_total_token_b =
|
||||
INITIAL_SWAP_TOKEN_B_AMOUNT + get_total_token_b_amount(&fuzz_instructions);
|
||||
|
||||
for fuzz_instruction in fuzz_instructions {
|
||||
run_fuzz_instruction(
|
||||
fuzz_instruction,
|
||||
&mut token_swap,
|
||||
&mut token_a_accounts,
|
||||
&mut token_b_accounts,
|
||||
&mut pool_accounts,
|
||||
);
|
||||
}
|
||||
|
||||
// check total token a and b amounts
|
||||
let after_total_token_a = token_a_accounts
|
||||
.values()
|
||||
.map(get_token_balance)
|
||||
.sum::<u64>()
|
||||
+ get_token_balance(&token_swap.token_a_account);
|
||||
assert_eq!(before_total_token_a, after_total_token_a);
|
||||
let after_total_token_b = token_b_accounts
|
||||
.values()
|
||||
.map(get_token_balance)
|
||||
.sum::<u64>()
|
||||
+ get_token_balance(&token_swap.token_b_account);
|
||||
assert_eq!(before_total_token_b, after_total_token_b);
|
||||
|
||||
// final check to make sure that withdrawing everything works
|
||||
let mut withdrawn_token_a_account = token_swap.create_token_a_account(0);
|
||||
let mut withdrawn_token_b_account = token_swap.create_token_b_account(0);
|
||||
for mut pool_account in pool_accounts.values_mut() {
|
||||
token_swap
|
||||
.withdraw_all(
|
||||
&mut pool_account,
|
||||
&mut withdrawn_token_a_account,
|
||||
&mut withdrawn_token_b_account,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let mut pool_account = token_swap.pool_token_account.clone();
|
||||
token_swap
|
||||
.withdraw_all(
|
||||
&mut pool_account,
|
||||
&mut withdrawn_token_a_account,
|
||||
&mut withdrawn_token_b_account,
|
||||
)
|
||||
.unwrap();
|
||||
let mut fee_account = token_swap.pool_fee_account.clone();
|
||||
token_swap
|
||||
.withdraw_all(
|
||||
&mut fee_account,
|
||||
&mut withdrawn_token_a_account,
|
||||
&mut withdrawn_token_b_account,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let after_total_token_a = token_a_accounts
|
||||
.values()
|
||||
.map(get_token_balance)
|
||||
.sum::<u64>()
|
||||
+ get_token_balance(&withdrawn_token_a_account)
|
||||
+ get_token_balance(&token_swap.token_a_account);
|
||||
assert_eq!(before_total_token_a, after_total_token_a);
|
||||
let after_total_token_b = token_b_accounts
|
||||
.values()
|
||||
.map(get_token_balance)
|
||||
.sum::<u64>()
|
||||
+ get_token_balance(&withdrawn_token_b_account)
|
||||
+ get_token_balance(&token_swap.token_b_account);
|
||||
assert_eq!(before_total_token_b, after_total_token_b);
|
||||
}
|
||||
|
||||
fn run_fuzz_instruction(
|
||||
fuzz_instruction: FuzzInstruction,
|
||||
token_swap: &mut NativeTokenSwap,
|
||||
token_a_accounts: &mut HashMap<AccountId, NativeAccountData>,
|
||||
token_b_accounts: &mut HashMap<AccountId, NativeAccountData>,
|
||||
pool_accounts: &mut HashMap<AccountId, NativeAccountData>,
|
||||
) {
|
||||
let result = match fuzz_instruction {
|
||||
FuzzInstruction::Swap {
|
||||
token_a_id,
|
||||
token_b_id,
|
||||
trade_direction,
|
||||
instruction,
|
||||
} => {
|
||||
let mut token_a_account = token_a_accounts
|
||||
.entry(token_a_id)
|
||||
.or_insert_with(|| token_swap.create_token_a_account(INITIAL_USER_TOKEN_A_AMOUNT));
|
||||
let mut token_b_account = token_b_accounts
|
||||
.entry(token_b_id)
|
||||
.or_insert_with(|| token_swap.create_token_b_account(INITIAL_USER_TOKEN_B_AMOUNT));
|
||||
match trade_direction {
|
||||
TradeDirection::AtoB => {
|
||||
token_swap.swap_a_to_b(&mut token_a_account, &mut token_b_account, instruction)
|
||||
}
|
||||
TradeDirection::BtoA => {
|
||||
token_swap.swap_b_to_a(&mut token_b_account, &mut token_a_account, instruction)
|
||||
}
|
||||
}
|
||||
}
|
||||
FuzzInstruction::Deposit {
|
||||
token_a_id,
|
||||
token_b_id,
|
||||
pool_token_id,
|
||||
instruction,
|
||||
} => {
|
||||
let mut token_a_account = token_a_accounts
|
||||
.entry(token_a_id)
|
||||
.or_insert_with(|| token_swap.create_token_a_account(INITIAL_USER_TOKEN_A_AMOUNT));
|
||||
let mut token_b_account = token_b_accounts
|
||||
.entry(token_b_id)
|
||||
.or_insert_with(|| token_swap.create_token_b_account(INITIAL_USER_TOKEN_B_AMOUNT));
|
||||
let mut pool_account = pool_accounts
|
||||
.entry(pool_token_id)
|
||||
.or_insert_with(|| token_swap.create_pool_account());
|
||||
token_swap.deposit(
|
||||
&mut token_a_account,
|
||||
&mut token_b_account,
|
||||
&mut pool_account,
|
||||
instruction,
|
||||
)
|
||||
}
|
||||
FuzzInstruction::Withdraw {
|
||||
token_a_id,
|
||||
token_b_id,
|
||||
pool_token_id,
|
||||
instruction,
|
||||
} => {
|
||||
let mut token_a_account = token_a_accounts
|
||||
.entry(token_a_id)
|
||||
.or_insert_with(|| token_swap.create_token_a_account(INITIAL_USER_TOKEN_A_AMOUNT));
|
||||
let mut token_b_account = token_b_accounts
|
||||
.entry(token_b_id)
|
||||
.or_insert_with(|| token_swap.create_token_b_account(INITIAL_USER_TOKEN_B_AMOUNT));
|
||||
let mut pool_account = pool_accounts
|
||||
.entry(pool_token_id)
|
||||
.or_insert_with(|| token_swap.create_pool_account());
|
||||
token_swap.withdraw(
|
||||
&mut pool_account,
|
||||
&mut token_a_account,
|
||||
&mut token_b_account,
|
||||
instruction,
|
||||
)
|
||||
}
|
||||
};
|
||||
result
|
||||
.map_err(|e| {
|
||||
if !(e == SwapError::CalculationFailure.into()
|
||||
|| e == SwapError::ConversionFailure.into()
|
||||
|| e == SwapError::FeeCalculationFailure.into()
|
||||
|| e == SwapError::ExceededSlippage.into()
|
||||
|| e == SwapError::ZeroTradingTokens.into()
|
||||
|| e == TokenError::InsufficientFunds.into())
|
||||
{
|
||||
Err(e).unwrap()
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn get_total_token_a_amount(fuzz_instructions: &[FuzzInstruction]) -> u64 {
|
||||
let mut token_a_ids = HashSet::new();
|
||||
for fuzz_instruction in fuzz_instructions.iter() {
|
||||
match fuzz_instruction {
|
||||
FuzzInstruction::Swap { token_a_id, .. } => token_a_ids.insert(token_a_id),
|
||||
FuzzInstruction::Deposit { token_a_id, .. } => token_a_ids.insert(token_a_id),
|
||||
FuzzInstruction::Withdraw { token_a_id, .. } => token_a_ids.insert(token_a_id),
|
||||
};
|
||||
}
|
||||
(token_a_ids.len() as u64) * INITIAL_USER_TOKEN_A_AMOUNT
|
||||
}
|
||||
|
||||
fn get_total_token_b_amount(fuzz_instructions: &[FuzzInstruction]) -> u64 {
|
||||
let mut token_b_ids = HashSet::new();
|
||||
for fuzz_instruction in fuzz_instructions.iter() {
|
||||
match fuzz_instruction {
|
||||
FuzzInstruction::Swap { token_b_id, .. } => token_b_ids.insert(token_b_id),
|
||||
FuzzInstruction::Deposit { token_b_id, .. } => token_b_ids.insert(token_b_id),
|
||||
FuzzInstruction::Withdraw { token_b_id, .. } => token_b_ids.insert(token_b_id),
|
||||
};
|
||||
}
|
||||
(token_b_ids.len() as u64) * INITIAL_USER_TOKEN_B_AMOUNT
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
pub mod native_account_data;
|
||||
pub mod native_processor;
|
||||
pub mod native_token;
|
||||
pub mod native_token_swap;
|
|
@ -0,0 +1,45 @@
|
|||
use solana_program::{account_info::AccountInfo, clock::Epoch, pubkey::Pubkey};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NativeAccountData {
|
||||
pub key: Pubkey,
|
||||
pub lamports: u64,
|
||||
pub data: Vec<u8>,
|
||||
pub program_id: Pubkey,
|
||||
pub is_signer: bool,
|
||||
}
|
||||
|
||||
impl NativeAccountData {
|
||||
pub fn new(size: usize, program_id: Pubkey) -> Self {
|
||||
Self {
|
||||
key: Pubkey::new_unique(),
|
||||
lamports: 0,
|
||||
data: vec![0; size],
|
||||
program_id,
|
||||
is_signer: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_account_info(account_info: &AccountInfo) -> Self {
|
||||
Self {
|
||||
key: *account_info.key,
|
||||
lamports: **account_info.lamports.borrow(),
|
||||
data: account_info.data.borrow().to_vec(),
|
||||
program_id: *account_info.owner,
|
||||
is_signer: account_info.is_signer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_account_info(&mut self) -> AccountInfo {
|
||||
AccountInfo::new(
|
||||
&self.key,
|
||||
self.is_signer,
|
||||
false,
|
||||
&mut self.lamports,
|
||||
&mut self.data[..],
|
||||
&self.program_id,
|
||||
false,
|
||||
Epoch::default(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
use crate::native_account_data::NativeAccountData;
|
||||
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction,
|
||||
program_error::ProgramError, program_stubs, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
struct TestSyscallStubs {}
|
||||
impl program_stubs::SyscallStubs for TestSyscallStubs {
|
||||
fn sol_invoke_signed(
|
||||
&self,
|
||||
instruction: &Instruction,
|
||||
account_infos: &[AccountInfo],
|
||||
signers_seeds: &[&[&[u8]]],
|
||||
) -> ProgramResult {
|
||||
let mut new_account_infos = vec![];
|
||||
|
||||
// mimic check for token program in accounts
|
||||
if !account_infos.iter().any(|x| *x.key == spl_token::id()) {
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
|
||||
for meta in instruction.accounts.iter() {
|
||||
for account_info in account_infos.iter() {
|
||||
if meta.pubkey == *account_info.key {
|
||||
let mut new_account_info = account_info.clone();
|
||||
for seeds in signers_seeds.iter() {
|
||||
let signer =
|
||||
Pubkey::create_program_address(&seeds, &spl_token_swap::id()).unwrap();
|
||||
if *account_info.key == signer {
|
||||
new_account_info.is_signer = true;
|
||||
}
|
||||
}
|
||||
new_account_infos.push(new_account_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spl_token::processor::Processor::process(
|
||||
&instruction.program_id,
|
||||
&new_account_infos,
|
||||
&instruction.data,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_syscall_stubs() {
|
||||
use std::sync::Once;
|
||||
static ONCE: Once = Once::new();
|
||||
|
||||
ONCE.call_once(|| {
|
||||
program_stubs::set_syscall_stubs(Box::new(TestSyscallStubs {}));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn do_process_instruction(instruction: Instruction, accounts: &[AccountInfo]) -> ProgramResult {
|
||||
test_syscall_stubs();
|
||||
|
||||
// approximate the logic in the actual runtime which runs the instruction
|
||||
// and only updates accounts if the instruction is successful
|
||||
let mut account_data = accounts
|
||||
.iter()
|
||||
.map(NativeAccountData::new_from_account_info)
|
||||
.collect::<Vec<_>>();
|
||||
let account_infos = account_data
|
||||
.iter_mut()
|
||||
.map(NativeAccountData::as_account_info)
|
||||
.collect::<Vec<_>>();
|
||||
let res = if instruction.program_id == spl_token_swap::id() {
|
||||
spl_token_swap::processor::Processor::process(
|
||||
&instruction.program_id,
|
||||
&account_infos,
|
||||
&instruction.data,
|
||||
)
|
||||
} else {
|
||||
spl_token::processor::Processor::process(
|
||||
&instruction.program_id,
|
||||
&account_infos,
|
||||
&instruction.data,
|
||||
)
|
||||
};
|
||||
|
||||
if res.is_ok() {
|
||||
let mut account_metas = instruction
|
||||
.accounts
|
||||
.iter()
|
||||
.zip(accounts)
|
||||
.map(|(account_meta, account)| (&account_meta.pubkey, account))
|
||||
.collect::<Vec<_>>();
|
||||
for account_info in account_infos.iter() {
|
||||
for account_meta in account_metas.iter_mut() {
|
||||
if account_info.key == account_meta.0 {
|
||||
let account = &mut account_meta.1;
|
||||
let mut lamports = account.lamports.borrow_mut();
|
||||
**lamports = **account_info.lamports.borrow();
|
||||
let mut data = account.data.borrow_mut();
|
||||
data.clone_from_slice(*account_info.data.borrow());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
use crate::native_account_data::NativeAccountData;
|
||||
|
||||
use spl_token::state::{Account as TokenAccount, AccountState as TokenAccountState, Mint};
|
||||
|
||||
use solana_program::{program_option::COption, program_pack::Pack, pubkey::Pubkey};
|
||||
|
||||
pub fn create_mint(owner: &Pubkey) -> NativeAccountData {
|
||||
let mut account_data = NativeAccountData::new(Mint::LEN, spl_token::id());
|
||||
let mint = Mint {
|
||||
is_initialized: true,
|
||||
mint_authority: COption::Some(*owner),
|
||||
..Default::default()
|
||||
};
|
||||
Mint::pack(mint, &mut account_data.data[..]).unwrap();
|
||||
account_data
|
||||
}
|
||||
|
||||
pub fn create_token_account(
|
||||
mint_account: &mut NativeAccountData,
|
||||
owner: &Pubkey,
|
||||
amount: u64,
|
||||
) -> NativeAccountData {
|
||||
let mut mint = Mint::unpack(&mint_account.data).unwrap();
|
||||
let mut account_data = NativeAccountData::new(TokenAccount::LEN, spl_token::id());
|
||||
let account = TokenAccount {
|
||||
state: TokenAccountState::Initialized,
|
||||
mint: mint_account.key,
|
||||
owner: *owner,
|
||||
amount,
|
||||
..Default::default()
|
||||
};
|
||||
mint.supply += amount;
|
||||
Mint::pack(mint, &mut mint_account.data[..]).unwrap();
|
||||
TokenAccount::pack(account, &mut account_data.data[..]).unwrap();
|
||||
account_data
|
||||
}
|
||||
|
||||
pub fn get_token_balance(account_data: &NativeAccountData) -> u64 {
|
||||
let account = TokenAccount::unpack(&account_data.data).unwrap();
|
||||
account.amount
|
||||
}
|
|
@ -0,0 +1,418 @@
|
|||
//! Helpers for working with swaps in a fuzzing environment
|
||||
|
||||
use crate::native_account_data::NativeAccountData;
|
||||
use crate::native_processor::do_process_instruction;
|
||||
use crate::native_token;
|
||||
|
||||
use spl_token_swap::{
|
||||
curve::base::SwapCurve,
|
||||
instruction::{self, Deposit, Swap, Withdraw},
|
||||
state::SwapInfo,
|
||||
};
|
||||
|
||||
use spl_token::instruction::approve;
|
||||
|
||||
use solana_program::{
|
||||
bpf_loader, entrypoint::ProgramResult, program_pack::Pack, pubkey::Pubkey, system_program,
|
||||
};
|
||||
|
||||
pub struct NativeTokenSwap {
|
||||
pub user_account: NativeAccountData,
|
||||
pub nonce: u8,
|
||||
pub authority_account: NativeAccountData,
|
||||
pub swap_curve: SwapCurve,
|
||||
pub swap_account: NativeAccountData,
|
||||
pub pool_mint_account: NativeAccountData,
|
||||
pub pool_fee_account: NativeAccountData,
|
||||
pub pool_token_account: NativeAccountData,
|
||||
pub token_a_account: NativeAccountData,
|
||||
pub token_a_mint_account: NativeAccountData,
|
||||
pub token_b_account: NativeAccountData,
|
||||
pub token_b_mint_account: NativeAccountData,
|
||||
pub token_program_account: NativeAccountData,
|
||||
}
|
||||
|
||||
pub fn create_program_account(program_id: Pubkey) -> NativeAccountData {
|
||||
let mut account_data = NativeAccountData::new(0, bpf_loader::id());
|
||||
account_data.key = program_id;
|
||||
account_data
|
||||
}
|
||||
|
||||
impl NativeTokenSwap {
|
||||
pub fn new(swap_curve: SwapCurve, token_a_amount: u64, token_b_amount: u64) -> Self {
|
||||
let mut user_account = NativeAccountData::new(0, system_program::id());
|
||||
user_account.is_signer = true;
|
||||
let mut swap_account = NativeAccountData::new(SwapInfo::LEN, spl_token_swap::id());
|
||||
let (authority_key, nonce) = Pubkey::find_program_address(
|
||||
&[&swap_account.key.to_bytes()[..]],
|
||||
&spl_token_swap::id(),
|
||||
);
|
||||
let mut authority_account = create_program_account(authority_key);
|
||||
let mut token_program_account = create_program_account(spl_token::id());
|
||||
|
||||
let mut pool_mint_account = native_token::create_mint(&authority_account.key);
|
||||
let mut pool_token_account =
|
||||
native_token::create_token_account(&mut pool_mint_account, &user_account.key, 0);
|
||||
let mut pool_fee_account =
|
||||
native_token::create_token_account(&mut pool_mint_account, &user_account.key, 0);
|
||||
let mut token_a_mint_account = native_token::create_mint(&user_account.key);
|
||||
let mut token_a_account = native_token::create_token_account(
|
||||
&mut token_a_mint_account,
|
||||
&authority_account.key,
|
||||
token_a_amount,
|
||||
);
|
||||
let mut token_b_mint_account = native_token::create_mint(&user_account.key);
|
||||
let mut token_b_account = native_token::create_token_account(
|
||||
&mut token_b_mint_account,
|
||||
&authority_account.key,
|
||||
token_b_amount,
|
||||
);
|
||||
|
||||
let init_instruction = instruction::initialize(
|
||||
&spl_token_swap::id(),
|
||||
&spl_token::id(),
|
||||
&swap_account.key,
|
||||
&authority_account.key,
|
||||
&token_a_account.key,
|
||||
&token_b_account.key,
|
||||
&pool_mint_account.key,
|
||||
&pool_fee_account.key,
|
||||
&pool_token_account.key,
|
||||
nonce,
|
||||
swap_curve.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
do_process_instruction(
|
||||
init_instruction,
|
||||
&[
|
||||
swap_account.as_account_info(),
|
||||
authority_account.as_account_info(),
|
||||
token_a_account.as_account_info(),
|
||||
token_b_account.as_account_info(),
|
||||
pool_mint_account.as_account_info(),
|
||||
pool_fee_account.as_account_info(),
|
||||
pool_token_account.as_account_info(),
|
||||
token_program_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
user_account,
|
||||
nonce,
|
||||
authority_account,
|
||||
swap_curve,
|
||||
swap_account,
|
||||
pool_mint_account,
|
||||
pool_fee_account,
|
||||
pool_token_account,
|
||||
token_a_account,
|
||||
token_a_mint_account,
|
||||
token_b_account,
|
||||
token_b_mint_account,
|
||||
token_program_account,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_pool_account(&mut self) -> NativeAccountData {
|
||||
native_token::create_token_account(&mut self.pool_mint_account, &self.user_account.key, 0)
|
||||
}
|
||||
|
||||
pub fn create_token_a_account(&mut self, amount: u64) -> NativeAccountData {
|
||||
native_token::create_token_account(
|
||||
&mut self.token_a_mint_account,
|
||||
&self.user_account.key,
|
||||
amount,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_token_b_account(&mut self, amount: u64) -> NativeAccountData {
|
||||
native_token::create_token_account(
|
||||
&mut self.token_b_mint_account,
|
||||
&self.user_account.key,
|
||||
amount,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn swap_a_to_b(
|
||||
&mut self,
|
||||
token_a_account: &mut NativeAccountData,
|
||||
token_b_account: &mut NativeAccountData,
|
||||
instruction: Swap,
|
||||
) -> ProgramResult {
|
||||
do_process_instruction(
|
||||
approve(
|
||||
&self.token_program_account.key,
|
||||
&token_a_account.key,
|
||||
&self.authority_account.key,
|
||||
&self.user_account.key,
|
||||
&[],
|
||||
instruction.amount_in,
|
||||
)
|
||||
.unwrap(),
|
||||
&[
|
||||
token_a_account.as_account_info(),
|
||||
self.authority_account.as_account_info(),
|
||||
self.user_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
let swap_instruction = instruction::swap(
|
||||
&spl_token_swap::id(),
|
||||
&spl_token::id(),
|
||||
&self.swap_account.key,
|
||||
&self.authority_account.key,
|
||||
&token_a_account.key,
|
||||
&self.token_a_account.key,
|
||||
&self.token_b_account.key,
|
||||
&token_b_account.key,
|
||||
&self.pool_mint_account.key,
|
||||
&self.pool_fee_account.key,
|
||||
Some(&self.pool_token_account.key),
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
do_process_instruction(
|
||||
swap_instruction,
|
||||
&[
|
||||
self.swap_account.as_account_info(),
|
||||
self.authority_account.as_account_info(),
|
||||
token_a_account.as_account_info(),
|
||||
self.token_a_account.as_account_info(),
|
||||
self.token_b_account.as_account_info(),
|
||||
token_b_account.as_account_info(),
|
||||
self.pool_mint_account.as_account_info(),
|
||||
self.pool_fee_account.as_account_info(),
|
||||
self.token_program_account.as_account_info(),
|
||||
self.pool_token_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn swap_b_to_a(
|
||||
&mut self,
|
||||
token_b_account: &mut NativeAccountData,
|
||||
token_a_account: &mut NativeAccountData,
|
||||
instruction: Swap,
|
||||
) -> ProgramResult {
|
||||
do_process_instruction(
|
||||
approve(
|
||||
&self.token_program_account.key,
|
||||
&token_b_account.key,
|
||||
&self.authority_account.key,
|
||||
&self.user_account.key,
|
||||
&[],
|
||||
instruction.amount_in,
|
||||
)
|
||||
.unwrap(),
|
||||
&[
|
||||
token_b_account.as_account_info(),
|
||||
self.authority_account.as_account_info(),
|
||||
self.user_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let swap_instruction = instruction::swap(
|
||||
&spl_token_swap::id(),
|
||||
&spl_token::id(),
|
||||
&self.swap_account.key,
|
||||
&self.authority_account.key,
|
||||
&token_b_account.key,
|
||||
&self.token_b_account.key,
|
||||
&self.token_a_account.key,
|
||||
&token_a_account.key,
|
||||
&self.pool_mint_account.key,
|
||||
&self.pool_fee_account.key,
|
||||
Some(&self.pool_token_account.key),
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
do_process_instruction(
|
||||
swap_instruction,
|
||||
&[
|
||||
self.swap_account.as_account_info(),
|
||||
self.authority_account.as_account_info(),
|
||||
token_b_account.as_account_info(),
|
||||
self.token_b_account.as_account_info(),
|
||||
self.token_a_account.as_account_info(),
|
||||
token_a_account.as_account_info(),
|
||||
self.pool_mint_account.as_account_info(),
|
||||
self.pool_fee_account.as_account_info(),
|
||||
self.token_program_account.as_account_info(),
|
||||
self.pool_token_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn deposit(
|
||||
&mut self,
|
||||
token_a_account: &mut NativeAccountData,
|
||||
token_b_account: &mut NativeAccountData,
|
||||
pool_account: &mut NativeAccountData,
|
||||
mut instruction: Deposit,
|
||||
) -> ProgramResult {
|
||||
do_process_instruction(
|
||||
approve(
|
||||
&self.token_program_account.key,
|
||||
&token_a_account.key,
|
||||
&self.authority_account.key,
|
||||
&self.user_account.key,
|
||||
&[],
|
||||
instruction.maximum_token_a_amount,
|
||||
)
|
||||
.unwrap(),
|
||||
&[
|
||||
token_a_account.as_account_info(),
|
||||
self.authority_account.as_account_info(),
|
||||
self.user_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
do_process_instruction(
|
||||
approve(
|
||||
&self.token_program_account.key,
|
||||
&token_b_account.key,
|
||||
&self.authority_account.key,
|
||||
&self.user_account.key,
|
||||
&[],
|
||||
instruction.maximum_token_b_amount,
|
||||
)
|
||||
.unwrap(),
|
||||
&[
|
||||
token_b_account.as_account_info(),
|
||||
self.authority_account.as_account_info(),
|
||||
self.user_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// special logic: if we only deposit 1 pool token, we can't withdraw it
|
||||
// because we incur a withdrawal fee, so we hack it to not be 1
|
||||
if instruction.pool_token_amount == 1 {
|
||||
instruction.pool_token_amount = 2;
|
||||
}
|
||||
|
||||
let deposit_instruction = instruction::deposit(
|
||||
&spl_token_swap::id(),
|
||||
&spl_token::id(),
|
||||
&self.swap_account.key,
|
||||
&self.authority_account.key,
|
||||
&token_a_account.key,
|
||||
&token_b_account.key,
|
||||
&self.token_a_account.key,
|
||||
&self.token_b_account.key,
|
||||
&self.pool_mint_account.key,
|
||||
&pool_account.key,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
do_process_instruction(
|
||||
deposit_instruction,
|
||||
&[
|
||||
self.swap_account.as_account_info(),
|
||||
self.authority_account.as_account_info(),
|
||||
token_a_account.as_account_info(),
|
||||
token_b_account.as_account_info(),
|
||||
self.token_a_account.as_account_info(),
|
||||
self.token_b_account.as_account_info(),
|
||||
self.pool_mint_account.as_account_info(),
|
||||
pool_account.as_account_info(),
|
||||
self.token_program_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn withdraw(
|
||||
&mut self,
|
||||
pool_account: &mut NativeAccountData,
|
||||
token_a_account: &mut NativeAccountData,
|
||||
token_b_account: &mut NativeAccountData,
|
||||
mut instruction: Withdraw,
|
||||
) -> ProgramResult {
|
||||
let pool_token_amount = native_token::get_token_balance(&pool_account);
|
||||
// special logic to avoid withdrawing down to 1 pool token, which
|
||||
// eventually causes an error on withdrawing all
|
||||
if pool_token_amount.saturating_sub(instruction.pool_token_amount) == 1 {
|
||||
instruction.pool_token_amount = pool_token_amount;
|
||||
}
|
||||
do_process_instruction(
|
||||
approve(
|
||||
&self.token_program_account.key,
|
||||
&pool_account.key,
|
||||
&self.authority_account.key,
|
||||
&self.user_account.key,
|
||||
&[],
|
||||
instruction.pool_token_amount,
|
||||
)
|
||||
.unwrap(),
|
||||
&[
|
||||
pool_account.as_account_info(),
|
||||
self.authority_account.as_account_info(),
|
||||
self.user_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let withdraw_instruction = instruction::withdraw(
|
||||
&spl_token_swap::id(),
|
||||
&spl_token::id(),
|
||||
&self.swap_account.key,
|
||||
&self.authority_account.key,
|
||||
&self.pool_mint_account.key,
|
||||
&self.pool_fee_account.key,
|
||||
&pool_account.key,
|
||||
&self.token_a_account.key,
|
||||
&self.token_b_account.key,
|
||||
&token_a_account.key,
|
||||
&token_b_account.key,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
do_process_instruction(
|
||||
withdraw_instruction,
|
||||
&[
|
||||
self.swap_account.as_account_info(),
|
||||
self.authority_account.as_account_info(),
|
||||
self.pool_mint_account.as_account_info(),
|
||||
pool_account.as_account_info(),
|
||||
self.token_a_account.as_account_info(),
|
||||
self.token_b_account.as_account_info(),
|
||||
token_a_account.as_account_info(),
|
||||
token_b_account.as_account_info(),
|
||||
self.pool_fee_account.as_account_info(),
|
||||
self.token_program_account.as_account_info(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn withdraw_all(
|
||||
&mut self,
|
||||
mut pool_account: &mut NativeAccountData,
|
||||
mut token_a_account: &mut NativeAccountData,
|
||||
mut token_b_account: &mut NativeAccountData,
|
||||
) -> ProgramResult {
|
||||
let pool_token_amount = native_token::get_token_balance(&pool_account);
|
||||
if pool_token_amount > 0 {
|
||||
let instruction = Withdraw {
|
||||
pool_token_amount,
|
||||
minimum_token_a_amount: 0,
|
||||
minimum_token_b_amount: 0,
|
||||
};
|
||||
self.withdraw(
|
||||
&mut pool_account,
|
||||
&mut token_a_account,
|
||||
&mut token_b_account,
|
||||
instruction,
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@ impl Default for SwapCurve {
|
|||
/// Clone takes advantage of pack / unpack to get around the difficulty of
|
||||
/// cloning dynamic objects.
|
||||
/// Note that this is only to be used for testing.
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "fuzz"))]
|
||||
impl Clone for SwapCurve {
|
||||
fn clone(&self) -> Self {
|
||||
let mut packed_self = [0u8; Self::LEN];
|
||||
|
|
|
@ -13,6 +13,59 @@ use solana_program::{
|
|||
use std::convert::TryInto;
|
||||
use std::mem::size_of;
|
||||
|
||||
#[cfg(feature = "fuzz")]
|
||||
use arbitrary::Arbitrary;
|
||||
|
||||
/// Initialize instruction data
|
||||
#[repr(C)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Initialize {
|
||||
/// nonce used to create valid program address
|
||||
pub nonce: u8,
|
||||
/// swap curve info for pool, including CurveType, fees, and anything
|
||||
/// else that may be required
|
||||
pub swap_curve: SwapCurve,
|
||||
}
|
||||
|
||||
/// Swap instruction data
|
||||
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Swap {
|
||||
/// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate
|
||||
pub amount_in: u64,
|
||||
/// Minimum amount of DESTINATION token to output, prevents excessive slippage
|
||||
pub minimum_amount_out: u64,
|
||||
}
|
||||
|
||||
/// Deposit instruction data
|
||||
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Deposit {
|
||||
/// Pool token amount to transfer. token_a and token_b amount are set by
|
||||
/// the current exchange rate and size of the pool
|
||||
pub pool_token_amount: u64,
|
||||
/// Maximum token A amount to deposit, prevents excessive slippage
|
||||
pub maximum_token_a_amount: u64,
|
||||
/// Maximum token B amount to deposit, prevents excessive slippage
|
||||
pub maximum_token_b_amount: u64,
|
||||
}
|
||||
|
||||
/// Withdraw instruction data
|
||||
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Withdraw {
|
||||
/// Amount of pool tokens to burn. User receives an output of token a
|
||||
/// and b based on the percentage of the pool tokens that are returned.
|
||||
pub pool_token_amount: u64,
|
||||
/// Minimum amount of token A to receive, prevents excessive slippage
|
||||
pub minimum_token_a_amount: u64,
|
||||
/// Minimum amount of token B to receive, prevents excessive slippage
|
||||
pub minimum_token_b_amount: u64,
|
||||
}
|
||||
|
||||
/// Instructions supported by the SwapInfo program.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
@ -29,13 +82,7 @@ pub enum SwapInstruction {
|
|||
/// 6. `[writable]` Pool Token Account to deposit the initial pool token
|
||||
/// supply. Must be empty, not owned by $authority.
|
||||
/// 7. '[]` Token program id
|
||||
Initialize {
|
||||
/// nonce used to create valid program address
|
||||
nonce: u8,
|
||||
/// swap curve info for pool, including CurveType, fees, and anything
|
||||
/// else that may be required
|
||||
swap_curve: SwapCurve,
|
||||
},
|
||||
Initialize(Initialize),
|
||||
|
||||
/// Swap the tokens in the pool.
|
||||
///
|
||||
|
@ -49,12 +96,7 @@ pub enum SwapInstruction {
|
|||
/// 7. `[writable]` Fee account, to receive trading fees
|
||||
/// 8. '[]` Token program id
|
||||
/// 9. `[optional, writable]` Host fee account to receive additional trading fees
|
||||
Swap {
|
||||
/// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate
|
||||
amount_in: u64,
|
||||
/// Minimum amount of DESTINATION token to output, prevents excessive slippage
|
||||
minimum_amount_out: u64,
|
||||
},
|
||||
Swap(Swap),
|
||||
|
||||
/// Deposit some tokens into the pool. The output is a "pool" token representing ownership
|
||||
/// into the pool. Inputs are converted to the current ratio.
|
||||
|
@ -68,15 +110,7 @@ pub enum SwapInstruction {
|
|||
/// 6. `[writable]` Pool MINT account, $authority is the owner.
|
||||
/// 7. `[writable]` Pool Account to deposit the generated tokens, user is the owner.
|
||||
/// 8. '[]` Token program id
|
||||
Deposit {
|
||||
/// Pool token amount to transfer. token_a and token_b amount are set by
|
||||
/// the current exchange rate and size of the pool
|
||||
pool_token_amount: u64,
|
||||
/// Maximum token A amount to deposit, prevents excessive slippage
|
||||
maximum_token_a_amount: u64,
|
||||
/// Maximum token B amount to deposit, prevents excessive slippage
|
||||
maximum_token_b_amount: u64,
|
||||
},
|
||||
Deposit(Deposit),
|
||||
|
||||
/// Withdraw the token from the pool at the current ratio.
|
||||
///
|
||||
|
@ -90,15 +124,7 @@ pub enum SwapInstruction {
|
|||
/// 7. `[writable]` token_b user Account to credit.
|
||||
/// 8. `[writable]` Fee account, to receive withdrawal fees
|
||||
/// 9. '[]` Token program id
|
||||
Withdraw {
|
||||
/// Amount of pool tokens to burn. User receives an output of token a
|
||||
/// and b based on the percentage of the pool tokens that are returned.
|
||||
pool_token_amount: u64,
|
||||
/// Minimum amount of token A to receive, prevents excessive slippage
|
||||
minimum_token_a_amount: u64,
|
||||
/// Minimum amount of token B to receive, prevents excessive slippage
|
||||
minimum_token_b_amount: u64,
|
||||
},
|
||||
Withdraw(Withdraw),
|
||||
}
|
||||
|
||||
impl SwapInstruction {
|
||||
|
@ -109,35 +135,35 @@ impl SwapInstruction {
|
|||
0 => {
|
||||
let (&nonce, rest) = rest.split_first().ok_or(SwapError::InvalidInstruction)?;
|
||||
let swap_curve = SwapCurve::unpack_unchecked(rest)?;
|
||||
Self::Initialize { nonce, swap_curve }
|
||||
Self::Initialize(Initialize { nonce, swap_curve })
|
||||
}
|
||||
1 => {
|
||||
let (amount_in, rest) = Self::unpack_u64(rest)?;
|
||||
let (minimum_amount_out, _rest) = Self::unpack_u64(rest)?;
|
||||
Self::Swap {
|
||||
Self::Swap(Swap {
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
}
|
||||
})
|
||||
}
|
||||
2 => {
|
||||
let (pool_token_amount, rest) = Self::unpack_u64(rest)?;
|
||||
let (maximum_token_a_amount, rest) = Self::unpack_u64(rest)?;
|
||||
let (maximum_token_b_amount, _rest) = Self::unpack_u64(rest)?;
|
||||
Self::Deposit {
|
||||
Self::Deposit(Deposit {
|
||||
pool_token_amount,
|
||||
maximum_token_a_amount,
|
||||
maximum_token_b_amount,
|
||||
}
|
||||
})
|
||||
}
|
||||
3 => {
|
||||
let (pool_token_amount, rest) = Self::unpack_u64(rest)?;
|
||||
let (minimum_token_a_amount, rest) = Self::unpack_u64(rest)?;
|
||||
let (minimum_token_b_amount, _rest) = Self::unpack_u64(rest)?;
|
||||
Self::Withdraw {
|
||||
Self::Withdraw(Withdraw {
|
||||
pool_token_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => return Err(SwapError::InvalidInstruction.into()),
|
||||
})
|
||||
|
@ -161,36 +187,36 @@ impl SwapInstruction {
|
|||
pub fn pack(&self) -> Vec<u8> {
|
||||
let mut buf = Vec::with_capacity(size_of::<Self>());
|
||||
match &*self {
|
||||
Self::Initialize { nonce, swap_curve } => {
|
||||
Self::Initialize(Initialize { nonce, swap_curve }) => {
|
||||
buf.push(0);
|
||||
buf.push(*nonce);
|
||||
let mut swap_curve_slice = [0u8; SwapCurve::LEN];
|
||||
Pack::pack_into_slice(swap_curve, &mut swap_curve_slice[..]);
|
||||
buf.extend_from_slice(&swap_curve_slice);
|
||||
}
|
||||
Self::Swap {
|
||||
Self::Swap(Swap {
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
} => {
|
||||
}) => {
|
||||
buf.push(1);
|
||||
buf.extend_from_slice(&amount_in.to_le_bytes());
|
||||
buf.extend_from_slice(&minimum_amount_out.to_le_bytes());
|
||||
}
|
||||
Self::Deposit {
|
||||
Self::Deposit(Deposit {
|
||||
pool_token_amount,
|
||||
maximum_token_a_amount,
|
||||
maximum_token_b_amount,
|
||||
} => {
|
||||
}) => {
|
||||
buf.push(2);
|
||||
buf.extend_from_slice(&pool_token_amount.to_le_bytes());
|
||||
buf.extend_from_slice(&maximum_token_a_amount.to_le_bytes());
|
||||
buf.extend_from_slice(&maximum_token_b_amount.to_le_bytes());
|
||||
}
|
||||
Self::Withdraw {
|
||||
Self::Withdraw(Withdraw {
|
||||
pool_token_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
} => {
|
||||
}) => {
|
||||
buf.push(3);
|
||||
buf.extend_from_slice(&pool_token_amount.to_le_bytes());
|
||||
buf.extend_from_slice(&minimum_token_a_amount.to_le_bytes());
|
||||
|
@ -215,7 +241,7 @@ pub fn initialize(
|
|||
nonce: u8,
|
||||
swap_curve: SwapCurve,
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
let init_data = SwapInstruction::Initialize { nonce, swap_curve };
|
||||
let init_data = SwapInstruction::Initialize(Initialize { nonce, swap_curve });
|
||||
let data = init_data.pack();
|
||||
|
||||
let accounts = vec![
|
||||
|
@ -248,16 +274,9 @@ pub fn deposit(
|
|||
swap_token_b_pubkey: &Pubkey,
|
||||
pool_mint_pubkey: &Pubkey,
|
||||
destination_pubkey: &Pubkey,
|
||||
pool_token_amount: u64,
|
||||
maximum_token_a_amount: u64,
|
||||
maximum_token_b_amount: u64,
|
||||
instruction: Deposit,
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
let data = SwapInstruction::Deposit {
|
||||
pool_token_amount,
|
||||
maximum_token_a_amount,
|
||||
maximum_token_b_amount,
|
||||
}
|
||||
.pack();
|
||||
let data = SwapInstruction::Deposit(instruction).pack();
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*swap_pubkey, false),
|
||||
|
@ -291,16 +310,9 @@ pub fn withdraw(
|
|||
swap_token_b_pubkey: &Pubkey,
|
||||
destination_token_a_pubkey: &Pubkey,
|
||||
destination_token_b_pubkey: &Pubkey,
|
||||
pool_token_amount: u64,
|
||||
minimum_token_a_amount: u64,
|
||||
minimum_token_b_amount: u64,
|
||||
instruction: Withdraw,
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
let data = SwapInstruction::Withdraw {
|
||||
pool_token_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
}
|
||||
.pack();
|
||||
let data = SwapInstruction::Withdraw(instruction).pack();
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*swap_pubkey, false),
|
||||
|
@ -335,14 +347,9 @@ pub fn swap(
|
|||
pool_mint_pubkey: &Pubkey,
|
||||
pool_fee_pubkey: &Pubkey,
|
||||
host_fee_pubkey: Option<&Pubkey>,
|
||||
amount_in: u64,
|
||||
minimum_amount_out: u64,
|
||||
instruction: Swap,
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
let data = SwapInstruction::Swap {
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
}
|
||||
.pack();
|
||||
let data = SwapInstruction::Swap(instruction).pack();
|
||||
|
||||
let mut accounts = vec![
|
||||
AccountMeta::new_readonly(*swap_pubkey, false),
|
||||
|
@ -411,7 +418,7 @@ mod tests {
|
|||
curve_type,
|
||||
calculator,
|
||||
};
|
||||
let check = SwapInstruction::Initialize { nonce, swap_curve };
|
||||
let check = SwapInstruction::Initialize(Initialize { nonce, swap_curve });
|
||||
let packed = check.pack();
|
||||
let mut expect = vec![];
|
||||
expect.push(0u8);
|
||||
|
@ -432,10 +439,10 @@ mod tests {
|
|||
|
||||
let amount_in: u64 = 2;
|
||||
let minimum_amount_out: u64 = 10;
|
||||
let check = SwapInstruction::Swap {
|
||||
let check = SwapInstruction::Swap(Swap {
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
};
|
||||
});
|
||||
let packed = check.pack();
|
||||
let mut expect = vec![1];
|
||||
expect.extend_from_slice(&amount_in.to_le_bytes());
|
||||
|
@ -447,11 +454,11 @@ mod tests {
|
|||
let pool_token_amount: u64 = 5;
|
||||
let maximum_token_a_amount: u64 = 10;
|
||||
let maximum_token_b_amount: u64 = 20;
|
||||
let check = SwapInstruction::Deposit {
|
||||
let check = SwapInstruction::Deposit(Deposit {
|
||||
pool_token_amount,
|
||||
maximum_token_a_amount,
|
||||
maximum_token_b_amount,
|
||||
};
|
||||
});
|
||||
let packed = check.pack();
|
||||
let mut expect = vec![2];
|
||||
expect.extend_from_slice(&pool_token_amount.to_le_bytes());
|
||||
|
@ -464,11 +471,11 @@ mod tests {
|
|||
let pool_token_amount: u64 = 1212438012089;
|
||||
let minimum_token_a_amount: u64 = 102198761982612;
|
||||
let minimum_token_b_amount: u64 = 2011239855213;
|
||||
let check = SwapInstruction::Withdraw {
|
||||
let check = SwapInstruction::Withdraw(Withdraw {
|
||||
pool_token_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
};
|
||||
});
|
||||
let packed = check.pack();
|
||||
let mut expect = vec![3];
|
||||
expect.extend_from_slice(&pool_token_amount.to_le_bytes());
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
use crate::constraints::{FeeConstraints, FEE_CONSTRAINTS};
|
||||
use crate::{
|
||||
curve::base::SwapCurve, error::SwapError, instruction::SwapInstruction, state::SwapInfo,
|
||||
curve::base::SwapCurve,
|
||||
error::SwapError,
|
||||
instruction::{Deposit, Initialize, Swap, SwapInstruction, Withdraw},
|
||||
state::SwapInfo,
|
||||
};
|
||||
use num_traits::FromPrimitive;
|
||||
use solana_program::{
|
||||
|
@ -550,7 +553,8 @@ impl Processor {
|
|||
to_u128(token_a.amount)?,
|
||||
)
|
||||
.ok_or(SwapError::ZeroTradingTokens)?;
|
||||
if a_amount < to_u128(minimum_token_a_amount)? {
|
||||
let a_amount = to_u64(a_amount)?;
|
||||
if a_amount < minimum_token_a_amount {
|
||||
return Err(SwapError::ExceededSlippage.into());
|
||||
}
|
||||
let b_amount = calculator
|
||||
|
@ -572,7 +576,7 @@ impl Processor {
|
|||
dest_token_a_info.clone(),
|
||||
authority_info.clone(),
|
||||
token_swap.nonce,
|
||||
to_u64(a_amount)?,
|
||||
a_amount,
|
||||
)?;
|
||||
Self::token_transfer(
|
||||
swap_info.key,
|
||||
|
@ -620,22 +624,22 @@ impl Processor {
|
|||
) -> ProgramResult {
|
||||
let instruction = SwapInstruction::unpack(input)?;
|
||||
match instruction {
|
||||
SwapInstruction::Initialize { nonce, swap_curve } => {
|
||||
SwapInstruction::Initialize(Initialize { nonce, swap_curve }) => {
|
||||
info!("Instruction: Init");
|
||||
Self::process_initialize(program_id, nonce, swap_curve, accounts, fee_constraints)
|
||||
}
|
||||
SwapInstruction::Swap {
|
||||
SwapInstruction::Swap(Swap {
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
} => {
|
||||
}) => {
|
||||
info!("Instruction: Swap");
|
||||
Self::process_swap(program_id, amount_in, minimum_amount_out, accounts)
|
||||
}
|
||||
SwapInstruction::Deposit {
|
||||
SwapInstruction::Deposit(Deposit {
|
||||
pool_token_amount,
|
||||
maximum_token_a_amount,
|
||||
maximum_token_b_amount,
|
||||
} => {
|
||||
}) => {
|
||||
info!("Instruction: Deposit");
|
||||
Self::process_deposit(
|
||||
program_id,
|
||||
|
@ -645,11 +649,11 @@ impl Processor {
|
|||
accounts,
|
||||
)
|
||||
}
|
||||
SwapInstruction::Withdraw {
|
||||
SwapInstruction::Withdraw(Withdraw {
|
||||
pool_token_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
} => {
|
||||
}) => {
|
||||
info!("Instruction: Withdraw");
|
||||
Self::process_withdraw(
|
||||
program_id,
|
||||
|
@ -1039,8 +1043,10 @@ mod tests {
|
|||
&self.pool_mint_key,
|
||||
&self.pool_fee_key,
|
||||
None,
|
||||
Swap {
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -1072,9 +1078,9 @@ mod tests {
|
|||
mut depositor_token_b_account: &mut Account,
|
||||
depositor_pool_key: &Pubkey,
|
||||
mut depositor_pool_account: &mut Account,
|
||||
pool_amount: u64,
|
||||
amount_a: u64,
|
||||
amount_b: u64,
|
||||
pool_token_amount: u64,
|
||||
maximum_token_a_amount: u64,
|
||||
maximum_token_b_amount: u64,
|
||||
) -> ProgramResult {
|
||||
do_process_instruction(
|
||||
approve(
|
||||
|
@ -1083,7 +1089,7 @@ mod tests {
|
|||
&self.authority_key,
|
||||
&depositor_key,
|
||||
&[],
|
||||
amount_a,
|
||||
maximum_token_a_amount,
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -1101,7 +1107,7 @@ mod tests {
|
|||
&self.authority_key,
|
||||
&depositor_key,
|
||||
&[],
|
||||
amount_b,
|
||||
maximum_token_b_amount,
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -1124,9 +1130,11 @@ mod tests {
|
|||
&self.token_b_key,
|
||||
&self.pool_mint_key,
|
||||
&depositor_pool_key,
|
||||
pool_amount,
|
||||
amount_a,
|
||||
amount_b,
|
||||
Deposit {
|
||||
pool_token_amount,
|
||||
maximum_token_a_amount,
|
||||
maximum_token_b_amount,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -1153,9 +1161,9 @@ mod tests {
|
|||
mut token_a_account: &mut Account,
|
||||
token_b_key: &Pubkey,
|
||||
mut token_b_account: &mut Account,
|
||||
pool_amount: u64,
|
||||
minimum_a_amount: u64,
|
||||
minimum_b_amount: u64,
|
||||
pool_token_amount: u64,
|
||||
minimum_token_a_amount: u64,
|
||||
minimum_token_b_amount: u64,
|
||||
) -> ProgramResult {
|
||||
// approve swap program to take out pool tokens
|
||||
do_process_instruction(
|
||||
|
@ -1165,7 +1173,7 @@ mod tests {
|
|||
&self.authority_key,
|
||||
&user_key,
|
||||
&[],
|
||||
pool_amount,
|
||||
pool_token_amount,
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -1190,9 +1198,11 @@ mod tests {
|
|||
&self.token_b_key,
|
||||
&token_a_key,
|
||||
&token_b_key,
|
||||
pool_amount,
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
Withdraw {
|
||||
pool_token_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -2357,9 +2367,11 @@ mod tests {
|
|||
&accounts.token_b_key,
|
||||
&accounts.pool_mint_key,
|
||||
&pool_key,
|
||||
pool_amount.try_into().unwrap(),
|
||||
deposit_a,
|
||||
deposit_b,
|
||||
Deposit {
|
||||
pool_token_amount: pool_amount.try_into().unwrap(),
|
||||
maximum_token_a_amount: deposit_a,
|
||||
maximum_token_b_amount: deposit_b,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -2402,9 +2414,11 @@ mod tests {
|
|||
&accounts.token_b_key,
|
||||
&accounts.pool_mint_key,
|
||||
&pool_key,
|
||||
pool_amount.try_into().unwrap(),
|
||||
deposit_a,
|
||||
deposit_b,
|
||||
Deposit {
|
||||
pool_token_amount: pool_amount.try_into().unwrap(),
|
||||
maximum_token_a_amount: deposit_a,
|
||||
maximum_token_b_amount: deposit_b,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -2705,8 +2719,8 @@ mod tests {
|
|||
let initial_b = token_b_amount / 10;
|
||||
let initial_pool = swap_curve.calculator.new_pool_supply() / 10;
|
||||
let withdraw_amount = initial_pool / 4;
|
||||
let minimum_a_amount = initial_a / 40;
|
||||
let minimum_b_amount = initial_b / 40;
|
||||
let minimum_token_a_amount = initial_a / 40;
|
||||
let minimum_token_b_amount = initial_b / 40;
|
||||
|
||||
let mut accounts =
|
||||
SwapAccountInfo::new(&user_key, swap_curve, token_a_amount, token_b_amount);
|
||||
|
@ -2732,8 +2746,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2767,8 +2781,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
accounts.authority_key = old_authority;
|
||||
|
@ -2801,8 +2815,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount / 2,
|
||||
minimum_b_amount / 2,
|
||||
minimum_token_a_amount / 2,
|
||||
minimum_token_b_amount / 2,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2834,8 +2848,8 @@ mod tests {
|
|||
&token_a_key,
|
||||
&mut token_a_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2881,8 +2895,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2932,8 +2946,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
),
|
||||
);
|
||||
accounts.pool_fee_account = old_pool_fee_account;
|
||||
|
@ -2971,9 +2985,11 @@ mod tests {
|
|||
&accounts.token_b_key,
|
||||
&token_a_key,
|
||||
&token_b_key,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
Withdraw {
|
||||
pool_token_amount: withdraw_amount.try_into().unwrap(),
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
}
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -3024,9 +3040,11 @@ mod tests {
|
|||
&accounts.token_b_key,
|
||||
&token_a_key,
|
||||
&token_b_key,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
Withdraw {
|
||||
pool_token_amount: withdraw_amount.try_into().unwrap(),
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -3080,8 +3098,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -3106,8 +3124,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -3149,8 +3167,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -3185,8 +3203,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
1,
|
||||
minimum_a_amount * 10,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount * 10,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -3219,8 +3237,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount * 10,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount * 10,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
// minimum B amount out too high
|
||||
|
@ -3235,8 +3253,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount * 10,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount * 10,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -3270,8 +3288,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
let swap_token_b_key = accounts.token_b_key;
|
||||
|
@ -3287,8 +3305,8 @@ mod tests {
|
|||
&swap_token_b_key,
|
||||
&mut swap_token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -3320,8 +3338,8 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
withdraw_amount.try_into().unwrap(),
|
||||
minimum_a_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_a_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -3479,7 +3497,7 @@ mod tests {
|
|||
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
||||
// swap one way
|
||||
let a_to_b_amount = initial_a / 10;
|
||||
let minimum_b_amount = 0;
|
||||
let minimum_token_b_amount = 0;
|
||||
let pool_mint = Processor::unpack_mint(&accounts.pool_mint_account.data).unwrap();
|
||||
let initial_supply = pool_mint.supply;
|
||||
accounts
|
||||
|
@ -3492,7 +3510,7 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
a_to_b_amount,
|
||||
minimum_b_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -3772,8 +3790,10 @@ mod tests {
|
|||
&accounts.pool_mint_key,
|
||||
&accounts.pool_fee_key,
|
||||
Some(&pool_key),
|
||||
Swap {
|
||||
amount_in,
|
||||
minimum_amount_out,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -3837,7 +3857,7 @@ mod tests {
|
|||
|
||||
let initial_a = token_a_amount / 5;
|
||||
let initial_b = token_b_amount / 5;
|
||||
let minimum_b_amount = initial_b / 2;
|
||||
let minimum_token_b_amount = initial_b / 2;
|
||||
|
||||
let swap_token_a_key = accounts.token_a_key;
|
||||
let swap_token_b_key = accounts.token_b_key;
|
||||
|
@ -3863,7 +3883,7 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -3897,7 +3917,7 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
accounts.authority_key = old_authority;
|
||||
|
@ -3929,8 +3949,10 @@ mod tests {
|
|||
&accounts.pool_mint_key,
|
||||
&accounts.pool_fee_key,
|
||||
None,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
Swap {
|
||||
amount_in: initial_a,
|
||||
minimum_amount_out: minimum_token_b_amount,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -3969,7 +3991,7 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
initial_a * 2,
|
||||
minimum_b_amount * 2,
|
||||
minimum_token_b_amount * 2,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -3999,8 +4021,10 @@ mod tests {
|
|||
&accounts.pool_mint_key,
|
||||
&accounts.pool_fee_key,
|
||||
None,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
Swap {
|
||||
amount_in: initial_a,
|
||||
minimum_amount_out: minimum_token_b_amount,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -4039,7 +4063,7 @@ mod tests {
|
|||
&token_a_key,
|
||||
&mut token_a_account,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -4065,7 +4089,7 @@ mod tests {
|
|||
&token_a_key,
|
||||
&mut token_a_account,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -4098,7 +4122,7 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -4131,7 +4155,7 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
accounts.pool_fee_account = old_pool_fee_account;
|
||||
|
@ -4163,8 +4187,10 @@ mod tests {
|
|||
&accounts.pool_mint_key,
|
||||
&accounts.pool_fee_key,
|
||||
None,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
Swap {
|
||||
amount_in: initial_a,
|
||||
minimum_amount_out: minimum_token_b_amount,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -4229,7 +4255,7 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
initial_a,
|
||||
minimum_b_amount * 2,
|
||||
minimum_token_b_amount * 2,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -4257,7 +4283,7 @@ mod tests {
|
|||
&token_b_key,
|
||||
&mut token_b_account,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
let mut swap_token_b_account = accounts.get_token_account(&swap_token_b_key).clone();
|
||||
|
@ -4272,7 +4298,7 @@ mod tests {
|
|||
&swap_token_b_key,
|
||||
&mut swap_token_b_account,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
minimum_token_b_amount,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -4308,8 +4334,10 @@ mod tests {
|
|||
&accounts.pool_mint_key,
|
||||
&accounts.pool_fee_key,
|
||||
None,
|
||||
initial_a,
|
||||
minimum_b_amount,
|
||||
Swap {
|
||||
amount_in: initial_a,
|
||||
minimum_amount_out: minimum_token_b_amount,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
@ -4369,8 +4397,10 @@ mod tests {
|
|||
&accounts.pool_mint_key,
|
||||
&accounts.pool_fee_key,
|
||||
Some(&bad_token_a_key),
|
||||
initial_a,
|
||||
0,
|
||||
Swap {
|
||||
amount_in: initial_a,
|
||||
minimum_amount_out: 0,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
|
|
Loading…
Reference in New Issue