Skip to content

Commit

Permalink
Simplify ModifyRange implementation (#788)
Browse files Browse the repository at this point in the history
  • Loading branch information
lubkoll authored Aug 20, 2024
1 parent c4e8678 commit ae82da8
Show file tree
Hide file tree
Showing 5 changed files with 529 additions and 273 deletions.
11 changes: 3 additions & 8 deletions smart-contracts/osmosis/contracts/cl-vault/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use crate::vault::{
handle_merge_withdraw_position_reply,
},
range::{
execute_update_range, handle_initial_create_position_reply,
handle_iteration_create_position_reply, handle_swap_reply, handle_withdraw_position_reply,
execute_update_range, handle_create_position, handle_swap_reply,
handle_withdraw_position_reply,
},
swap::execute_swap_non_vault_funds,
withdraw::{execute_withdraw, handle_withdraw_user_reply},
Expand Down Expand Up @@ -200,12 +200,7 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result<Response, ContractEr
Replies::CollectIncentives => handle_collect_incentives_reply(deps, env, msg.result),
Replies::CollectSpreadRewards => handle_collect_spread_rewards_reply(deps, env, msg.result),
Replies::WithdrawPosition => handle_withdraw_position_reply(deps, env),
Replies::RangeInitialCreatePosition => {
handle_initial_create_position_reply(deps, env, msg.result)
}
Replies::RangeIterationCreatePosition => {
handle_iteration_create_position_reply(deps, env, msg.result)
}
Replies::CreatePosition => handle_create_position(deps, env, msg.result),
Replies::Swap => handle_swap_reply(deps, env),
Replies::Merge => handle_merge_reply(deps, env, msg.result),
Replies::CreateDenom => handle_create_denom_reply(deps, msg.result),
Expand Down
5 changes: 1 addition & 4 deletions smart-contracts/osmosis/contracts/cl-vault/src/reply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ pub enum Replies {

// withdraw position
WithdrawPosition,
// create position in the modify range inital step
RangeInitialCreatePosition,
// create position in the modify range iteration step
RangeIterationCreatePosition,
CreatePosition,
// swap
Swap,
/// Merge positions, used to merge positions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use cosmwasm_std::{Coin, Decimal256, DepsMut, Env, QuerierWrapper, Storage, Uint128};
use cosmwasm_std::{Coin, Decimal256, DepsMut, Env, QuerierWrapper, Storage, Uint128, Uint256};
use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::{
ConcentratedliquidityQuerier, FullPositionBreakdown, MsgCreatePosition, MsgWithdrawPosition,
Pool,
Expand Down Expand Up @@ -108,14 +108,73 @@ pub fn _may_get_position(
}
}

// see https://uniswap.org/whitepaper-v3.pdf for below formulas (eq 6.29 & 6.30)
pub fn get_liquidity_for_base_token(
amount: Uint256,
sqrt_p: Decimal256,
sqrt_pl: Decimal256,
sqrt_pu: Decimal256,
) -> Result<Uint256, ContractError> {
debug_assert!(
sqrt_p < sqrt_pu,
"can't compute liquidity if sqrt_p >= sqrt_pu"
);
if sqrt_p >= sqrt_pu {
return Ok(Uint256::MAX);
}
let sqrt_p = std::cmp::max(sqrt_p, sqrt_pl);
let delta_p = sqrt_pu - sqrt_p;
Ok(amount.checked_mul_floor(sqrt_pu.checked_mul(sqrt_p)?.checked_div(delta_p)?)?)
}

pub fn get_liquidity_for_quote_token(
amount: Uint256,
sqrt_p: Decimal256,
sqrt_pl: Decimal256,
sqrt_pu: Decimal256,
) -> Result<Uint256, ContractError> {
debug_assert!(
sqrt_p > sqrt_pl,
"can't compute liquidity if sqrt_p <= sqrt_pl"
);
if sqrt_p <= sqrt_pl {
return Ok(Uint256::MAX);
}
let sqrt_p = std::cmp::min(sqrt_p, sqrt_pu);
let delta_p = sqrt_p - sqrt_pl;
Ok(amount.checked_div_floor(delta_p)?)
}

pub fn get_amount_from_liquidity_for_base_token(
liq: Uint256,
sqrt_p: Decimal256,
sqrt_pl: Decimal256,
sqrt_pu: Decimal256,
) -> Result<Uint256, ContractError> {
let sqrt_p = std::cmp::max(sqrt_p, sqrt_pl);
let delta_p = sqrt_pu.checked_sub(sqrt_p).unwrap_or_default();
Ok(liq.checked_mul_floor(delta_p.checked_div(sqrt_pu.checked_mul(sqrt_p)?)?)?)
}

pub fn get_amount_from_liquidity_for_quote_token(
liq: Uint256,
sqrt_p: Decimal256,
sqrt_pl: Decimal256,
sqrt_pu: Decimal256,
) -> Result<Uint256, ContractError> {
let sqrt_p = std::cmp::min(sqrt_p, sqrt_pu);
let delta_p = sqrt_p.checked_sub(sqrt_pl).unwrap_or_default();
Ok(liq.checked_mul_floor(delta_p)?)
}

#[cfg(test)]
mod tests {
use crate::{
state::{PoolConfig, Position},
test_helpers::QuasarQuerier,
};
use cosmwasm_std::{
coin,
assert_approx_eq, coin,
testing::{mock_dependencies, mock_env},
Coin, Uint128,
};
Expand Down Expand Up @@ -225,4 +284,174 @@ mod tests {
}
);
}

#[test]
fn test_get_amount_from_liquidity_for_base_token_if_price_above_range() {
let liq = Uint256::from(50u64);
let sqrt_pl = Decimal256::percent(50);
let sqrt_pu = Decimal256::percent(100);
let sqrt_p = Decimal256::percent(200);
let amount =
get_amount_from_liquidity_for_base_token(liq, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_amount = Uint256::zero();
assert_eq!(amount, expected_amount);
}

#[test]
fn test_get_amount_from_liquidity_for_base_token_if_price_in_range() {
let liq = Uint256::from(50u64);
let sqrt_pl = Decimal256::percent(25);
let sqrt_pu = Decimal256::percent(100);
let sqrt_p = Decimal256::percent(50);
let amount =
get_amount_from_liquidity_for_base_token(liq, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_amount = liq;
assert_eq!(amount, expected_amount);
}

#[test]
fn test_get_amount_from_liquidity_for_base_token_if_price_below_range() {
let liq = Uint256::from(50u64);
let sqrt_pl = Decimal256::percent(50);
let sqrt_pu = Decimal256::percent(100);
let sqrt_p = Decimal256::percent(25);
let amount =
get_amount_from_liquidity_for_base_token(liq, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_amount = liq;
assert_eq!(amount, expected_amount);
}

#[test]
fn test_get_amount_from_liquidity_for_quote_token_if_price_below_range() {
let liq = Uint256::from(50u64);
let sqrt_pl = Decimal256::percent(50);
let sqrt_pu = Decimal256::percent(100);
let sqrt_p = Decimal256::percent(25);
let amount =
get_amount_from_liquidity_for_quote_token(liq, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_amount = Uint256::zero();
assert_eq!(amount, expected_amount);
}

#[test]
fn test_get_amount_from_liquidity_for_quote_token_if_price_in_range() {
let liq = Uint256::from(50u64);
let sqrt_pl = Decimal256::percent(25);
let sqrt_pu = Decimal256::percent(100);
let sqrt_p = Decimal256::percent(75);
let amount =
get_amount_from_liquidity_for_quote_token(liq, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_amount = Uint256::from(25u64);
assert_eq!(amount, expected_amount);
}

#[test]
fn test_get_amount_from_liquidity_for_quote_token_if_price_above_range() {
let liq = Uint256::from(50u64);
let sqrt_pl = Decimal256::percent(50);
let sqrt_pu = Decimal256::percent(100);
let sqrt_p = Decimal256::percent(200);
let amount =
get_amount_from_liquidity_for_quote_token(liq, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_amount = Uint256::from(25u64);
assert_eq!(amount, expected_amount);
}

#[test]
fn test_get_liquidity_from_amount_for_base_token_if_price_in_range() {
let amount = Uint256::from(150u64);
let sqrt_pl = Decimal256::percent(10);
let sqrt_pu = Decimal256::percent(100);
let sqrt_p = Decimal256::percent(25);
let liq = get_liquidity_for_base_token(amount, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_liq = Uint256::from(49u64);
assert_eq!(liq, expected_liq);
}

#[test]
fn test_get_liquidity_from_amount_for_base_token_if_price_below_range() {
let amount = Uint256::from(150u64);
let sqrt_pl = Decimal256::percent(25);
let sqrt_pu = Decimal256::percent(100);
let sqrt_p = Decimal256::percent(10);
let liq = get_liquidity_for_base_token(amount, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_liq = Uint256::from(49u64);
assert_eq!(liq, expected_liq);
}

#[test]
fn test_get_liquidity_from_amount_for_quote_token_if_price_in_range() {
let amount = Uint256::from(150u64);
let sqrt_pl = Decimal256::percent(10);
let sqrt_pu = Decimal256::percent(100);
let sqrt_p = Decimal256::percent(60);
let liq = get_liquidity_for_quote_token(amount, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_liq = Uint256::from(300u64);
assert_eq!(liq, expected_liq);
}

#[test]
fn test_get_liquidity_from_amount_for_quote_token_if_price_above_range() {
let amount = Uint256::from(150u64);
let sqrt_pl = Decimal256::percent(10);
let sqrt_pu = Decimal256::percent(60);
let sqrt_p = Decimal256::percent(100);
let liq = get_liquidity_for_quote_token(amount, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_liq = Uint256::from(300u64);
assert_eq!(liq, expected_liq);
}

// tests

#[test]
fn test_get_liquidity_for_base() {
let amount = Uint256::from(1_000_000u64);
let sqrt_pl = Decimal256::percent(65);
let sqrt_pu = Decimal256::percent(130);
let sqrt_p = Decimal256::one();
let liq = get_liquidity_for_base_token(amount, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_liq = Uint256::from(4_333_333u64);
assert_eq!(liq, expected_liq);

let final_amount: Uint128 =
get_amount_from_liquidity_for_base_token(liq, sqrt_p, sqrt_pl, sqrt_pu)
.unwrap()
.try_into()
.unwrap();
assert_approx_eq!(final_amount, amount.try_into().unwrap(), "0.000001");

let used_liquidity = Uint256::from(2_857_142u64);
let residual_liquidity = Uint256::from(4_333_333u64) - used_liquidity;
let residual_amount: Uint128 =
get_amount_from_liquidity_for_base_token(residual_liquidity, sqrt_p, sqrt_pl, sqrt_pu)
.unwrap()
.try_into()
.unwrap();
let expected_residual_amount = Uint128::from(340659u64);
assert_eq!(residual_amount, expected_residual_amount);

let used_amount =
get_amount_from_liquidity_for_base_token(used_liquidity, sqrt_p, sqrt_pl, sqrt_pu)
.unwrap();
let residual_amount: Uint128 = (amount - used_amount).try_into().unwrap();
let expected_residual_amount = Uint128::from(340660u64);
assert_eq!(residual_amount, expected_residual_amount);
}
#[test]
fn test_get_liquidity_for_quote() {
let amount = Uint256::from(1_000_000u64);
let sqrt_pl = Decimal256::percent(65);
let sqrt_pu = Decimal256::percent(130);
let sqrt_p = Decimal256::one();
let liq = get_liquidity_for_quote_token(amount, sqrt_p, sqrt_pl, sqrt_pu).unwrap();
let expected_liq = Uint256::from(2_857_142u64);
assert_eq!(liq, expected_liq);

let final_amount: Uint128 =
get_amount_from_liquidity_for_quote_token(liq, sqrt_p, sqrt_pl, sqrt_pu)
.unwrap()
.try_into()
.unwrap();
assert_approx_eq!(final_amount, amount.try_into().unwrap(), "0.000001");
}
}
Loading

0 comments on commit ae82da8

Please sign in to comment.