Skip to content

Commit

Permalink
fix: fix max_slippage to be same as fee with SCALE_FACTOR
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelPretorius committed Sep 4, 2024
1 parent 45f974e commit aa0c30c
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 47 deletions.
18 changes: 9 additions & 9 deletions ixo-swap/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::state::{
};
use crate::token_amount::TokenAmount;
use crate::utils::{
decimal_to_uint128, MIN_FEE_PERCENT, PREDEFINED_MAX_FEES_PERCENT, PREDEFINED_MAX_SLIPPAGE_PERCENT, SCALE_FACTOR
calculate_amount_with_percent, decimal_to_uint128, MIN_FEE_PERCENT, PREDEFINED_MAX_FEES_PERCENT, PREDEFINED_MAX_SLIPPAGE_PERCENT, SCALE_FACTOR
};

// Version info for migration info
Expand Down Expand Up @@ -580,12 +580,12 @@ fn validate_slippage(
min_token_amount: Uint128,
actual_token_amount: Uint128,
) -> Result<(), ContractError> {
let max_slippage_percent = MAX_SLIPPAGE_PERCENT.load(deps.storage)?;
let max_slippage = MAX_SLIPPAGE_PERCENT.load(deps.storage)?;
let max_slippage_percent = decimal_to_uint128(max_slippage)?;

let slippage_impact = calculate_amount_with_percent(actual_token_amount, max_slippage_percent)?;

let actual_token_decimal_amount = Decimal::from_str(actual_token_amount.to_string().as_str())?;
let slippage_impact = actual_token_decimal_amount * max_slippage_percent;
let min_required_decimal_amount = actual_token_decimal_amount - slippage_impact;
let min_required_amount = min_required_decimal_amount.to_uint_floor();
let min_required_amount = actual_token_amount - slippage_impact;

if min_token_amount < min_required_amount {
return Err(ContractError::MinTokenAmountError {
Expand Down Expand Up @@ -2179,7 +2179,7 @@ mod tests {
let mut deps = mock_dependencies();

MAX_SLIPPAGE_PERCENT
.save(&mut deps.storage, &Decimal::from_str("0.1").unwrap())
.save(&mut deps.storage, &Decimal::from_str("2").unwrap())
.unwrap();

let min_token_amount = Uint128::new(95_000);
Expand All @@ -2191,7 +2191,7 @@ mod tests {
err,
ContractError::MinTokenAmountError {
min_token: min_token_amount,
min_required: Uint128::new(99_000)
min_required: Uint128::new(107_800) // 110_000 * 0.98 (2% slippage) = 107_800
}
);
}
Expand All @@ -2204,7 +2204,7 @@ mod tests {
.save(&mut deps.storage, &Decimal::from_str("0.1").unwrap())
.unwrap();

let min_token_amount = Uint128::new(108_000);
let min_token_amount = Uint128::new(109_900); // 110_000 * 0.999 (0.1% slippage) = 109_890
let actual_token_amount = Uint128::new(110_000);
let res = validate_slippage(&deps.as_mut(), min_token_amount, actual_token_amount).unwrap();

Expand Down
50 changes: 25 additions & 25 deletions ixo-swap/src/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ fn cw1155_to_cw20_swap() {

let token_ids = vec![TokenId::from("FIRST/1"), TokenId::from("FIRST/2")];

let max_slippage_percent = Decimal::from_str("0.3").unwrap();
let max_slippage_percent = Decimal::from_str("8").unwrap();

let lp_fee_percent = Decimal::from_str("0.2").unwrap();
let protocol_fee_percent = Decimal::from_str("0.1").unwrap();
Expand Down Expand Up @@ -899,7 +899,6 @@ fn cw1155_to_cw20_swap() {
let res = router
.execute_contract(owner.clone(), amm.clone(), &swap_msg, &[])
.unwrap();
println!("res: {:?}", res);
let event = Event::new("wasm").add_attributes(vec![
attr("action", "swap"),
attr("sender", owner.clone()),
Expand Down Expand Up @@ -938,8 +937,8 @@ fn cw1155_to_cw20_swap() {
input_token: TokenSelect::Token2,
input_amount: TokenAmount::Single(Uint128::new(60_000)),
min_output: TokenAmount::Multiple(HashMap::from([
(token_ids[0].clone(), Uint128::new(30_000)),
(token_ids[1].clone(), Uint128::new(30_000)),
(token_ids[0].clone(), Uint128::new(33_000)),
(token_ids[1].clone(), Uint128::new(33_000)),
])),
expiration: None,
};
Expand All @@ -950,7 +949,7 @@ fn cw1155_to_cw20_swap() {
// ensure balances updated
let owner_balance =
batch_balance_for_owner(&router, &cw1155_token, &owner, &token_ids).balances;
assert_eq!(owner_balance, [Uint128::new(65_878), Uint128::new(55_000)]);
assert_eq!(owner_balance, [Uint128::new(62_878), Uint128::new(58_000)]);
let owner_balance = cw20_token.balance(&router.wrap(), owner.clone()).unwrap();
assert_eq!(owner_balance, Uint128::new(23_266));
let fee_recipient_balance = cw20_token
Expand Down Expand Up @@ -984,7 +983,7 @@ fn cw1155_to_native_swap() {
let cw1155_token = create_cw1155(&mut router, &owner);
let token_ids = vec![TokenId::from("FIRST/1"), TokenId::from("FIRST/2")];

let max_slippage_percent = Decimal::from_str("0.3").unwrap();
let max_slippage_percent = Decimal::from_str("8").unwrap();

let lp_fee_percent = Decimal::from_str("0.2").unwrap();
let protocol_fee_percent = Decimal::from_str("0.1").unwrap();
Expand Down Expand Up @@ -1087,8 +1086,8 @@ fn cw1155_to_native_swap() {
input_token: TokenSelect::Token2,
input_amount: TokenAmount::Single(Uint128::new(60_000)),
min_output: TokenAmount::Multiple(HashMap::from([
(token_ids[0].clone(), Uint128::new(30_000)),
(token_ids[1].clone(), Uint128::new(30_000)),
(token_ids[0].clone(), Uint128::new(34_000)),
(token_ids[1].clone(), Uint128::new(34_000)),
])),
expiration: None,
};
Expand All @@ -1107,7 +1106,7 @@ fn cw1155_to_native_swap() {
// ensure balances updated
let owner_balance =
batch_balance_for_owner(&router, &cw1155_token, &owner, &token_ids).balances;
assert_eq!(owner_balance, [Uint128::new(65_878), Uint128::new(55_000)]);
assert_eq!(owner_balance, [Uint128::new(61_878), Uint128::new(59_000)]);
let owner_balance: Coin = bank_balance(&mut router, &owner, NATIVE_TOKEN_DENOM.to_string());
assert_eq!(owner_balance.amount, Uint128::new(23_266));
let fee_recipient_balance = bank_balance(
Expand Down Expand Up @@ -1143,7 +1142,7 @@ fn cw1155_to_native_swap_low_fees() {
let cw1155_token = create_cw1155(&mut router, &owner);
let token_ids = vec![TokenId::from("FIRST/1"), TokenId::from("FIRST/2")];

let max_slippage_percent = Decimal::from_str("0.3").unwrap();
let max_slippage_percent = Decimal::from_str("8").unwrap();

let lp_fee_percent = Decimal::from_str("0.0").unwrap();
let protocol_fee_percent = Decimal::from_str("0.01").unwrap();
Expand Down Expand Up @@ -1223,7 +1222,7 @@ fn cw1155_to_native_swap_low_fees() {
(token_ids[0].clone(), Uint128::new(5_000)),
(token_ids[1].clone(), Uint128::new(5_000)),
])),
min_output: TokenAmount::Single(Uint128::new(6_500)),
min_output: TokenAmount::Single(Uint128::new(8_362)),
expiration: None,
};
let _res = router
Expand All @@ -1247,8 +1246,8 @@ fn cw1155_to_native_swap_low_fees() {
input_token: TokenSelect::Token2,
input_amount: TokenAmount::Single(Uint128::new(7_000)),
min_output: TokenAmount::Multiple(HashMap::from([
(token_ids[0].clone(), Uint128::new(3_000)),
(token_ids[1].clone(), Uint128::new(3_000)),
(token_ids[0].clone(), Uint128::new(3_700)),
(token_ids[1].clone(), Uint128::new(3_700)),
])),
expiration: None,
};
Expand All @@ -1267,7 +1266,7 @@ fn cw1155_to_native_swap_low_fees() {
// ensure balances updated
let owner_balance =
batch_balance_for_owner(&router, &cw1155_token, &owner, &token_ids).balances;
assert_eq!(owner_balance, [Uint128::new(49_863), Uint128::new(48_000)]);
assert_eq!(owner_balance, [Uint128::new(49_163), Uint128::new(48_700)]);
let owner_balance: Coin = bank_balance(&mut router, &owner, NATIVE_TOKEN_DENOM.to_string());
assert_eq!(owner_balance.amount, Uint128::new(52_090));
let fee_recipient_balance = bank_balance(
Expand Down Expand Up @@ -1336,7 +1335,7 @@ fn amm_add_and_remove_liquidity() {

let cw1155_token = create_cw1155(&mut router, &owner);

let max_slippage_percent = Decimal::from_str("0.3").unwrap();
let max_slippage_percent = Decimal::from_str("1").unwrap();

let supported_denom = "CARBON".to_string();
let token_ids = vec![
Expand Down Expand Up @@ -1718,7 +1717,7 @@ fn amm_add_and_remove_liquidity() {
let remove_liquidity_msg = ExecuteMsg::RemoveLiquidity {
amount: Uint128::new(50),
min_token1155: TokenAmount::Multiple(HashMap::from([
(token_ids[0].clone(), Uint128::new(35)),
(token_ids[0].clone(), Uint128::new(45)),
(token_ids[1].clone(), Uint128::new(5)),
])),
min_token2: Uint128::new(50),
Expand All @@ -1741,12 +1740,12 @@ fn amm_add_and_remove_liquidity() {
// ensure balances updated
let owner_balance =
batch_balance_for_owner(&router, &cw1155_token, &owner, &token_ids).balances;
assert_eq!(owner_balance, [Uint128::new(4915), Uint128::new(4985)]);
assert_eq!(owner_balance, [Uint128::new(4925), Uint128::new(4975)]);
let token_supplies = get_owner_lp_tokens_balance(&router, &amm_addr, &token_ids).supplies;
assert_eq!(token_supplies, [Uint128::new(85), Uint128::new(15)]);
assert_eq!(token_supplies, [Uint128::new(75), Uint128::new(25)]);
let amm_balances =
batch_balance_for_owner(&router, &cw1155_token, &amm_addr, &token_ids).balances;
assert_eq!(amm_balances, [Uint128::new(85), Uint128::new(15)]);
assert_eq!(amm_balances, [Uint128::new(75), Uint128::new(25)]);
let crust_balance = lp_token.balance(&router.wrap(), owner.clone()).unwrap();
assert_eq!(crust_balance, Uint128::new(100));

Expand All @@ -1763,8 +1762,8 @@ fn amm_add_and_remove_liquidity() {
let remove_liquidity_msg = ExecuteMsg::RemoveLiquidity {
amount: Uint128::new(100),
min_token1155: TokenAmount::Multiple(HashMap::from([
(token_ids[0].clone(), Uint128::new(85)),
(token_ids[1].clone(), Uint128::new(15)),
(token_ids[0].clone(), Uint128::new(75)),
(token_ids[1].clone(), Uint128::new(25)),
])),
min_token2: Uint128::new(100),
expiration: None,
Expand Down Expand Up @@ -1803,7 +1802,7 @@ fn remove_liquidity_with_partially_and_any_filling() {

let cw1155_token = create_cw1155(&mut router, &owner);

let max_slippage_percent = Decimal::from_str("0.3").unwrap();
let max_slippage_percent = Decimal::from_str("5").unwrap();

let supported_denom = "CARBON".to_string();
let token_ids = vec![
Expand Down Expand Up @@ -1938,8 +1937,9 @@ fn remove_liquidity_with_partially_and_any_filling() {
let remove_liquidity_msg = ExecuteMsg::RemoveLiquidity {
amount: Uint128::new(80),
min_token1155: TokenAmount::Multiple(HashMap::from([
(token_ids[0].clone(), Uint128::new(40)),
(token_ids[0].clone(), Uint128::new(41)),
(token_ids[1].clone(), Uint128::new(30)),
(token_ids[2].clone(), Uint128::new(5)),
])),
min_token2: Uint128::new(80),
expiration: None,
Expand Down Expand Up @@ -1996,8 +1996,8 @@ fn remove_liquidity_with_partially_and_any_filling() {

let remove_liquidity_msg = ExecuteMsg::RemoveLiquidity {
amount: Uint128::new(55),
min_token1155: TokenAmount::Single(Uint128::new(40)),
min_token2: Uint128::new(40),
min_token1155: TokenAmount::Single(Uint128::new(52)),
min_token2: Uint128::new(52),
expiration: None,
};
let _res = router
Expand Down
27 changes: 15 additions & 12 deletions ixo-swap/src/token_amount.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::{collections::HashMap, convert::TryFrom};
use std::collections::HashMap;

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{CheckedMultiplyFractionError, Decimal, Uint128};
use cw1155::TokenId;

use crate::{
error::ContractError,
utils::{decimal_to_uint128, SCALE_FACTOR},
utils::{calculate_amount_with_percent, decimal_to_uint128},
};

#[cw_serde]
Expand Down Expand Up @@ -133,16 +133,8 @@ impl TokenAmount {
input_amount: Uint128,
percent: Uint128,
) -> Result<TokenAmount, CheckedMultiplyFractionError> {
if percent.is_zero() || input_amount.is_zero() {
return Ok(TokenAmount::Single(Uint128::zero()));
}

let fraction = (SCALE_FACTOR.u128(), 1u128);
let result = input_amount
.full_mul(percent)
.checked_div_ceil(fraction)
.map_err(|err| err)?;
Ok(TokenAmount::Single(Uint128::try_from(result)?))
let result = calculate_amount_with_percent(input_amount, percent)?;
Ok(TokenAmount::Single(result))
}
}

Expand Down Expand Up @@ -175,6 +167,17 @@ mod tests {
assert_eq!(fee.get_total(), Uint128::new(1))
}

#[test]
fn should_return_zero_when_input_is_zero() {
let token_amount = TokenAmount::Single(Uint128::new(0));
let fee = token_amount
.get_percent(Decimal::from_str("0.1").unwrap())
.unwrap()
.unwrap();

assert_eq!(fee.get_total(), Uint128::new(0))
}

#[test]
fn should_return_fee_amount_when_multiple_input_token_provided_and_two_token_amount_are_over() {
let token_amount = TokenAmount::Multiple(HashMap::from([
Expand Down
22 changes: 21 additions & 1 deletion ixo-swap/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use cosmwasm_std::{Decimal, StdError, StdResult, Uint128};
use std::convert::TryFrom;

use cosmwasm_std::{CheckedMultiplyFractionError, Decimal, StdError, StdResult, Uint128};

/// The minimum fee percent allowed is 0.01%, based of the SCALE_FACTOR,
/// otherwise it will always end up with 0 fee if lower than 0.01%
Expand All @@ -8,6 +10,7 @@ pub const PREDEFINED_MAX_FEES_PERCENT: &str = "5";
pub const PREDEFINED_MAX_SLIPPAGE_PERCENT: &str = "10";
pub const DECIMAL_PRECISION: Uint128 = Uint128::new(10u128.pow(20));

/// Converts a Decimal to a Uint128 with the SCALE_FACTOR applied, so that Uint128::1 is 0.01%
pub fn decimal_to_uint128(decimal: Decimal) -> StdResult<Uint128> {
let result: Uint128 = decimal
.atomics()
Expand All @@ -16,3 +19,20 @@ pub fn decimal_to_uint128(decimal: Decimal) -> StdResult<Uint128> {

Ok(result / DECIMAL_PRECISION)
}

// Utility function to calculate amount based on percent
pub fn calculate_amount_with_percent(
input_amount: Uint128,
percent: Uint128,
) -> Result<Uint128, CheckedMultiplyFractionError> {
if percent.is_zero() || input_amount.is_zero() {
return Ok(Uint128::zero());
}

let fraction = (SCALE_FACTOR.u128(), 1u128);
let result = input_amount
.full_mul(percent)
.checked_div_ceil(fraction)
.map_err(|err| err)?;
Ok(Uint128::try_from(result)?)
}

0 comments on commit aa0c30c

Please sign in to comment.