diff --git a/Cargo.lock b/Cargo.lock index edf9adf3c..d908e9410 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3685,7 +3685,7 @@ dependencies = [ [[package]] name = "hydra-dx-math" -version = "7.4.1" +version = "7.4.2" dependencies = [ "approx", "criterion", @@ -7316,7 +7316,7 @@ dependencies = [ [[package]] name = "pallet-stableswap" -version = "2.1.0" +version = "2.1.1" dependencies = [ "bitflags", "frame-benchmarking", diff --git a/math/Cargo.toml b/math/Cargo.toml index 5f0bfb8c0..47191d1c9 100644 --- a/math/Cargo.toml +++ b/math/Cargo.toml @@ -6,7 +6,7 @@ license = 'Apache-2.0' name = "hydra-dx-math" description = "A collection of utilities to make performing liquidity pool calculations more convenient." repository = 'https://github.com/galacticcouncil/hydradx-math' -version = "7.4.1" +version = "7.4.2" [dependencies] primitive-types = {default-features = false, version = '0.12.0'} diff --git a/math/src/stableswap/tests/multi_assets.rs b/math/src/stableswap/tests/multi_assets.rs index f0e66986f..7d1fb0fde 100644 --- a/math/src/stableswap/tests/multi_assets.rs +++ b/math/src/stableswap/tests/multi_assets.rs @@ -347,3 +347,111 @@ fn calculate_withdraw_should_return_correct_amount_when_removing_provided_shares assert_eq!(result, (4993u128, 0u128)); } + +#[test] +fn calculate_out_given_in_with_fee_should_work_when_reserves_have_different_precision() { + let amp = 1000_u128; + + let balances: [Balance; 3] = [1_000_000_000, 3_000_000_000, 5_000_000_000_000_000_000_000]; + + let result = calculate_out_given_in_with_fee::( + &balances, + 1, + 2, + 1_000_000, + amp, + Permill::from_percent(1), + ); + assert_eq!(result.unwrap(), (824_786_715_118_092_963, 8_331_178_940_586_797)); + + let result = calculate_out_given_in_with_fee::( + &balances, + 2, + 1, + 1_000_000_000_000_000_000, + amp, + Permill::from_percent(1), + ); + assert_eq!(result.unwrap(), (1_187_653, 11996)); +} + +#[test] +fn calculate_out_given_in_with_zero_fee_should_work_when_reserves_have_different_precision() { + let amp = 1000_u128; + + let balances: [Balance; 3] = [1_000_000_000, 3_000_000_000, 5_000_000_000_000_000_000_000]; + + let result = calculate_out_given_in_with_fee::( + &balances, + 1, + 2, + 1_000_000, + amp, + Permill::from_percent(0), + ); + assert_eq!(result.unwrap(), (824_786_715_118_092_963 + 8_331_178_940_586_797, 0)); + + let result = calculate_out_given_in_with_fee::( + &balances, + 2, + 1, + 1_000_000_000_000_000_000, + amp, + Permill::from_percent(0), + ); + assert_eq!(result.unwrap(), (1_187_653 + 11996, 0)); +} + +#[test] +fn calculate_in_given_out_with_fee_should_work_when_reserves_have_different_precision() { + let amp = 1000_u128; + + let balances: [Balance; 3] = [1_000_000_000, 3_000_000_000, 5_000_000_000_000_000_000_000]; + + let result = calculate_in_given_out_with_fee::( + &balances, + 1, + 2, + 1_000_000_000_000_000_000, + amp, + Permill::from_percent(1), + ); + assert_eq!(result.unwrap(), (1212376, 12004)); + + let result = calculate_in_given_out_with_fee::( + &balances, + 2, + 1, + 1_000_000, + amp, + Permill::from_percent(1), + ); + assert_eq!(result.unwrap(), (841869902748480839, 8335345571767138)); +} + +#[test] +fn calculate_in_given_out_with_zero_fee_should_work_when_reserves_have_different_precision() { + let amp = 1000_u128; + + let balances: [Balance; 3] = [1_000_000_000, 3_000_000_000, 5_000_000_000_000_000_000_000]; + + let result = calculate_in_given_out_with_fee::( + &balances, + 1, + 2, + 1_000_000_000_000_000_000, + amp, + Permill::from_percent(0), + ); + assert_eq!(result.unwrap(), (1212376 - 12004, 0)); + + let result = calculate_in_given_out_with_fee::( + &balances, + 2, + 1, + 1_000_000, + amp, + Permill::from_percent(0), + ); + assert_eq!(result.unwrap(), (841869902748480839 - 8335345571767138, 0)); +} diff --git a/pallets/stableswap/Cargo.toml b/pallets/stableswap/Cargo.toml index 4209d600c..eec4595b9 100644 --- a/pallets/stableswap/Cargo.toml +++ b/pallets/stableswap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'pallet-stableswap' -version = '2.1.0' +version = '2.1.1' description = 'AMM for correlated assets' authors = ['GalacticCouncil'] edition = '2021' diff --git a/pallets/stableswap/src/lib.rs b/pallets/stableswap/src/lib.rs index 272e4d69f..768956783 100644 --- a/pallets/stableswap/src/lib.rs +++ b/pallets/stableswap/src/lib.rs @@ -631,7 +631,6 @@ pub mod pallet { ensure!(amount_out >= min_buy_amount, Error::::BuyLimitNotReached); let pool_account = Self::pool_account(pool_id); - T::Currency::transfer(asset_in, &who, &pool_account, amount_in)?; T::Currency::transfer(asset_out, &pool_account, &who, amount_out)?; @@ -746,8 +745,8 @@ pub mod pallet { impl Pallet { fn calculate_out_amount( pool_id: T::AssetId, - asset_out: T::AssetId, asset_in: T::AssetId, + asset_out: T::AssetId, amount_in: Balance, ) -> Result<(Balance, Balance), DispatchError> { let pool = Pools::::get(pool_id).ok_or(Error::::PoolNotFound)?; diff --git a/pallets/stableswap/src/tests/trades.rs b/pallets/stableswap/src/tests/trades.rs index 59f2fdae7..ed9124887 100644 --- a/pallets/stableswap/src/tests/trades.rs +++ b/pallets/stableswap/src/tests/trades.rs @@ -502,3 +502,141 @@ fn buy_should_fail_when_insufficient_amount_is_provided() { ); }); } + +#[test] +fn sell_should_work_when_pool_have_asset_with_various_decimals() { + let asset_a: AssetId = 1; + let asset_b: AssetId = 2; + let asset_c: AssetId = 3; + ExtBuilder::default() + .with_endowed_accounts(vec![(BOB, 1, 200 * ONE), (ALICE, 1, 200 * ONE), (ALICE, 2, 200 * ONE)]) + .with_registered_asset("one".as_bytes().to_vec(), 1) + .with_registered_asset("two".as_bytes().to_vec(), 2) + .with_registered_asset("three".as_bytes().to_vec(), 3) + .with_endowed_accounts(vec![ + (BOB, asset_c, ONE * 1_000_000), + (ALICE, asset_a, 2000 * ONE), + (ALICE, asset_b, 4000 * ONE), + (ALICE, asset_c, 10000 * ONE * 1_000_000), + ]) + .with_pool( + ALICE, + PoolInfo:: { + assets: vec![asset_a, asset_b, asset_c].try_into().unwrap(), + initial_amplification: NonZeroU16::new(1000).unwrap(), + final_amplification: NonZeroU16::new(1000).unwrap(), + initial_block: 0, + trade_fee: Permill::from_percent(0), + withdraw_fee: Permill::from_percent(0), + final_block: 0, + }, + InitialLiquidity { + account: ALICE, + assets: vec![ + AssetBalance { + asset_id: asset_a, + amount: 1_000_000_000, + }, + AssetBalance { + asset_id: asset_b, + amount: 3_000_000_000, + }, + AssetBalance { + asset_id: asset_c, + amount: 5000 * ONE * 1_000_000, + }, + ], + }, + ) + .build() + .execute_with(|| { + let pool_id = get_pool_id_at(0); + + assert_ok!(Stableswap::sell( + RuntimeOrigin::signed(BOB), + pool_id, + asset_c, + asset_b, + ONE * 1_000_000, + 0, + )); + + let expected = 1_199_649; + + let pool_account = pool_account(pool_id); + + assert_balance!(BOB, asset_c, 0); + assert_balance!(BOB, asset_b, expected); + assert_balance!(pool_account, asset_c, 5_001_000_000_000_000_000_000); + assert_balance!(pool_account, asset_b, 3_000_000_000 - expected); + }); +} + +#[test] +fn buy_should_work_when_pool_have_asset_with_various_decimals() { + let asset_a: AssetId = 1; + let asset_b: AssetId = 2; + let asset_c: AssetId = 3; + ExtBuilder::default() + .with_endowed_accounts(vec![(BOB, 1, 200 * ONE), (ALICE, 1, 200 * ONE), (ALICE, 2, 200 * ONE)]) + .with_registered_asset("one".as_bytes().to_vec(), 1) + .with_registered_asset("two".as_bytes().to_vec(), 2) + .with_registered_asset("three".as_bytes().to_vec(), 3) + .with_endowed_accounts(vec![ + (BOB, asset_c, ONE * 1_000_000), + (ALICE, asset_a, 2000 * ONE), + (ALICE, asset_b, 4000 * ONE), + (ALICE, asset_c, 10000 * ONE * 1_000_000), + ]) + .with_pool( + ALICE, + PoolInfo:: { + assets: vec![asset_a, asset_b, asset_c].try_into().unwrap(), + initial_amplification: NonZeroU16::new(1000).unwrap(), + final_amplification: NonZeroU16::new(1000).unwrap(), + initial_block: 0, + trade_fee: Permill::from_percent(0), + withdraw_fee: Permill::from_percent(0), + final_block: 0, + }, + InitialLiquidity { + account: ALICE, + assets: vec![ + AssetBalance { + asset_id: asset_a, + amount: 1_000_000_000, + }, + AssetBalance { + asset_id: asset_b, + amount: 3_000_000_000, + }, + AssetBalance { + asset_id: asset_c, + amount: 5000 * ONE * 1_000_000, + }, + ], + }, + ) + .build() + .execute_with(|| { + let pool_id = get_pool_id_at(0); + + assert_ok!(Stableswap::buy( + RuntimeOrigin::signed(BOB), + pool_id, + asset_b, + asset_c, + 1_199_649, + 2 * ONE * 1_000_000, + )); + + let expected = 1_199_649; + + let pool_account = pool_account(pool_id); + + assert_balance!(BOB, asset_c, 1_174_293_340_450); + assert_balance!(BOB, asset_b, expected); + assert_balance!(pool_account, asset_c, 5000999998825706659550); + assert_balance!(pool_account, asset_b, 3_000_000_000 - expected); + }); +}