diff --git a/amm/drink-tests/src/lib.rs b/amm/drink-tests/src/lib.rs index 0681550..d4a9d82 100644 --- a/amm/drink-tests/src/lib.rs +++ b/amm/drink-tests/src/lib.rs @@ -1,9 +1,9 @@ #[cfg(test)] -mod psp22; -#[cfg(test)] #[allow(unused_imports)] mod mock_sazero_rate_contract; #[cfg(test)] +mod psp22; +#[cfg(test)] mod stable_pool_contract; #[cfg(test)] mod stable_swap_tests; diff --git a/amm/drink-tests/src/stable_swap_tests/mod.rs b/amm/drink-tests/src/stable_swap_tests/mod.rs index 650a86d..143e50d 100644 --- a/amm/drink-tests/src/stable_swap_tests/mod.rs +++ b/amm/drink-tests/src/stable_swap_tests/mod.rs @@ -1,4 +1,9 @@ -#[allow(dead_code)] +mod tests_add_remove_lp; +mod tests_getters; +mod tests_rated; +mod tests_swap_exact_in_received; +mod tests_swap_exact_out; + use crate::stable_pool_contract; pub use crate::utils::*; use primitive_types::U256; @@ -35,6 +40,7 @@ pub fn setup_stable_swap_with_tokens( trade_fee: u32, protocol_trade_fee: u32, caller: AccountId32, + salt: Vec, ) -> (AccountId, Vec) { let _ = session.set_actor(caller); @@ -44,6 +50,7 @@ pub fn setup_stable_swap_with_tokens( upload_all(session); + let salty_str = String::from_utf8(salt.clone()).unwrap_or("Test token".to_string()); // instantiate tokens let tokens: Vec = token_decimals .iter() @@ -52,7 +59,7 @@ pub fn setup_stable_swap_with_tokens( .map(|(id, (&decimals, &supply))| { psp22_utils::setup_with_amounts( session, - format!("Test Token {id}").to_string(), + format!("{salty_str} {id}").to_string(), decimals, supply, BOB, @@ -70,7 +77,8 @@ pub fn setup_stable_swap_with_tokens( trade_fee, protocol_trade_fee, Some(fee_receiver()), - ); + ) + .with_salt(salt); let stable_swap: stable_pool_contract::Instance = session .instantiate(instance) @@ -91,15 +99,11 @@ pub fn setup_stable_swap_with_tokens( pub fn share_price_and_total_shares( session: &mut Session, stable_swap: AccountId, - token_rates: Option>, ) -> (u128, u128) { let total_shares = psp22_utils::total_supply(session, stable_swap); let reserves = stable_swap::reserves(session, stable_swap); - let token_rates: Vec = if let Some(rates) = token_rates { - rates - } else { - reserves.iter().map(|_| RATE_PRECISION).collect() - }; + let token_rates = stable_swap::token_rates(session, stable_swap); + let sum_token = stable_swap::tokens(session, stable_swap) .iter() .zip(reserves.iter()) @@ -116,7 +120,7 @@ pub fn share_price_and_total_shares( .checked_mul(100000000.into()) .unwrap() .checked_div(total_shares.into()) - .unwrap_or(100000000.into()) + .unwrap_or(0.into()) // return 0 if total shares 0 .as_u128(), total_shares, ) diff --git a/amm/drink-tests/src/stable_swap_tests/tests_add_remove_lp.rs b/amm/drink-tests/src/stable_swap_tests/tests_add_remove_lp.rs new file mode 100644 index 0000000..2606cad --- /dev/null +++ b/amm/drink-tests/src/stable_swap_tests/tests_add_remove_lp.rs @@ -0,0 +1,1227 @@ +use drink::{self, session::Session}; +use stable_pool_contract::MathError; + +use super::*; + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/tests/test_stable_pool.rs#L123 +#[drink::test] +fn test_01(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + let initial_reserves = vec![100000 * ONE_DAI, 100000 * ONE_USDT, 100000 * ONE_USDC]; + let initial_supply = initial_reserves + .iter() + .map(|amount| amount * 100_000_000_000) + .collect::>(); + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![18, 6, 6], + initial_supply.clone(), + 10_000, + 2_500_000, + 200_000_000, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, stable_swap); + + transfer_and_increase_allowance( + &mut session, + stable_swap, + tokens.clone(), + CHARLIE, + vec![500 * ONE_DAI, 500 * ONE_USDT, 500 * ONE_USDC], + BOB, + ); + + // add more liquidity with balanced tokens (charlie) + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + CHARLIE, + 1, + vec![500 * ONE_DAI, 500 * ONE_USDT, 500 * ONE_USDC], + charlie(), + ) + .expect("Should successfully add liquidity"); + + assert_eq!( + share_price_and_total_shares(&mut session, stable_swap), + (last_share_price, last_total_shares + 1500 * ONE_LPT) + ); + + let last_total_shares = last_total_shares + 1500 * ONE_LPT; + + // remove by shares (charlie) + _ = stable_swap::remove_liquidity_by_shares( + &mut session, + stable_swap, + CHARLIE, + 300 * ONE_LPT, + vec![1 * ONE_DAI, 1 * ONE_USDT, 1 * ONE_USDC], + charlie(), + ) + .expect("Should successfully remove liquidity"); + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, charlie()), + 1200 * ONE_LPT + ); + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, charlie())) + .collect::>(); + assert_eq!( + balances, + vec![100 * ONE_DAI, 100 * ONE_USDT, 100 * ONE_USDC], + "Incorrect Users tokens balances" + ); + assert_eq!( + share_price_and_total_shares(&mut session, stable_swap), + (last_share_price, last_total_shares - 300 * ONE_LPT) + ); + let last_total_shares = last_total_shares - 300 * ONE_LPT; + + transfer_and_increase_allowance( + &mut session, + stable_swap, + tokens.clone(), + DAVE, + vec![100 * ONE_DAI, 200 * ONE_USDT, 400 * ONE_USDC], + BOB, + ); + + // add more liquidity with imbalanced tokens (dave) + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + DAVE, + 1, + vec![100 * ONE_DAI, 200 * ONE_USDT, 400 * ONE_USDC], + dave(), + ) + .expect("Should successfully add liquidity"); + // Ref + // "Mint 699699997426210330025 shares for user2, fee is 299999998348895348 shares", + // "Exchange swap got 59999999669779069 shares", + // -- DIFF -- + // Common + // "Mint 699687497426279107411 shares for dave, fee is 312499998280117962 shares", + // "Exchange swap got 62499999656023592 shares, No referral fee (not implemented)", + // + // The difference is due to the implemented fee precision (1e9 in Common vs 1e4 in Ref) + + assert_eq!( + stable_swap::reserves(&mut session, stable_swap), + vec![100500 * ONE_DAI, 100600 * ONE_USDT, 100800 * ONE_USDC], + "Incorrect reserves" + ); + assert_eq!( + psp22_utils::total_supply(&mut session, stable_swap), + 301200 * ONE_LPT + 699687497426279107411 + 62499999656023592, + "Incorrect total shares" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, dave()), + 699687497426279107411, + "Incorrect Users share" + ); + + let (current_share_price, current_total_shares) = + share_price_and_total_shares(&mut session, stable_swap); + assert!( + current_share_price > last_share_price, + "Incorrect share price" + ); + let last_share_price = current_share_price; + + assert_eq!( + current_total_shares, + last_total_shares + 699687497426279107411 + 62499999656023592 + ); + let last_total_shares = current_total_shares; + + // remove by tokens (charlie) + _ = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + CHARLIE, + 550 * ONE_LPT, + vec![1 * ONE_DAI, 500 * ONE_USDT, 1 * ONE_USDC], + charlie(), + ) + .expect("Should successfully remove liquidity. Err: {err:?}"); + // "LP charlie removed 502623448746385017122 shares by given tokens, and fee is 623853418327862983 shares", + // "Exchange swap got 124770683665572596 shares, No referral fee (not implemented)", + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, charlie()), + 1200 * ONE_LPT - 502623448746385017122, + "Incorrect users share" + ); + + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, charlie())) + .collect::>(); + assert_eq!( + balances, + vec![101 * ONE_DAI, 600 * ONE_USDT, 101 * ONE_USDC], + "Incorrect Users tokens balances" + ); + + assert_eq!( + stable_swap::reserves(&mut session, stable_swap), + vec![100499 * ONE_DAI, 100100 * ONE_USDT, 100799 * ONE_USDC], + "Incorrect reserves" + ); + assert_eq!( + psp22_utils::total_supply(&mut session, stable_swap), + last_total_shares - 502623448746385017122 + 124770683665572596, + "Incorrect total shares" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, charlie()), + 1200 * ONE_LPT - 502623448746385017122, + "Incorrect users share" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, dave()), + 699687497426279107411, + "Incorrect users share" + ); + let (current_share_price, _) = share_price_and_total_shares(&mut session, stable_swap); + assert!( + current_share_price > last_share_price, + "Incorrect share price" + ); + let last_share_price = current_share_price; + let last_total_shares = last_total_shares - 502623448746385017122 + 124770683665572596; + + // transfer some LPT to from charlie to dave + _ = psp22_utils::transfer(&mut session, stable_swap, dave(), 100 * ONE_LPT, CHARLIE); + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, charlie()), + 1100 * ONE_LPT - 502623448746385017122, + "Incorrect user balance" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, dave()), + 699687497426279107411 + 100 * ONE_LPT, + "Incorrect user balance" + ); + + assert_eq!( + share_price_and_total_shares(&mut session, stable_swap), + (last_share_price, last_total_shares), + "Incorrect share price and/or total shares" + ); + + // dave remove by shares trigger slippage + let res = stable_swap::remove_liquidity_by_shares( + &mut session, + stable_swap, + DAVE, + 300 * ONE_LPT, + vec![1 * ONE_DAI, 298 * ONE_USDT, 1 * ONE_USDC], + dave(), + ) + .expect_err("Should return an error"); + + assert_eq!( + res, + StablePoolError::InsufficientOutputAmount(), + "Should return correct error" + ); + + assert_eq!( + share_price_and_total_shares(&mut session, stable_swap), + (last_share_price, last_total_shares), + "Incorrect share price and/or total shares" + ); + + // dave remove by tokens trigger slippage + let res = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + DAVE, + 300 * ONE_LPT, + vec![1 * ONE_DAI, 298 * ONE_USDT, 1 * ONE_USDC], + dave(), + ) + .expect_err("Should return an error"); + + assert_eq!( + res, + StablePoolError::InsufficientLiquidityBurned(), + "Should return correct error" + ); + + assert_eq!( + share_price_and_total_shares(&mut session, stable_swap), + (last_share_price, last_total_shares), + "Incorrect share price and/or total shares" + ); + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, charlie()), + 1100 * ONE_LPT - 502623448746385017122, + "Incorrect user balance" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, dave()), + 699687497426279107411 + 100 * ONE_LPT, + "Incorrect user balance" + ); + + // dave remove by share + _ = stable_swap::remove_liquidity_by_shares( + &mut session, + stable_swap, + DAVE, + 300 * ONE_LPT, + vec![1 * ONE_DAI, 1 * ONE_USDT, 1 * ONE_USDC], + dave(), + ) + .expect("Should successfully remove liquidity"); + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, charlie()), + 1100 * ONE_LPT - 502623448746385017122, + "Incorrect user balance" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, dave()), + 699687497426279107411 - 200 * ONE_LPT, + "Incorrect user balance" + ); + let (current_share_price, current_total_shares) = + share_price_and_total_shares(&mut session, stable_swap); + assert_eq!( + current_share_price, last_share_price, + "Incorrect share price" + ); + assert_eq!( + current_total_shares, + last_total_shares - 300 * ONE_LPT, + "Incorrect total shares" + ); + let last_total_shares = last_total_shares - 300 * ONE_LPT; + + _ = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + DAVE, + 499 * ONE_LPT, + vec![498 * ONE_DAI, 0 * ONE_USDT, 0 * ONE_USDC], + dave(), + ) + .expect("Should successfully remove liquidity"); + // "LP dave removed 498621166533015126275 shares by given tokens, and fee is 622396225347309589 shares", + // "Exchange swap got 124479245069461917 shares, No referral fee (not implemented)", + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, charlie()), + 1100 * ONE_LPT - 502623448746385017122, + "Incorrect user balance" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, dave()), + 699687497426279107411 - 200 * ONE_LPT - 498621166533015126275, + "Incorrect user balance" + ); + let last_total_shares = last_total_shares - 498621166533015126275 + 124479245069461917; + let (current_share_price, current_total_shares) = + share_price_and_total_shares(&mut session, stable_swap); + assert!( + current_share_price > last_share_price, + "Incorrect share price" + ); + assert_eq!( + current_total_shares, last_total_shares, + "Incorrect total shares" + ); + + transfer_and_increase_allowance( + &mut session, + stable_swap, + tokens.clone(), + EVA, + vec![ + 100_000_000_000 * ONE_DAI, + 100_000_000_000 * ONE_USDT, + 100_000_000_000 * ONE_USDC, + ], + BOB, + ); + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + EVA, + 1, + vec![ + 100_000_000_000 * ONE_DAI, + 100_000_000_000 * ONE_USDT, + 100_000_000_000 * ONE_USDC, + ], + eva(), + ) + .expect("Should successfully add liquidity"); + // "Mint 299997824748271184577117019598 shares for eva, fee is 933133378066387864612868 shares", + // "Exchange swap got 186626675613277572922573 shares, No referral fee (not implemented)", + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, eva()), + 299997824748271184577117019598, + "Incorrect user balance" + ); + let last_total_shares = + last_total_shares + 299997824748271184577117019598 + 186626675613277572922573; + assert_eq!( + psp22_utils::total_supply(&mut session, stable_swap), + last_total_shares, + "Incorrect total shares" + ); +} + +/// Test withdrawing all liquidity with all shares +#[drink::test] +fn test_02(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + let initial_reserves = vec![100000 * ONE_DAI, 100000 * ONE_USDT, 100000 * ONE_USDC]; + let initial_supply = initial_reserves + .iter() + .map(|amount| amount * 100_000_000_000) + .collect::>(); + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![18, 6, 6], + initial_supply.clone(), + 10_000, + 2_500_000, + 200_000_000, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + // remove by shares + _ = stable_swap::remove_liquidity_by_shares( + &mut session, + stable_swap, + BOB, + 300000 * ONE_LPT, + vec![1 * ONE_DAI, 1 * ONE_USDT, 1 * ONE_USDC], + bob(), + ) + .expect("Should successfully remove liquidity"); + + assert_eq!(psp22_utils::balance_of(&mut session, stable_swap, bob()), 0); + assert_eq!(psp22_utils::total_supply(&mut session, stable_swap), 0); + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, bob())) + .collect::>(); + assert_eq!(balances, initial_supply, "Incorrect Users tokens balances"); +} + +/// Test withdrawing all liquidity by amounts +#[drink::test] +fn test_03(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + let initial_reserves = vec![100000 * ONE_DAI, 100000 * ONE_USDT, 100000 * ONE_USDC]; + let initial_supply = initial_reserves + .iter() + .map(|amount| amount * 100_000_000_000) + .collect::>(); + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![18, 6, 6], + initial_supply.clone(), + 10_000, + 2_500_000, + 200_000_000, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + _ = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + BOB, + 300000 * ONE_LPT, + initial_reserves, + bob(), + ) + .expect("Should successfully remove liquidity"); + + assert_eq!(psp22_utils::balance_of(&mut session, stable_swap, bob()), 0); + assert_eq!(psp22_utils::total_supply(&mut session, stable_swap), 0); + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, bob())) + .collect::>(); + assert_eq!(balances, initial_supply, "Incorrect Users tokens balances"); +} + +/// Test withdrawing all liquidity with shares - 1 +#[drink::test] +fn test_04(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + let initial_reserves = vec![100000 * ONE_DAI, 100000 * ONE_USDT, 100000 * ONE_USDC]; + let initial_supply = initial_reserves + .iter() + .map(|amount| amount * 100_000_000_000) + .collect::>(); + let initial_supply_sub_reserves = initial_supply + .iter() + .zip(initial_reserves.iter()) + .map(|(supply, reserve)| supply - reserve) + .collect::>(); + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![18, 6, 6], + initial_supply.clone(), + 10_000, + 2_500_000, + 200_000_000, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + let err = stable_swap::remove_liquidity_by_shares( + &mut session, + stable_swap, + BOB, + 300000 * ONE_LPT - 1, + initial_reserves.clone(), + bob(), + ) + .expect_err("Liquidity withdraw should fail"); + assert_eq!( + err, + StablePoolError::InsufficientOutputAmount(), + "Should return appropriate error" + ); + + let err = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + BOB, + 300000 * ONE_LPT - 1, + initial_reserves, + bob(), + ) + .expect_err("Liquidity withdraw should fail"); + assert_eq!( + err, + StablePoolError::InsufficientLiquidityBurned(), + "Should return appropriate error" + ); + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, bob()), + 300000 * ONE_LPT + ); + assert_eq!( + psp22_utils::total_supply(&mut session, stable_swap), + 300000 * ONE_LPT + ); + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, bob())) + .collect::>(); + assert_eq!( + balances, initial_supply_sub_reserves, + "Incorrect Users tokens balances" + ); +} + +/// Test withdrawing single token whole reserve +#[drink::test] +fn test_05(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + let initial_reserves = vec![100000 * ONE_DAI, 100000 * ONE_USDT, 100000 * ONE_USDC]; + let initial_supply = initial_reserves + .iter() + .map(|amount| amount * 100_000_000_000) + .collect::>(); + let initial_supply_sub_reserves = initial_supply + .iter() + .zip(initial_reserves.iter()) + .map(|(supply, reserve)| supply - reserve) + .collect::>(); + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![18, 6, 6], + initial_supply.clone(), + 10_000, + 2_500_000, + 200_000_000, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + let err = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + BOB, + 300000 * ONE_LPT, + vec![initial_reserves[0], 0, 0], + bob(), + ) + .expect_err("Liquidity withdraw should fail"); + + assert_eq!( + err, + StablePoolError::MathError(MathError::DivByZero(1)), + "Should return appropriate error" + ); + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, bob()), + 300000 * ONE_LPT + ); + assert_eq!( + psp22_utils::total_supply(&mut session, stable_swap), + 300000 * ONE_LPT + ); + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, bob())) + .collect::>(); + assert_eq!( + balances, initial_supply_sub_reserves, + "Incorrect Users tokens balances" + ); +} + +/// Test withdrawing all liquidity with shares - 1 (with different initial reserves) +#[drink::test] +fn test_06(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + let initial_reserves = vec![543257 * ONE_DAI, 123123 * ONE_USDT, 32178139 * ONE_USDC]; + let initial_supply = initial_reserves + .iter() + .map(|amount| amount * 100_000_000_000) + .collect::>(); + let initial_supply_sub_reserves = initial_supply + .iter() + .zip(initial_reserves.iter()) + .map(|(supply, reserve)| supply - reserve) + .collect::>(); + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![18, 6, 6], + initial_supply.clone(), + 10_000, + 2_500_000, + 200_000_000, + BOB, + vec![], + ); + + let (shares, _) = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + let err = stable_swap::remove_liquidity_by_shares( + &mut session, + stable_swap, + BOB, + shares - 1, + initial_reserves.clone(), + bob(), + ) + .expect_err("Liquidity withdraw should fail"); + assert_eq!( + err, + StablePoolError::InsufficientOutputAmount(), + "Should return appropriate error" + ); + + let err = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + BOB, + shares - 1, + initial_reserves, + bob(), + ) + .expect_err("Liquidity withdraw should fail"); + assert_eq!( + err, + StablePoolError::InsufficientLiquidityBurned(), + "Should return appropriate error" + ); + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, bob()), + shares + ); + assert_eq!(psp22_utils::total_supply(&mut session, stable_swap), shares); + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, bob())) + .collect::>(); + assert_eq!( + balances, initial_supply_sub_reserves, + "Incorrect Users tokens balances" + ); +} + +/// Test withdrawing single token whole reserve (with different initial reserves) +#[drink::test] +fn test_07(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + let initial_reserves = vec![543257 * ONE_DAI, 123123 * ONE_USDT, 32178139 * ONE_USDC]; + let initial_supply = initial_reserves + .iter() + .map(|amount| amount * 100_000_000_000) + .collect::>(); + let initial_supply_sub_reserves = initial_supply + .iter() + .zip(initial_reserves.iter()) + .map(|(supply, reserve)| supply - reserve) + .collect::>(); + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![18, 6, 6], + initial_supply.clone(), + 10_000, + 2_500_000, + 200_000_000, + BOB, + vec![], + ); + + let (shares, _) = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + let err = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + BOB, + shares, + vec![initial_reserves[0], 0, 0], + bob(), + ) + .expect_err("Liquidity withdraw should fail"); + assert_eq!( + err, + StablePoolError::MathError(MathError::DivByZero(1)), + "Should return appropriate error" + ); + + let err = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + BOB, + shares, + vec![0, initial_reserves[1], 0], + bob(), + ) + .expect_err("Liquidity withdraw should fail"); + assert_eq!( + err, + StablePoolError::MathError(MathError::DivByZero(1)), + "Should return appropriate error" + ); + + let err = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + BOB, + shares, + vec![0, 0, initial_reserves[2]], + bob(), + ) + .expect_err("Liquidity withdraw should fail"); + assert_eq!( + err, + StablePoolError::MathError(MathError::DivByZero(1)), + "Should return appropriate error" + ); + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, bob()), + shares + ); + assert_eq!(psp22_utils::total_supply(&mut session, stable_swap), shares); + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, bob())) + .collect::>(); + assert_eq!( + balances, initial_supply_sub_reserves, + "Incorrect Users tokens balances" + ); +} + +/// Tests that after depositing X tokens amounts for L shares, user cannot withdraw X tokens amounts for L - 1 shares +#[drink::test] +fn test_08(mut session: Session) { + let initial_reserves = vec![100000 * ONE_USDT, 100000 * ONE_USDC]; + let initial_supply: Vec = initial_reserves.iter().map(|amount| amount * 10).collect(); + let amp_coef = 10_000u128; + let trade_fee = 2_500_000u32; + let protocol_fee = 200_000_000u32; + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![12, 12], + initial_supply.clone(), + amp_coef, + trade_fee, + protocol_fee, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + _ = stable_swap::swap_exact_in( + &mut session, + stable_swap, + BOB, + tokens[0], // in USDT + tokens[1], // out USDC + 123 * ONE_USDT + 132312, // amount_in + 1, // min_token_out + bob(), + ) + .expect("Should successfully swap"); + + let total_shares = psp22_utils::total_supply(&mut session, stable_swap); + let share = total_shares / 20; // 4.999...% - 5% + + let deposit_amounts = + stable_swap::get_amounts_for_liquidity_mint(&mut session, stable_swap, share) + .expect("Should compute"); + + let (share_mint, _) = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + deposit_amounts.clone(), + bob(), + ) + .expect("Should mint LPT"); + + let err = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + BOB, + share_mint - 1, + deposit_amounts, + bob(), + ) + .expect_err("Should fail to remove lpt"); + + assert_eq!( + StablePoolError::InsufficientLiquidityBurned(), + err, + "Should be insufficient" + ); +} + +/// Tests that after withdrawing X tokens amounts for L shares, user cannot deposit X tokens amounts for L + 1 shares +#[drink::test] +fn test_09(mut session: Session) { + let initial_reserves = vec![100000 * ONE_USDT * ONE_USDT, 100000 * ONE_USDC * ONE_USDC]; + let initial_supply: Vec = initial_reserves.iter().map(|amount| amount * 10).collect(); + let amp_coef = 10_000u128; + let trade_fee = 2_500_000u32; + let protocol_fee = 200_000_000u32; + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![12, 12], + initial_supply.clone(), + amp_coef, + trade_fee, + protocol_fee, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + _ = stable_swap::swap_exact_in( + &mut session, + stable_swap, + BOB, + tokens[0], // in USDT + tokens[1], // out USDC + 123 * ONE_USDT * ONE_USDC + 132312, // amount_in + 1, // min_token_out + bob(), + ) + .expect("Should successfully swap"); + + let total_shares = psp22_utils::total_supply(&mut session, stable_swap); + let share = total_shares / 20; // 4.999...% - 5% + + let withdraw_amounts = + stable_swap::get_amounts_for_liquidity_burn(&mut session, stable_swap, share) + .expect("Should compute"); + let (share_burn, _) = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + BOB, + u128::MAX, + withdraw_amounts.clone(), + bob(), + ) + .expect("Should burn LPT"); + + let err = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + share_burn + 1, + withdraw_amounts.clone(), + bob(), + ) + .expect_err("Should fail to mint lpt"); + + assert_eq!( + StablePoolError::InsufficientLiquidityMinted(), + err, + "Should be insufficient" + ); +} + +/// Tests that after depositing X tokens amounts for L shares, user cannot withdraw X tokens amounts for L - 1 shares (using remove_by_shares method) +#[drink::test] +fn test_10(mut session: Session) { + let initial_reserves = vec![100000 * ONE_USDT, 100000 * ONE_USDC]; + let initial_supply: Vec = initial_reserves.iter().map(|amount| amount * 10).collect(); + let amp_coef = 10_000u128; + let trade_fee = 2_500_000u32; + let protocol_fee = 200_000_000u32; + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![12, 12], + initial_supply.clone(), + amp_coef, + trade_fee, + protocol_fee, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + _ = stable_swap::swap_exact_in( + &mut session, + stable_swap, + BOB, + tokens[0], // in USDT + tokens[1], // out USDC + 123 * ONE_USDT + 132312, // amount_in + 1, // min_token_out + bob(), + ) + .expect("Should successfully swap"); + + let total_shares = psp22_utils::total_supply(&mut session, stable_swap); + let share = total_shares / 20; // 4.999...% - 5% + + let deposit_amounts = + stable_swap::get_amounts_for_liquidity_mint(&mut session, stable_swap, share) + .expect("Should compute"); + + let (share_mint, _) = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + deposit_amounts.clone(), + bob(), + ) + .expect("Should mint LPT"); + + let err = stable_swap::remove_liquidity_by_shares( + &mut session, + stable_swap, + BOB, + share_mint - 1, + deposit_amounts, + bob(), + ) + .expect_err("Should fail to remove lpt"); + + assert_eq!( + StablePoolError::InsufficientOutputAmount(), + err, + "Should be insufficient" + ); +} + +/// Tests that after withdrawing X tokens amounts for L shares, user cannot deposit X tokens amounts for L + 1 shares (using remove_by_shares method) +#[drink::test] +fn test_11(mut session: Session) { + let initial_reserves = vec![100000 * ONE_USDT * ONE_USDT, 100000 * ONE_USDC * ONE_USDC]; + let initial_supply: Vec = initial_reserves.iter().map(|amount| amount * 10).collect(); + let amp_coef = 10_000u128; + let trade_fee = 2_500_000u32; + let protocol_fee = 200_000_000u32; + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![12, 12], + initial_supply.clone(), + amp_coef, + trade_fee, + protocol_fee, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + _ = stable_swap::swap_exact_in( + &mut session, + stable_swap, + BOB, + tokens[0], // in USDT + tokens[1], // out USDC + 123 * ONE_USDT * ONE_USDC + 132312, // amount_in + 1, // min_token_out + bob(), + ) + .expect("Should successfully swap"); + + let total_shares = psp22_utils::total_supply(&mut session, stable_swap); + let share = total_shares / 20; // 4.999...% - 5% + + let withdraw_amounts = stable_swap::remove_liquidity_by_shares( + &mut session, + stable_swap, + BOB, + share, + vec![1, 1], + bob(), + ) + .expect("Should burn LPT"); + + let err = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + share + 1, + withdraw_amounts.clone(), + bob(), + ) + .expect_err("Should fail to mint lpt"); + + assert_eq!( + StablePoolError::InsufficientLiquidityMinted(), + err, + "Should be insufficient" + ); +} + +#[drink::test] +fn test_lp_withdraw_all_but_no_more() { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + let charlie_input = vec![1_434_543 * ONE_USDT, 1_112_323 * ONE_USDC]; + let dave_input = vec![1131 * 105 * ONE_USDT, 1157 * 105 * ONE_USDC]; // treat as 105 parts of (1131, 1157) + let initial_supply: Vec = charlie_input.iter().map(|amount| amount * 10).collect(); + + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![6, 6], + initial_supply.clone(), + 10_000, + 0, + 0, + BOB, + vec![], + ); + + transfer_and_increase_allowance( + &mut session, + stable_swap, + tokens.clone(), + CHARLIE, + vec![1_434_543 * ONE_USDT, 1_112_323 * ONE_USDC], + BOB, + ); + transfer_and_increase_allowance( + &mut session, + stable_swap, + tokens, + DAVE, + vec![1131 * 105 * ONE_USDT, 1157 * 105 * ONE_USDC], + BOB, + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + CHARLIE, + 1, + charlie_input.clone(), + charlie(), + ) + .expect("Charlie should successfully add liquidity"); + + let (shares, _) = stable_swap::add_liquidity( + &mut session, + stable_swap, + DAVE, + 1, + dave_input.clone(), + dave(), + ) + .expect("Dave should successfully add liquidity"); + + // 105 times withdraw (1131, 1157) which should withdraw the whole Dave's share + for _ in 0..105 { + let withdraw = vec![1131 * ONE_USDT, 1157 * ONE_USDC]; // 1/105 of Dave's + _ = stable_swap::remove_liquidity_by_amounts( + &mut session, + stable_swap, + DAVE, + shares, // just an upper bound + withdraw, + dave(), + ) + .expect("Should successfully remove liquidity"); + } + + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, dave()), + 0, + "Dave shouldn't have any LPT left", + ); + + _ = stable_swap::remove_liquidity_by_shares( + &mut session, + stable_swap, + DAVE, + 10, + vec![1, 1], // at least withdraw something + dave(), + ) + .expect_err("Should not successfully remove liquidity"); +} diff --git a/amm/drink-tests/src/stable_swap_tests/tests_getters.rs b/amm/drink-tests/src/stable_swap_tests/tests_getters.rs new file mode 100644 index 0000000..3ac7cfd --- /dev/null +++ b/amm/drink-tests/src/stable_swap_tests/tests_getters.rs @@ -0,0 +1,135 @@ +use super::*; + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/tests/test_stable_pool.rs#L23 +#[drink::test] +fn test_01(mut session: Session) { + let initial_reserves = vec![100000 * ONE_DAI, 100000 * ONE_USDT, 100000 * ONE_USDC]; + let initial_supply: Vec = initial_reserves.iter().map(|amount| amount * 10).collect(); + let amp_coef = 10_000u128; + let trade_fee = 2_500_000u32; + let protocol_fee = 200_000_000u32; + let (stable_swap, tokens) = setup_stable_swap_with_tokens( + &mut session, + vec![18, 6, 6], + initial_supply.clone(), + amp_coef, + trade_fee, + protocol_fee, + BOB, + vec![], + ); + + _ = stable_swap::add_liquidity( + &mut session, + stable_swap, + BOB, + 1, + initial_reserves.clone(), + bob(), + ) + .expect("Should successfully add liquidity"); + + assert_eq!( + stable_swap::tokens(&mut session, stable_swap), + tokens, + "Incorrect token accounts" + ); + assert_eq!( + stable_swap::reserves(&mut session, stable_swap), + initial_reserves, + "Incorrect reserves" + ); + assert_eq!( + stable_swap::amp_coef(&mut session, stable_swap), + amp_coef, + "Incorrect A" + ); + assert_eq!( + stable_swap::fees(&mut session, stable_swap), + (trade_fee, protocol_fee), + "Incorrect fees" + ); + assert_eq!( + psp22_utils::total_supply(&mut session, stable_swap), + 300_000 * ONE_LPT, + "Incorrect LP token supply" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, stable_swap, bob()), + 300_000 * ONE_LPT, + "Incorrect Users LP token balance" + ); + + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, bob())) + .collect(); + assert_eq!( + balances, + initial_supply + .iter() + .zip(initial_reserves) + .map(|(init_token_supply, init_reserve)| init_token_supply - init_reserve) + .collect::>(), + "Incorrect Users tokens balances" + ); + + _ = stable_swap::swap_exact_in( + &mut session, + stable_swap, + BOB, + tokens[0], // DAI + tokens[2], // USDC + ONE_DAI, // amount_in + 1, // min_token_out + charlie(), + ) + .expect("Should successfully swap"); + + _ = stable_swap::swap_exact_in( + &mut session, + stable_swap, + BOB, + tokens[0], // DAI + tokens[1], // USDT + ONE_DAI, // amount_in + 1, // min_token_out + charlie(), + ) + .expect("Should successfully swap. "); + + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, charlie())) + .collect(); + assert_eq!( + balances, + vec![0, 997499, 997499], + "Incorrect Users tokens balances" + ); + + let balances: Vec = tokens + .iter() + .map(|&token| psp22_utils::balance_of(&mut session, token, stable_swap)) + .collect(); + assert_eq!( + stable_swap::reserves(&mut session, stable_swap), + balances, + "Pool reserves and token balances mismatch" + ); + + assert_eq!( + stable_swap::reserves(&mut session, stable_swap), + vec![ + 100002 * ONE_DAI, + 99999 * ONE_USDT + 2501, // -- DIFF -- 99999 * ONE_USDT + 2500 + 99999 * ONE_USDC + 2501 // -- DIFF -- 99999 * ONE_USDC + 2500 + ], + "Incorrect reserves" + ); + assert_eq!( + psp22_utils::total_supply(&mut session, stable_swap), + 300000 * ONE_LPT + 498999996725367 + 498999993395420, // -- DIFF -- 300000 * ONE_LPT + 499999996666583 + 499999993277742 + "Incorrect LP token supply" + ); +} diff --git a/amm/drink-tests/src/stable_swap_tests/tests_rated.rs b/amm/drink-tests/src/stable_swap_tests/tests_rated.rs new file mode 100644 index 0000000..83acb14 --- /dev/null +++ b/amm/drink-tests/src/stable_swap_tests/tests_rated.rs @@ -0,0 +1,558 @@ +use crate::mock_sazero_rate_contract; +use crate::stable_pool_contract; +use crate::utils::*; + +use super::*; + +use drink::{self, runtime::MinimalRuntime, session::Session}; +use ink_primitives::AccountId; +use ink_wrapper_types::{Connection, ToAccountId}; + +const WAZERO_DEC: u8 = 12; +const SAZERO_DEC: u8 = 12; + +const ONE_LPT: u128 = 10u128.pow(18); +const ONE_WAZERO: u128 = 10u128.pow(WAZERO_DEC as u32); +const ONE_SAZERO: u128 = 10u128.pow(SAZERO_DEC as u32); + +/// Cached token rate expiry time in milliseconds +const EXPIRE_TIME_MILLIS: u64 = 24 * 3600 * 1000; // 24h + +fn deploy_rate_provider(session: &mut Session, salt: Vec) -> AccountId { + let instance = mock_sazero_rate_contract::Instance::new().with_salt(salt); + session + .instantiate(instance) + .unwrap() + .result + .to_account_id() + .into() +} + +fn setup_rated_swap_with_tokens( + session: &mut Session, + caller: drink::AccountId32, + rate_providers: Vec>, + initial_token_supply: u128, + init_amp_coef: u128, + rate_expiration_duration_ms: u64, + trade_fee: u32, + protocol_fee: u32, +) -> (AccountId, Vec) { + let _ = session.set_actor(caller.clone()); + + let tokens: Vec = rate_providers + .iter() + .enumerate() + .map(|(i, _)| { + psp22_utils::setup_with_amounts( + session, + format!("Token{i}"), + WAZERO_DEC, + initial_token_supply * ONE_WAZERO, + caller.clone(), + ) + .into() + }) + .collect(); + + let instance = stable_pool_contract::Instance::new_rated( + tokens.clone(), + vec![WAZERO_DEC; rate_providers.len()], + rate_providers, + rate_expiration_duration_ms, + init_amp_coef, + caller.to_account_id(), + trade_fee, + protocol_fee, + Some(fee_receiver()), + ); + + let rated_swap = session + .instantiate(instance) + .unwrap() + .result + .to_account_id() + .into(); + + for token in tokens.clone() { + psp22_utils::increase_allowance( + session, + token.into(), + rated_swap, + u128::MAX, + caller.clone(), + ) + .unwrap(); + } + + (rated_swap, tokens) +} + +fn set_mock_rate(session: &mut Session, mock_rate_contract: AccountId, rate: u128) { + _ = handle_ink_error( + session + .execute(mock_sazero_rate_contract::Instance::from(mock_rate_contract).set_rate(rate)) + .unwrap(), + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/tests/test_rated_pool.rs#L27 +#[drink::test] +fn test_01(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + upload_all(&mut session); + + let now = get_timestamp(&mut session); + set_timestamp(&mut session, now); + let initial_token_supply: u128 = 1_000_000_000; + let mock_sazero_rate = deploy_rate_provider(&mut session, vec![0]); + let (rated_swap, tokens) = setup_rated_swap_with_tokens( + &mut session, + BOB, + vec![Some(mock_sazero_rate), None], + initial_token_supply, + 10000, + EXPIRE_TIME_MILLIS, + 2_500_000, + 200_000_000, + ); + let [sazero, wazero]: [AccountId; 2] = tokens.try_into().unwrap(); + + set_timestamp(&mut session, now + EXPIRE_TIME_MILLIS + 1); + set_mock_rate(&mut session, mock_sazero_rate, 2 * RATE_PRECISION); + + _ = stable_swap::add_liquidity( + &mut session, + rated_swap.into(), + BOB, + 1, + vec![50000 * ONE_SAZERO, 100000 * ONE_WAZERO], + bob(), + ) + .expect("Should successfully add liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, bob()), + 200000 * ONE_LPT, + "Incorrect user share" + ); + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 200000 * ONE_LPT, + "Incorrect total shares" + ); + assert_eq!(last_share_price, 100000000, "Incorrect share price"); + + transfer_and_increase_allowance( + &mut session, + rated_swap, + vec![sazero, wazero], + CHARLIE, + vec![100000 * ONE_SAZERO, 100000 * ONE_WAZERO], + BOB, + ); + _ = stable_swap::add_liquidity( + &mut session, + rated_swap.into(), + CHARLIE, + 1, + vec![50000 * ONE_SAZERO, 100000 * ONE_WAZERO], + charlie(), + ) + .expect("Should successfully add liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, charlie()), + 200000 * ONE_LPT, + "Incorrect user share" + ); + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 400000 * ONE_LPT, + "Incorrect total shares" + ); + assert_eq!(last_share_price, 100000000, "Incorrect share price"); + + _ = stable_swap::remove_liquidity_by_shares( + &mut session, + rated_swap.into(), + CHARLIE, + 200000 * ONE_LPT, + vec![1 * ONE_SAZERO, 1 * ONE_WAZERO], + charlie(), + ) + .expect("Should successfully remove liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, charlie()), + 0, + "Incorrect user share" + ); + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 200000 * ONE_LPT, + "Incorrect total shares" + ); + assert_eq!(last_share_price, 100000000, "Incorrect share price"); + + // --- DIFF ---- + // Allow withdrawing all liquidity from the pool + + _ = stable_swap::remove_liquidity_by_shares( + &mut session, + rated_swap.into(), + BOB, + 200000 * ONE_LPT, + vec![1 * ONE_SAZERO, 1 * ONE_WAZERO], + bob(), + ) + .expect("Should successfully remove liquidity"); + + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, bob()), + 0, + "Incorrect user share" + ); + + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + + // no shares left + assert_eq!(last_total_shares, 0, "Incorrect total shares"); + assert_eq!(last_share_price, 0, "Incorrect share price"); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/tests/test_rated_pool.rs#L116 +#[drink::test] +fn test_02(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + upload_all(&mut session); + + let now = get_timestamp(&mut session); + set_timestamp(&mut session, now); + let mock_token_2_rate = deploy_rate_provider(&mut session, vec![0]); + + let initial_token_supply: u128 = 1_000_000_000; + let (rated_swap, tokens) = setup_rated_swap_with_tokens( + &mut session, + BOB, + vec![None, Some(mock_token_2_rate), None], + initial_token_supply, + 10000, + EXPIRE_TIME_MILLIS, + 2_500_000, + 200_000_000, + ); + + set_timestamp(&mut session, now + EXPIRE_TIME_MILLIS); + set_mock_rate(&mut session, mock_token_2_rate, 2 * RATE_PRECISION); + + _ = stable_swap::add_liquidity( + &mut session, + rated_swap.into(), + BOB, + 1, + vec![100000 * ONE_WAZERO, 50000 * ONE_WAZERO, 100000 * ONE_WAZERO], + bob(), + ) + .expect("Should successfully add liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, bob()), + 300000 * ONE_LPT, + "Incorrect user share" + ); + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 300000 * ONE_LPT, + "Incorrect total shares" + ); + assert_eq!(last_share_price, 100000000, "Incorrect share price"); + + transfer_and_increase_allowance( + &mut session, + rated_swap, + tokens, + CHARLIE, + vec![ + 100000 * ONE_WAZERO, + 100000 * ONE_WAZERO, + 100000 * ONE_WAZERO, + ], + BOB, + ); + _ = stable_swap::add_liquidity( + &mut session, + rated_swap.into(), + CHARLIE, + 1, + vec![100000 * ONE_WAZERO, 50000 * ONE_WAZERO, 100000 * ONE_WAZERO], + charlie(), + ) + .expect("Should successfully add liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, charlie()), + 300000 * ONE_LPT, + "Incorrect user share" + ); + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 600000 * ONE_LPT, + "Incorrect total shares" + ); + assert_eq!(last_share_price, 100000000, "Incorrect share price"); + + _ = stable_swap::remove_liquidity_by_shares( + &mut session, + rated_swap.into(), + CHARLIE, + 300000 * ONE_LPT, + vec![1 * ONE_WAZERO, 1 * ONE_WAZERO, 1 * ONE_WAZERO], + charlie(), + ) + .expect("Should successfully remove liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, charlie()), + 0, + "Incorrect user share" + ); + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 300000 * ONE_LPT, + "Incorrect total shares" + ); + assert_eq!(last_share_price, 100000000, "Incorrect share price"); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/tests/test_rated_pool.rs#L197 +#[drink::test] +fn test_03(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + upload_all(&mut session); + + let now = get_timestamp(&mut session); + set_timestamp(&mut session, now); + let mock_token_2_rate = deploy_rate_provider(&mut session, vec![0]); + let mock_token_3_rate = deploy_rate_provider(&mut session, vec![1]); + + let initial_token_supply: u128 = 1_000_000_000; + let (rated_swap, tokens) = setup_rated_swap_with_tokens( + &mut session, + BOB, + vec![None, Some(mock_token_2_rate), Some(mock_token_3_rate)], + initial_token_supply, + 10000, + EXPIRE_TIME_MILLIS, + 2_500_000, + 200_000_000, + ); + + set_timestamp(&mut session, now + EXPIRE_TIME_MILLIS); + set_mock_rate(&mut session, mock_token_2_rate, 2 * RATE_PRECISION); + set_mock_rate(&mut session, mock_token_3_rate, 4 * RATE_PRECISION); + + _ = stable_swap::add_liquidity( + &mut session, + rated_swap.into(), + BOB, + 1, + vec![100000 * ONE_WAZERO, 50000 * ONE_WAZERO, 25000 * ONE_WAZERO], + bob(), + ) + .expect("Should successfully add liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, bob()), + 300000 * ONE_LPT, + "Incorrect user share" + ); + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 300000 * ONE_LPT, + "Incorrect total shares" + ); + assert_eq!(last_share_price, 100000000, "Incorrect share price"); + + transfer_and_increase_allowance( + &mut session, + rated_swap, + tokens, + CHARLIE, + vec![ + 100000 * ONE_WAZERO, + 100000 * ONE_WAZERO, + 100000 * ONE_WAZERO, + ], + BOB, + ); + + _ = stable_swap::add_liquidity( + &mut session, + rated_swap.into(), + CHARLIE, + 1, + vec![100000 * ONE_WAZERO, 50000 * ONE_WAZERO, 25000 * ONE_WAZERO], + charlie(), + ) + .expect("Should successfully add liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, charlie()), + 300000 * ONE_LPT, + "Incorrect user share" + ); + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 600000 * ONE_LPT, + "Incorrect total shares" + ); + assert_eq!(last_share_price, 100000000, "Incorrect share price"); + + _ = stable_swap::remove_liquidity_by_shares( + &mut session, + rated_swap.into(), + CHARLIE, + 300000 * ONE_LPT, + vec![1 * ONE_WAZERO, 1 * ONE_WAZERO, 1 * ONE_WAZERO], + charlie(), + ) + .expect("Should successfully remove liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, charlie()), + 0, + "Incorrect user share" + ); + let (last_share_price, last_total_shares) = + share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 300000 * ONE_LPT, + "Incorrect total shares" + ); + assert_eq!(last_share_price, 100000000, "Incorrect share price"); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/tests/test_rated_pool.rs#L303 +#[drink::test] +fn test_04(mut session: Session) { + seed_account(&mut session, CHARLIE); + seed_account(&mut session, DAVE); + seed_account(&mut session, EVA); + + upload_all(&mut session); + + let initial_token_supply: u128 = 1_000_000_000; + let (rated_swap, tokens) = setup_rated_swap_with_tokens( + &mut session, + BOB, + vec![None, None], + initial_token_supply, + 10000, + EXPIRE_TIME_MILLIS, + 2_500_000, + 200_000_000, + ); + + _ = stable_swap::add_liquidity( + &mut session, + rated_swap.into(), + BOB, + 1, + vec![100000 * ONE_WAZERO, 100000 * ONE_WAZERO], + bob(), + ) + .expect("Should successfully add liquidity"); + assert_eq!( + psp22_utils::balance_of(&mut session, rated_swap, bob()), + 200000 * ONE_LPT, + "Incorrect user share" + ); + let (_, last_total_shares) = share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 200000 * ONE_LPT, + "Incorrect total shares" + ); + + assert_eq!( + psp22_utils::balance_of(&mut session, tokens[0], charlie()), + 0, + "Incorrect user token balance" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, tokens[1], charlie()), + 0, + "Incorrect user token balance" + ); + transfer_and_increase_allowance( + &mut session, + rated_swap, + tokens.clone(), + CHARLIE, + vec![ONE_WAZERO, 0], + BOB, + ); + assert_eq!( + psp22_utils::balance_of(&mut session, tokens[0], charlie()), + ONE_WAZERO, + "Incorrect user token balance" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, tokens[1], charlie()), + 0, + "Incorrect user token balance" + ); + + _ = stable_swap::swap_exact_in( + &mut session, + rated_swap.into(), + CHARLIE, + tokens[0], + tokens[1], + ONE_WAZERO, + 1, + charlie(), + ) + .expect("Should swap"); + assert_eq!( + psp22_utils::balance_of(&mut session, tokens[0], charlie()), + 0, + "Incorrect user token balance" + ); + assert_eq!( + psp22_utils::balance_of(&mut session, tokens[1], charlie()), + 997499999501, // -- DIFF -- 997499999501 [274936452669] + "Incorrect user token balance" + ); + + let (_, last_total_shares) = share_price_and_total_shares(&mut session, rated_swap); + assert_eq!( + last_total_shares, + 200000 * ONE_LPT + 499999994249708, // -- DIFF -- 499999994999720 [058346] + "Incorrect total shares" + ); + assert_eq!( + stable_swap::reserves(&mut session, rated_swap), + vec![100001 * ONE_WAZERO, 99999 * ONE_WAZERO + 2500000499], // DIFF -- 2500000498 [725063547331] + "Incorrect reserves" + ); +} diff --git a/amm/drink-tests/src/stable_swap_tests/tests_swap_exact_in_received.rs b/amm/drink-tests/src/stable_swap_tests/tests_swap_exact_in_received.rs new file mode 100644 index 0000000..b4ec0d4 --- /dev/null +++ b/amm/drink-tests/src/stable_swap_tests/tests_swap_exact_in_received.rs @@ -0,0 +1,371 @@ +use drink::{self, runtime::MinimalRuntime, session::Session}; + +use super::*; + +/// Tests swap of token at index 0 to token at index 1. +/// Tests two ways of swapping: swap_exact_in and swap_received +fn test_swap_exact_in( + session: &mut Session, + token_decimals: Vec, + initial_reserves: Vec, + amp_coef: u128, + trade_fee: u32, + protocol_fee: u32, + swap_amount_in: u128, + expected_swap_amount_out_total_result: Result, +) { + let initial_supply = vec![initial_reserves[0] + swap_amount_in, initial_reserves[1]]; + // setup two identical pools + let (stable_swap_1, tokens_1) = setup_stable_swap_with_tokens( + session, + token_decimals.clone(), + initial_supply.clone(), + amp_coef, + trade_fee, + protocol_fee, + BOB, + "foo".as_bytes().to_vec(), + ); + let (stable_swap_2, tokens_2) = setup_stable_swap_with_tokens( + session, + token_decimals.clone(), + initial_supply, + amp_coef, + trade_fee, + protocol_fee, + BOB, + "bar".as_bytes().to_vec(), + ); + _ = stable_swap::add_liquidity( + session, + stable_swap_1, + BOB, + 1, + initial_reserves.to_vec(), + bob(), + ); + _ = stable_swap::add_liquidity( + session, + stable_swap_2, + BOB, + 1, + initial_reserves.to_vec(), + bob(), + ); + + let swap_result_1 = stable_swap::swap_exact_in( + session, + stable_swap_1, + BOB, + tokens_1[0], // in + tokens_1[1], // out + swap_amount_in, // amount_in + 0, // min_token_out + bob(), + ); + + let _ = psp22_utils::transfer(session, tokens_2[0], stable_swap_2, swap_amount_in, BOB); + + let swap_result_2 = stable_swap::swap_received( + session, + stable_swap_2, + BOB, + tokens_2[0], // in + tokens_2[1], // out + 0, // min_token_out + bob(), + ); + + if expected_swap_amount_out_total_result.is_err() { + let swap_err_1 = swap_result_1.expect_err("swap_exact_in: Should return an error."); + let swap_err_2 = swap_result_2.expect_err("swap_received: Should return an error."); + let expected_err = expected_swap_amount_out_total_result.err().unwrap(); + assert_eq!(expected_err, swap_err_1); + assert_eq!(expected_err, swap_err_2); + return; + } + + let expected_swap_amount_out_total = expected_swap_amount_out_total_result.unwrap(); + let expected_fee = expected_swap_amount_out_total * trade_fee as u128 / FEE_DENOM; + let expected_swap_amount_out = expected_swap_amount_out_total - expected_fee; + let expected_protocol_fee_part = expected_fee * protocol_fee as u128 / FEE_DENOM; + + let (amount_out_1, fee_1) = swap_result_1.unwrap(); + let (amount_out_2, fee_2) = swap_result_2.unwrap(); + + // check returned amount swapped and fee + assert_eq!( + expected_swap_amount_out, amount_out_1, + "swap_exact_in: Amount out mismatch" + ); + assert_eq!(expected_fee, fee_1, "swap_exact_in: Fee mismatch"); + + assert_eq!( + expected_swap_amount_out, amount_out_2, + "swap_received: Amount out mismatch" + ); + assert_eq!(expected_fee, fee_2, "swap_received: Fee mismatch"); + + // check if reserves were updated properly + let expected_reserves = [ + initial_reserves[0] + swap_amount_in, + initial_reserves[1] - expected_swap_amount_out, + ]; + let reserves_1 = stable_swap::reserves(session, stable_swap_1); + assert_eq!( + reserves_1, expected_reserves, + "swap_exact_in: Reserves not updated properly" + ); + + let reserves_2 = stable_swap::reserves(session, stable_swap_1); + assert_eq!( + reserves_2, expected_reserves, + "swap_received: Reserves not updated properly" + ); + + // check if reserves are equal the actual balances + let balance_0 = psp22_utils::balance_of(session, tokens_1[0], stable_swap_1); + let balance_1 = psp22_utils::balance_of(session, tokens_1[1], stable_swap_1); + assert_eq!( + reserves_1, + vec![balance_0, balance_1], + "swap_exact_in: Balances - reserves mismatch" + ); + let balance_0 = psp22_utils::balance_of(session, tokens_2[0], stable_swap_2); + let balance_1 = psp22_utils::balance_of(session, tokens_2[1], stable_swap_2); + assert_eq!( + reserves_2, + vec![balance_0, balance_1], + "swap_received: Balances - reserves mismatch" + ); + + // check bobs balances + let balance_0 = psp22_utils::balance_of(session, tokens_1[0], bob()); + let balance_1 = psp22_utils::balance_of(session, tokens_1[1], bob()); + assert_eq!( + [0, expected_swap_amount_out], + [balance_0, balance_1], + "swap_exact_in: Incorrect Bob's balances" + ); + let balance_0 = psp22_utils::balance_of(session, tokens_2[0], bob()); + let balance_1 = psp22_utils::balance_of(session, tokens_2[1], bob()); + assert_eq!( + [0, expected_swap_amount_out], + [balance_0, balance_1], + "swap_received: Incorrect Bob's balances" + ); + + // check protocol fee + let protocol_fee_lp = psp22_utils::balance_of(session, stable_swap_1, fee_receiver()); + let (total_lp_required, lp_fee_part) = stable_swap::remove_liquidity_by_amounts( + session, + stable_swap_1, + BOB, + protocol_fee_lp * 2, + [0, expected_protocol_fee_part].to_vec(), + bob(), + ) + .expect("swap_exact_in: Should remove liquidity"); + assert_eq!( + total_lp_required - lp_fee_part, + protocol_fee_lp, + "swap_exact_in: Incorrect protocol fee" + ); + + let protocol_fee_lp = psp22_utils::balance_of(session, stable_swap_2, fee_receiver()); + let (total_lp_required, lp_fee_part) = stable_swap::remove_liquidity_by_amounts( + session, + stable_swap_2, + BOB, + protocol_fee_lp * 2, + [0, expected_protocol_fee_part].to_vec(), + bob(), + ) + .expect("swap_received: Should remove liquidity"); + assert_eq!( + total_lp_required - lp_fee_part, + protocol_fee_lp, + "swap_received: Incorrect protocol fee" + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L744 +#[drink::test] +fn test_01(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![6, 6], // decimals + vec![100000000000, 100000000000], // initial reserves + 1000, // A + 600_000, // trade fee in 1e9 precision + 2000, // protocol fee in 1e9 precision + 10000000000, // swap_amount_in + Ok(9999495232), // expected out (with fee) + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L763 +#[drink::test] +fn test_02(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![12, 18], + vec![100000000000000000, 100000000000000000000000], + 1000, + 600_000, + 2000, + 10000000000000000, + Ok(9999495232752197989995), + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L782 +#[drink::test] +fn test_03(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![6, 6], + vec![100000000000, 100000000000], + 1000, + 600_000, + 2000, + 0, + Err(StablePoolError::InsufficientInputAmount()), + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L801 +#[drink::test] +fn test_04(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![12, 18], + vec![100000000000000000, 100000000000000000000000], + 1000, + 600_000, + 2000, + 0, + Err(StablePoolError::InsufficientInputAmount()), + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L820 +#[drink::test] +fn test_05(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![6, 6], + vec![100000000000, 100000000000], + 1000, + 600_000, + 2000, + 1, + Ok(0), + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L839 +// Test that swapping 0.000000000001000000 gives 0.000000000000 (token precision cut) +#[drink::test] +fn test_06_a(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![18, 12], + vec![100000000000000000000000, 100000000000000000], + 1000, + 600_000, + 2000, + 1000000, + Ok(0), + ); +} + +// Test that swapping (with fees disabled) 0.000000000001000000 gives 0.000000000000 +#[drink::test] +fn test_06_b(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![18, 12], + vec![100000000000000000000000, 100000000000000000], + 1000, + 0, + 0, + 1000000, + Ok(0), + ); +} + +// Test that swapping (with disabled fees) 0.000000000001000001 gives 0.000000000001 +#[drink::test] +fn test_06_c(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![18, 12], + vec![100000000000000000000000, 100000000000000000], + 1000, + 0, + 0, + 1000001, + Ok(1), + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L858 +#[drink::test] +fn test_07(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![6, 6], + vec![100000000000, 100000000000], + 1000, + 600_000, + 2000, + 100000000000, + Ok(98443663539), + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L877 +#[drink::test] +fn test_08(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![12, 18], + vec![100000000000000000, 100000000000000000000000], + 1000, + 600_000, + 2000, + 100000000000000000, + Ok(98443663539913153080656), + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L896 +#[drink::test] +fn test_09(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![6, 6], + vec![100000000000, 100000000000], + 1000, + 600_000, + 2000, + 99999000000 + 1, // +1 because of accounting for fee rounding + Ok(98443167413), + ); +} + +// ref https://github.com/ref-finance/ref-contracts/blob/d241d7aeaa6250937b160d56e5c4b5b48d9d97f7/ref-exchange/src/stable_swap/mod.rs#L915 +#[drink::test] +fn test_10(mut session: Session) { + test_swap_exact_in( + &mut session, + vec![12, 18], + vec![100000000000000000, 100000000000000000000000], + 1000, + 600_000, + 2000, + 99999000000000000, + Ok(98443167413204135506296), + ); +} diff --git a/amm/drink-tests/src/stable_swap_tests/tests_swap_exact_out.rs b/amm/drink-tests/src/stable_swap_tests/tests_swap_exact_out.rs new file mode 100644 index 0000000..a199bba --- /dev/null +++ b/amm/drink-tests/src/stable_swap_tests/tests_swap_exact_out.rs @@ -0,0 +1,304 @@ +use std::ops::{Add, Sub}; + +use drink::{self, runtime::MinimalRuntime, session::Session}; + +use super::*; + +/// Assert if `a` and `b` are equal +/- `delta` +fn assert_approx(a: T, b: T, delta: T, message: &str) +where + T: Add + Sub + Copy + PartialEq + PartialOrd + Eq + Ord + std::fmt::Display, + ::Output: PartialOrd, +{ + if a > b { + if a - b > delta { + panic!("{}, {}, {}", a, b, message) + } + } else { + if b - a > delta { + panic!("{}, {}, {}", a, b, message) + } + } +} + +/// Cross test swap methods. +/// Tests if `swap_exact_out` performs a fair swap +/// and produces correct result. +/// Tests swap of the token at index 0 to token at index 1 +/// +/// NOTE: When token_decimals[0] > token_decimals[1], the method tests if +/// `swap_amount_out` is approximatelly correct (`+/- 10^(token_decimals[0] - token_decimals[1])`) and +/// always in the protocols favour. +fn test_swap_exact_out( + session: &mut Session, + token_decimals: Vec, + initial_reserves: Vec, + amp_coef: u128, + trade_fee: u32, + protocol_fee: u32, + swap_amount_out: u128, +) { + let initial_supply = vec![initial_reserves[0] * 2, initial_reserves[1] * 2]; + // setup two identical pools + let (stable_swap_1, tokens_1) = setup_stable_swap_with_tokens( + session, + token_decimals.clone(), + initial_supply.clone(), + amp_coef, + trade_fee, + protocol_fee, + BOB, + "foo".as_bytes().to_vec(), + ); + let (stable_swap_2, tokens_2) = setup_stable_swap_with_tokens( + session, + token_decimals.clone(), + initial_supply, + amp_coef, + trade_fee, + protocol_fee, + BOB, + "bar".as_bytes().to_vec(), + ); + _ = stable_swap::add_liquidity( + session, + stable_swap_1, + BOB, + 1, + initial_reserves.to_vec(), + bob(), + ); + _ = stable_swap::add_liquidity( + session, + stable_swap_2, + BOB, + 1, + initial_reserves.to_vec(), + bob(), + ); + + let swap_exact_out_result = stable_swap::swap_exact_out( + session, + stable_swap_1, + BOB, + tokens_1[0], // in + tokens_1[1], // out + swap_amount_out, // amount_out + u128::MAX, // max_token_in + bob(), + ) + .expect("swap_exact_out failed"); + + let swap_exact_in_result = stable_swap::swap_exact_in( + session, + stable_swap_2, + BOB, + tokens_2[0], // in + tokens_2[1], // out + swap_exact_out_result.0, // amount_in - cross test result + 0, // min_token_out + bob(), + ) + .expect("swap_exact_in failed"); + + // If token_out has more decimals than token_in, allow rounding error + // up to the difference in their precision. + // Otherwise the results should be exact. + let delta: u128 = if token_decimals[1] > token_decimals[0] { + 10u128.pow((token_decimals[1] - token_decimals[0]) as u32) + } else { + 0 + }; + + // check returned amount swapped and fee + assert_approx( + swap_amount_out, + swap_exact_in_result.0, + delta, + "Amount out mismatch", + ); + assert!( + swap_amount_out <= swap_exact_in_result.0, + "Protocol at loss (amount out)", + ); + assert_approx( + swap_exact_out_result.1, + swap_exact_in_result.1, + delta, + "Fee mismatch", + ); + assert!( + swap_exact_out_result.1 <= swap_exact_in_result.1, + "Protocol at loss (fee)", + ); + + // check if reserves were updated properly + let reserves = stable_swap::reserves(session, stable_swap_1); + assert_eq!( + reserves, + [ + initial_reserves[0] + swap_exact_out_result.0, + initial_reserves[1] - swap_amount_out + ], + "Reserves not updated properly" + ); + + // check if reserves are equal the actual balances + let balance_0 = psp22_utils::balance_of(session, tokens_1[0], stable_swap_1); + let balance_1 = psp22_utils::balance_of(session, tokens_1[1], stable_swap_1); + assert_eq!( + reserves, + vec![balance_0, balance_1], + "Balances - reserves mismatch" + ); + + // check bobs balances + let balance_0 = psp22_utils::balance_of(session, tokens_1[0], bob()); + let balance_1 = psp22_utils::balance_of(session, tokens_1[1], bob()); + assert_eq!( + [ + initial_reserves[0] - swap_exact_out_result.0, + initial_reserves[1] + swap_amount_out + ], + [balance_0, balance_1], + "Incorrect Bob's balances" + ); + + // check protocol fee + let expected_protocol_fee_part = swap_exact_out_result.1 * protocol_fee as u128 / FEE_DENOM; + let protocol_fee_lp = psp22_utils::balance_of(session, stable_swap_1, fee_receiver()); + let (total_lp_required, lp_fee_part) = stable_swap::remove_liquidity_by_amounts( + session, + stable_swap_1, + BOB, + protocol_fee_lp * 2, + [0, expected_protocol_fee_part].to_vec(), + bob(), + ) + .expect("Should remove lp"); + assert_eq!( + total_lp_required - lp_fee_part, + protocol_fee_lp, + "Incorrect protocol fee" + ); +} + +// test when tokens precisions are the same +#[drink::test] +fn test_01(mut session: Session) { + test_swap_exact_out( + &mut session, + vec![6, 6], // decimals + vec![100000000000, 100000000000], // initial reserves + 10000, // A + 600_000, // trade fee + 200_000_000, // protocol fee + 100000000, // expected amount out + ); +} + +#[drink::test] +fn test_02(mut session: Session) { + test_swap_exact_out( + &mut session, + vec![12, 12], + vec![100000000000000000, 100000000000000000], + 10000, + 600_000, + 200_000_000, + 100000000000000, + ); +} + +#[drink::test] +fn test_03(mut session: Session) { + test_swap_exact_out( + &mut session, + vec![18, 18], + vec![100000000000000000000000, 100000000000000000000000], + 10000, + 600_000, + 200_000_000, + 100000000000000000000, + ); +} + +// test when token_in precision is smaller than token_out precision +#[drink::test] +fn test_04(mut session: Session) { + test_swap_exact_out( + &mut session, + vec![6, 12], + vec![100000000000, 100000000000000000], + 10000, + 600_000, + 200_000_000, + 100000000000000, + ); +} + +#[drink::test] +fn test_05(mut session: Session) { + test_swap_exact_out( + &mut session, + vec![6, 18], + vec![100000000000, 100000000000000000000000], + 10000, + 600_000, + 200_000_000, + 100000000000000000000, + ); +} + +#[drink::test] +fn test_06(mut session: Session) { + test_swap_exact_out( + &mut session, + vec![12, 18], + vec![100000000000000000, 100000000000000000000000], + 10000, + 600_000, + 200_000_000, + 100000000000000000000, + ); +} + +// test when token_out precision is smaller than token_in precision +#[drink::test] +fn test_07(mut session: Session) { + test_swap_exact_out( + &mut session, + vec![12, 6], + vec![100000000000000000, 100000000000], + 10000, + 600_000, + 200_000_000, + 100000000, + ); +} + +#[drink::test] +fn test_08(mut session: Session) { + test_swap_exact_out( + &mut session, + vec![18, 6], + vec![100000000000000000000000, 100000000000], + 10000, + 600_000, + 200_000_000, + 100000000, + ); +} + +#[drink::test] +fn test_09(mut session: Session) { + test_swap_exact_out( + &mut session, + vec![18, 12], + vec![100000000000000000000000, 100000000000000000], + 10000, + 600_000, + 200_000_000, + 100000000000000, + ); +} diff --git a/amm/drink-tests/src/utils.rs b/amm/drink-tests/src/utils.rs index 5faaffd..b017ca7 100644 --- a/amm/drink-tests/src/utils.rs +++ b/amm/drink-tests/src/utils.rs @@ -247,6 +247,14 @@ pub mod stable_swap { ) } + pub fn token_rates(session: &mut Session, stable_pool: AccountId) -> Vec { + handle_ink_error( + session + .query(stable_pool_contract::Instance::from(stable_pool).token_rates()) + .unwrap(), + ) + } + pub fn tokens(session: &mut Session, stable_pool: AccountId) -> Vec { handle_ink_error( session @@ -254,6 +262,66 @@ pub mod stable_swap { .unwrap(), ) } + + pub fn get_amounts_for_liquidity_burn( + session: &mut Session, + stable_pool: AccountId, + liquidity: u128, + ) -> Result, StablePoolError> { + handle_ink_error( + session + .query( + stable_pool_contract::Instance::from(stable_pool) + .get_amounts_for_liquidity_burn(liquidity), + ) + .unwrap(), + ) + } + + pub fn get_amounts_for_liquidity_mint( + session: &mut Session, + stable_pool: AccountId, + liquidity: u128, + ) -> Result, StablePoolError> { + handle_ink_error( + session + .query( + stable_pool_contract::Instance::from(stable_pool) + .get_amounts_for_liquidity_mint(liquidity), + ) + .unwrap(), + ) + } + + pub fn get_burn_liquidity_for_amounts( + session: &mut Session, + stable_pool: AccountId, + amounts: Vec, + ) -> Result<(u128, u128), StablePoolError> { + handle_ink_error( + session + .query( + stable_pool_contract::Instance::from(stable_pool) + .get_burn_liquidity_for_amounts(amounts), + ) + .unwrap(), + ) + } + + pub fn get_mint_liquidity_for_amounts( + session: &mut Session, + stable_pool: AccountId, + amounts: Vec, + ) -> Result<(u128, u128), StablePoolError> { + handle_ink_error( + session + .query( + stable_pool_contract::Instance::from(stable_pool) + .get_mint_liquidity_for_amounts(amounts), + ) + .unwrap(), + ) + } } pub mod psp22_utils {