Skip to content

Commit

Permalink
Merge pull request #629 from galacticcouncil/feat/stableswap-amplific…
Browse files Browse the repository at this point in the history
…ation-rework

feat: stableswap gradual amplification change
  • Loading branch information
enthusiastmartin authored Jul 6, 2023
2 parents d897478 + e574d29 commit 371a6f4
Show file tree
Hide file tree
Showing 19 changed files with 1,057 additions and 242 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion math/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.3.0"
version = "7.4.0"

[dependencies]
primitive-types = {default-features = false, version = '0.12.0'}
Expand Down
30 changes: 30 additions & 0 deletions math/src/stableswap/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::types::Balance;
use num_traits::{CheckedDiv, CheckedMul, Zero};
use primitive_types::U256;
use sp_arithmetic::{FixedPointNumber, FixedU128, Permill};
use sp_std::ops::Div;
use sp_std::prelude::*;

pub const MAX_Y_ITERATIONS: u8 = 128;
Expand Down Expand Up @@ -346,6 +347,35 @@ pub(crate) fn calculate_y<const N: u8>(xp: &[Balance], d: Balance, amplification
Balance::try_from(y).ok()
}

pub fn calculate_amplification(
initial_amplification: u128,
final_amplification: u128,
initial_block: u128,
final_block: u128,
current_block: u128,
) -> u128 {
// short circuit if block parameters are invalid or start block is not reached yet
if current_block < initial_block || final_block <= initial_block {
return initial_amplification;
}

// short circuit if already reached desired block
if current_block >= final_block {
return final_amplification;
}

let step = final_amplification
.abs_diff(initial_amplification)
.saturating_mul(current_block.saturating_sub(initial_block))
.div(final_block.saturating_sub(initial_block));

if final_amplification > initial_amplification {
initial_amplification.saturating_add(step)
} else {
initial_amplification.saturating_sub(step)
}
}

#[inline]
fn has_converged(v0: U256, v1: U256, precision: U256) -> bool {
let diff = abs_diff(v0, v1);
Expand Down
41 changes: 41 additions & 0 deletions math/src/stableswap/tests/amplification.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::stableswap::calculate_amplification;

#[test]
fn calculate_amplification_should_short_circuit_when_future_and_initial_amp_are_equal() {
let result = calculate_amplification(10, 10, 0, 100, 50);
assert_eq!(result, 10);
}

#[test]
fn calculate_amplification_should_short_circuit_when_current_timestamp_is_greater_than_future_timestamp() {
let result = calculate_amplification(10, 20, 0, 100, 150);
assert_eq!(result, 20);
}

#[test]
fn test_calculate_amplification_increase_amplification() {
let result = calculate_amplification(10, 20, 0, 100, 50);
assert_eq!(result, 15);
}

#[test]
fn test_calculate_amplification_decrease_amplification() {
let result = calculate_amplification(20, 10, 0, 100, 50);
assert_eq!(result, 15);
}

#[test]
fn test_calculate_amplification_step_increase_amplification() {
for idx in 0..1000 {
let result = calculate_amplification(2000, 5000, 0, 1000, idx);
assert_eq!(result, 2000 + idx * 3);
}
}

#[test]
fn test_calculate_amplification_step_decrease_amplification() {
for idx in 0..1000 {
let result = calculate_amplification(5000, 2000, 0, 1000, idx);
assert_eq!(result, 5000 - idx * 3);
}
}
1 change: 1 addition & 0 deletions math/src/stableswap/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod amplification;
mod invariants;
mod multi_assets;
mod two_assets;
Expand Down
6 changes: 4 additions & 2 deletions pallets/stableswap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = 'pallet-stableswap'
version = '1.3.0'
version = '2.0.0'
description = 'AMM for correlated assets'
authors = ['GalacticCouncil']
edition = '2021'
Expand All @@ -17,6 +17,7 @@ bitflags = "1.3.2"
# parity
scale-info = { version = "2.1.2", default-features = false, features = ["derive"] }
codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.4.0" }
serde = { features = ["derive"], optional = true, version = "1.0.137" }

# primitives
sp-runtime = { workspace = true }
Expand Down Expand Up @@ -55,14 +56,15 @@ runtime-benchmarks = [
"hydra-dx-math/runtime-benchmarks",
]
std = [
"serde/std",
'codec/std',
"scale-info/std",
'frame-support/std',
'frame-system/std',
'sp-runtime/std',
'sp-core/std',
'sp-io/std',
'sp-std/std',
"scale-info/std",
"orml-tokens/std",
"frame-benchmarking/std",
"hydra-dx-math/std",
Expand Down
87 changes: 79 additions & 8 deletions pallets/stableswap/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use frame_benchmarking::account;
use frame_benchmarking::benchmarks;
use frame_support::pallet_prelude::DispatchError;
use frame_support::traits::EnsureOrigin;
use frame_system::RawOrigin;
use frame_system::{Pallet as System, RawOrigin};
use orml_traits::MultiCurrency;
use orml_traits::MultiCurrencyExtended;
use sp_runtime::Permill;
Expand Down Expand Up @@ -179,7 +179,6 @@ benchmarks! {
assert_eq!(T::Currency::free_balance(asset_id_to_withdraw, &lp_provider), 1296846466078107);
}


sell{
let caller: T::AccountId = account("caller", 0, 1);
let lp_provider: T::AccountId = account("provider", 0, 1);
Expand Down Expand Up @@ -223,7 +222,6 @@ benchmarks! {
withdraw_fee,
)?;

// Worst case is adding additional liquidity and not initial liquidity
crate::Pallet::<T>::add_liquidity(RawOrigin::Signed(caller).into(),
pool_id,
initial,
Expand All @@ -236,6 +234,16 @@ benchmarks! {

let buy_min_amount = 1_000u128;

// Worst case is when amplification is changing
crate::Pallet::<T>::update_amplification(RawOrigin::Root.into(),
pool_id,
1000,
100u32.into(),
1000u32.into(),
)?;

System::<T>::set_block_number(500u32.into());

}: _(RawOrigin::Signed(seller.clone()), pool_id, asset_in, asset_out, amount_sell, buy_min_amount)
verify {
assert!(T::Currency::free_balance(asset_in, &seller) == 0u128);
Expand Down Expand Up @@ -285,7 +293,6 @@ benchmarks! {
withdraw_fee,
)?;

// Worst case is adding additional liquidity and not initial liquidity
crate::Pallet::<T>::add_liquidity(RawOrigin::Signed(caller).into(),
pool_id,
initial,
Expand All @@ -298,6 +305,16 @@ benchmarks! {
let amount_buy = 10_000_000_000_000u128;
let sell_max_limit = 20_000_000_000_000_000u128;

// Worst case is when amplification is changing
crate::Pallet::<T>::update_amplification(RawOrigin::Root.into(),
pool_id,
1000,
100u32.into(),
1000u32.into(),
)?;

System::<T>::set_block_number(500u32.into());

}: _(RawOrigin::Signed(buyer.clone()), pool_id, asset_out, asset_in, amount_buy, sell_max_limit)
verify {
assert!(T::Currency::free_balance(asset_out, &buyer) > 0u128);
Expand Down Expand Up @@ -351,7 +368,7 @@ benchmarks! {
assert_ne!(asset_tradability_old, asset_tradability_new);
}

update_pool {
update_pool_fees{
let caller: T::AccountId = account("caller", 0, 1);
let lp_provider: T::AccountId = account("provider", 0, 1);
let initial_liquidity = 1_000_000_000_000_000u128;
Expand Down Expand Up @@ -387,16 +404,70 @@ benchmarks! {
Permill::from_percent(1),
)?;

let amplification_new = Some(200_u16);
let trade_fee_new = Some(Permill::from_percent(50));
let withdraw_fee_new = Some(Permill::from_percent(40));
}: _<T::RuntimeOrigin>(successful_origin, pool_id, amplification_new, trade_fee_new, withdraw_fee_new)
}: _<T::RuntimeOrigin>(successful_origin, pool_id, trade_fee_new, withdraw_fee_new)
verify {
let pool = crate::Pallet::<T>::pools(pool_id).unwrap();
assert_eq!(pool.amplification, amplification_new.unwrap());
assert_eq!(pool.trade_fee, trade_fee_new.unwrap());
assert_eq!(pool.withdraw_fee, withdraw_fee_new.unwrap());
}

update_amplification{
let caller: T::AccountId = account("caller", 0, 1);
let lp_provider: T::AccountId = account("provider", 0, 1);
let initial_liquidity = 1_000_000_000_000_000u128;
let liquidity_added = 300_000_000_000_000u128;

let mut initial: Vec<AssetLiquidity<T::AssetId>> = vec![];
let mut added_liquidity: Vec<AssetLiquidity<T::AssetId>> = vec![];

let mut asset_ids: Vec<T::AssetId> = Vec::new() ;
for idx in 0..MAX_ASSETS_IN_POOL {
let name: Vec<u8> = idx.to_ne_bytes().to_vec();
let asset_id = T::AssetRegistry::create_asset(&name, 1u128)?;
asset_ids.push(asset_id);
T::Currency::update_balance(asset_id, &caller, 1_000_000_000_000_000i128)?;
T::Currency::update_balance(asset_id, &lp_provider, 1_000_000_000_000_000_000_000i128)?;
initial.push(AssetLiquidity{
asset_id,
amount: initial_liquidity
});
added_liquidity.push(AssetLiquidity{
asset_id,
amount: liquidity_added
});
}
let pool_id = T::AssetRegistry::create_asset(&b"pool".to_vec(), 1u128)?;

let successful_origin = T::AuthorityOrigin::try_successful_origin().unwrap();
crate::Pallet::<T>::create_pool(successful_origin.clone(),
pool_id,
asset_ids,
100u16,
Permill::from_percent(1),
Permill::from_percent(1),
)?;

// Worst case is when amplification is changing
crate::Pallet::<T>::update_amplification(RawOrigin::Root.into(),
pool_id,
1000,
100u32.into(),
1000u32.into(),
)?;

System::<T>::set_block_number(500u32.into());

}: _<T::RuntimeOrigin>(successful_origin, pool_id, 5000, 501u32.into(), 1000u32.into())
verify {
let pool = crate::Pallet::<T>::pools(pool_id).unwrap();

assert_eq!(pool.initial_amplification, NonZeroU16::new(500).unwrap());
assert_eq!(pool.final_amplification, NonZeroU16::new(5000).unwrap());
assert_eq!(pool.initial_block, 501u32.into());
assert_eq!(pool.final_block, 1000u32.into());
}

impl_benchmark_test_suite!(Pallet, crate::tests::mock::ExtBuilder::default().build(), crate::tests::mock::Test);
}
Loading

0 comments on commit 371a6f4

Please sign in to comment.