From 5e7d5cd3920dd6094a6e7a090a81b51a4371a8c6 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 27 May 2024 18:39:37 +0400 Subject: [PATCH] feat(liq. manager): add PCL support --- Cargo.lock | 42 +++---- .../periphery/liquidity_manager/Cargo.toml | 5 +- .../periphery/liquidity_manager/src/query.rs | 59 +++++++++- .../periphery/liquidity_manager/src/utils.rs | 107 +++++++++++++++++- .../liquidity_manager/tests/helper.rs | 20 +++- .../tests/liquidity_manager_integration.rs | 59 +++++++++- packages/astroport/Cargo.toml | 2 +- packages/astroport_pcl_common/Cargo.toml | 2 +- packages/astroport_pcl_common/src/state.rs | 2 +- .../astroport-liquidity-manager.json | 2 +- 10 files changed, 264 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4ab7fce1..fb43e5a7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,7 +57,7 @@ dependencies = [ name = "astro-token-converter" version = "1.0.0" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.3", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -72,7 +72,7 @@ name = "astro-token-converter-neutron" version = "1.0.0" dependencies = [ "astro-token-converter", - "astroport 4.0.2", + "astroport 4.0.3", "cosmwasm-std", "cw-utils 1.0.3", "cw2 1.1.2", @@ -114,7 +114,7 @@ dependencies = [ [[package]] name = "astroport" -version = "4.0.2" +version = "4.0.3" dependencies = [ "astroport-circular-buffer 0.2.0", "cosmwasm-schema", @@ -173,7 +173,7 @@ name = "astroport-factory" version = "1.7.0" dependencies = [ "anyhow", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-pair 1.5.1", "cosmwasm-schema", "cosmwasm-std", @@ -208,7 +208,7 @@ name = "astroport-governance" version = "1.2.0" source = "git+https://github.com/astroport-fi/astroport-governance#182dd5bc201dd634995b5e4dc9e2774495693703" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.3", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", @@ -221,7 +221,7 @@ version = "1.1.0" dependencies = [ "anyhow", "astro-token-converter", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-factory 1.7.0", "astroport-native-coin-registry", "astroport-pair 1.5.1", @@ -243,15 +243,17 @@ dependencies = [ [[package]] name = "astroport-liquidity-manager" -version = "1.1.0" +version = "1.2.0" dependencies = [ "anyhow", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-factory 1.7.0", "astroport-incentives", "astroport-native-coin-registry", "astroport-pair 1.5.1", + "astroport-pair-concentrated 3.0.0", "astroport-pair-stable", + "astroport-pcl-common", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 1.0.0", @@ -269,7 +271,7 @@ name = "astroport-maker" version = "1.5.0" dependencies = [ "astro-satellite-package", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-factory 1.7.0", "astroport-native-coin-registry", "astroport-pair 1.5.1", @@ -289,7 +291,7 @@ name = "astroport-mocks" version = "0.2.0" dependencies = [ "anyhow", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-factory 1.7.0", "astroport-native-coin-registry", "astroport-pair 1.5.1", @@ -366,7 +368,7 @@ dependencies = [ name = "astroport-pair" version = "1.5.1" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.3", "astroport-factory 1.7.0", "astroport-mocks", "cosmwasm-schema", @@ -405,7 +407,7 @@ name = "astroport-pair-concentrated" version = "3.0.0" dependencies = [ "anyhow", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-circular-buffer 0.2.0", "astroport-factory 1.7.0", "astroport-mocks", @@ -431,7 +433,7 @@ version = "1.0.0" dependencies = [ "anyhow", "astro-token-converter", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-factory 1.7.0", "astroport-pair 1.3.3", "cosmwasm-schema", @@ -453,7 +455,7 @@ name = "astroport-pair-stable" version = "3.5.0" dependencies = [ "anyhow", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-circular-buffer 0.2.0", "astroport-factory 1.7.0", "astroport-mocks", @@ -498,7 +500,7 @@ dependencies = [ name = "astroport-pair-xyk-sale-tax" version = "1.6.0" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.3", "astroport-factory 1.7.0", "astroport-mocks", "astroport-pair 1.3.3", @@ -519,10 +521,10 @@ dependencies = [ [[package]] name = "astroport-pcl-common" -version = "2.0.0" +version = "2.0.1" dependencies = [ "anyhow", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-factory 1.7.0", "cosmwasm-schema", "cosmwasm-std", @@ -556,7 +558,7 @@ name = "astroport-staking" version = "2.1.0" dependencies = [ "anyhow", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-tokenfactory-tracker", "cosmwasm-schema", "cosmwasm-std", @@ -573,7 +575,7 @@ dependencies = [ name = "astroport-tokenfactory-tracker" version = "1.0.0" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.3", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 0.20.0 (git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks)", @@ -606,7 +608,7 @@ name = "astroport-vesting" version = "1.4.0" dependencies = [ "astro-token-converter", - "astroport 4.0.2", + "astroport 4.0.3", "astroport-vesting 1.3.1", "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/periphery/liquidity_manager/Cargo.toml b/contracts/periphery/liquidity_manager/Cargo.toml index a0b973942..6376b5350 100644 --- a/contracts/periphery/liquidity_manager/Cargo.toml +++ b/contracts/periphery/liquidity_manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-liquidity-manager" -version = "1.1.0" +version = "1.2.0" edition = "2021" description = "Astroport Liquidity Manager contract" license = "GPL-3.0-only" @@ -19,10 +19,13 @@ cosmwasm-schema.workspace = true cw-storage-plus.workspace = true cw20 = "1.1" thiserror.workspace = true +itertools.workspace = true astroport = { path = "../../../packages/astroport", version = "4" } cw20-base = { version = "1.1", features = ["library"] } astroport-pair = { path = "../../pair", features = ["library"], version = "1.5" } astroport-pair-stable = { path = "../../pair_stable", features = ["library"], version = "3" } +astroport-pair-concentrated = { path = "../../pair_concentrated", features = ["library"], version = "3" } +astroport-pcl-common = { path = "../../../packages/astroport_pcl_common", version = "2" } astroport-factory = { path = "../../factory", features = ["library"], version = "1" } [dev-dependencies] diff --git a/contracts/periphery/liquidity_manager/src/query.rs b/contracts/periphery/liquidity_manager/src/query.rs index b851d05b3..0be7d5939 100644 --- a/contracts/periphery/liquidity_manager/src/query.rs +++ b/contracts/periphery/liquidity_manager/src/query.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_json_binary, Binary, Deps, Env, StdError, StdResult, Uint128}; @@ -8,9 +10,13 @@ use astroport::liquidity_manager::QueryMsg; use astroport::pair::{ExecuteMsg as PairExecuteMsg, QueryMsg as PairQueryMsg}; use astroport::querier::query_supply; use astroport_pair::contract::get_share_in_assets; +use astroport_pcl_common::state::Precisions; use crate::error::ContractError; -use crate::utils::{convert_config, stableswap_provide_simulation, xyk_provide_simulation}; +use crate::utils::{ + convert_pcl_config, convert_stable_config, pcl_provide_simulation, + stableswap_provide_simulation, xyk_provide_simulation, +}; #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { @@ -102,7 +108,7 @@ fn simulate_provide( .querier .query_wasm_raw(pair_addr, b"config")? .ok_or_else(|| StdError::generic_err("pair stable config not found"))?; - let pair_config = convert_config(deps.querier, pair_config_data)?; + let pair_config = convert_stable_config(deps.querier, pair_config_data)?; to_json_binary( &stableswap_provide_simulation( deps.querier, @@ -114,7 +120,54 @@ fn simulate_provide( .map_err(|err| StdError::generic_err(format!("{err}")))?, ) } - PairType::Custom(..) => unimplemented!("not implemented yet"), + PairType::Custom(typ) if typ == "concentrated" => { + let balances = pair_info.query_pools(&deps.querier, &pair_addr)?; + let pcl_config_raw = deps + .querier + .query_wasm_raw(&pair_addr, b"config")? + .ok_or_else(|| StdError::generic_err("PCL config not found"))?; + let pcl_config = convert_pcl_config(pcl_config_raw)?; + let precisions = balances + .iter() + .map(|asset| { + let prec = Precisions::PRECISIONS + .query(&deps.querier, pair_addr.clone(), asset.info.to_string())? + .or_else(|| { + asset + .info + .decimals(&deps.querier, &pcl_config.factory_addr) + .ok() + }) + .ok_or_else(|| { + StdError::generic_err(format!( + "Asset {} precision not found", + &asset.info + )) + })?; + Ok((asset.info.to_string(), prec)) + }) + .collect::>>()?; + let dec_balances = balances + .into_iter() + .map(|asset| { + asset + .to_decimal_asset(*precisions.get(&asset.info.to_string()).unwrap()) + .map_err(Into::into) + }) + .collect::>>()?; + let total_share = query_supply(&deps.querier, &pair_info.liquidity_token)?; + pcl_provide_simulation( + env, + dec_balances, + assets, + total_share, + pcl_config, + precisions, + ) + .map_err(|err| StdError::generic_err(err.to_string())) + .and_then(|res| to_json_binary(&res)) + } + PairType::Custom(_) => unimplemented!("not implemented yet"), } } _ => Err(StdError::generic_err("Invalid simulate message")), diff --git a/contracts/periphery/liquidity_manager/src/utils.rs b/contracts/periphery/liquidity_manager/src/utils.rs index 737203814..b926acb39 100644 --- a/contracts/periphery/liquidity_manager/src/utils.rs +++ b/contracts/periphery/liquidity_manager/src/utils.rs @@ -1,21 +1,34 @@ use std::collections::HashMap; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{ from_json, Addr, Decimal, Decimal256, Env, QuerierWrapper, StdError, StdResult, Uint128, }; +use itertools::Itertools; -use astroport::asset::{Asset, Decimal256Ext, DecimalAsset, PairInfo, MINIMUM_LIQUIDITY_AMOUNT}; +use astroport::asset::{ + Asset, AssetInfo, Decimal256Ext, DecimalAsset, PairInfo, MINIMUM_LIQUIDITY_AMOUNT, +}; +use astroport::cosmwasm_ext::IntegerToDecimal; use astroport::incentives::QueryMsg as GeneratorQueryMsg; use astroport::liquidity_manager::CompatPairStableConfig; +use astroport::pair::FeeShareConfig; use astroport::querier::{query_supply, query_token_balance}; use astroport::U256; use astroport_pair::{ contract::assert_slippage_tolerance, error::ContractError as PairContractError, }; +use astroport_pair_concentrated::error::ContractError as PclContractError; use astroport_pair_stable::error::ContractError as StableContractError; use astroport_pair_stable::math::compute_d; use astroport_pair_stable::state::Config as PairStableConfig; use astroport_pair_stable::utils::compute_current_amp; +use astroport_pcl_common::state::{Config as PclConfig, PoolParams, PoolState}; +use astroport_pcl_common::utils::calc_provide_fee; +use astroport_pcl_common::{calc_d, get_xcp}; + +/// LP token's precision. +pub const LP_TOKEN_PRECISION: u8 = 6; pub fn query_lp_amount( querier: QuerierWrapper, @@ -243,7 +256,66 @@ pub fn stableswap_provide_simulation( Ok(share) } -pub fn convert_config( +pub fn pcl_provide_simulation( + env: Env, + balances: Vec, + deposit: Vec, + total_share: Uint128, + config: PclConfig, + precisions: HashMap, +) -> Result { + let to_dec = |index: usize| -> StdResult { + Decimal256::with_precision( + deposit[index].amount, + *precisions + .get(&deposit[index].info.to_string()) + .ok_or_else(|| { + StdError::generic_err(format!("Invalid asset {}", deposit[index].info)) + })?, + ) + }; + + let deposits = [to_dec(0)?, to_dec(1)?]; + + let mut new_xp = balances + .iter() + .zip(deposits.iter()) + .map(|(balance, deposit)| balance.amount + deposit) + .collect_vec(); + new_xp[1] *= config.pool_state.price_state.price_scale; + + let amp_gamma = config.pool_state.get_amp_gamma(&env); + let new_d = calc_d(&new_xp, &_gamma)?; + + let total_share = total_share.to_decimal256(LP_TOKEN_PRECISION)?; + + if total_share.is_zero() { + let xcp = get_xcp(new_d, config.pool_state.price_state.price_scale) + .to_uint128_with_precision(LP_TOKEN_PRECISION)?; + let mint_amount = xcp.saturating_sub(MINIMUM_LIQUIDITY_AMOUNT); + + // share cannot become zero after minimum liquidity subtraction + if mint_amount.is_zero() { + return Err(PclContractError::MinimumLiquidityAmountError {}); + } + + Ok(mint_amount) + } else { + let mut old_xp = balances.iter().map(|a| a.amount).collect_vec(); + old_xp[1] *= config.pool_state.price_state.price_scale; + let old_d = calc_d(&old_xp, &_gamma)?; + let share = (total_share * new_d / old_d).saturating_sub(total_share); + + let mut ideposits = deposits; + ideposits[1] *= config.pool_state.price_state.price_scale; + + let lp_amount = share + * (Decimal256::one() - calc_provide_fee(&ideposits, &new_xp, &config.pool_params)); + Ok(lp_amount.to_uint128_with_precision(LP_TOKEN_PRECISION)?) + } +} + +pub fn convert_stable_config( querier: QuerierWrapper, config_data: Vec, ) -> StdResult { @@ -274,3 +346,34 @@ pub fn convert_config( fee_share: None, }) } + +#[cw_serde] +pub struct CompatPclConfig { + pub pair_info: PairInfo, + pub factory_addr: Addr, + pub block_time_last: u64, + #[serde(default)] + pub cumulative_prices: Vec<(AssetInfo, AssetInfo, Uint128)>, + pub pool_params: PoolParams, + pub pool_state: PoolState, + pub owner: Option, + #[serde(default)] + pub track_asset_balances: bool, + pub fee_share: Option, +} + +pub fn convert_pcl_config(config_data: Vec) -> StdResult { + let compat_config: CompatPclConfig = from_json(config_data)?; + + Ok(PclConfig { + pair_info: compat_config.pair_info, + factory_addr: compat_config.factory_addr, + block_time_last: compat_config.block_time_last, + cumulative_prices: compat_config.cumulative_prices, + pool_params: compat_config.pool_params, + pool_state: compat_config.pool_state, + owner: compat_config.owner, + track_asset_balances: compat_config.track_asset_balances, + fee_share: compat_config.fee_share, + }) +} diff --git a/contracts/periphery/liquidity_manager/tests/helper.rs b/contracts/periphery/liquidity_manager/tests/helper.rs index 1d4442542..0d1e353b9 100644 --- a/contracts/periphery/liquidity_manager/tests/helper.rs +++ b/contracts/periphery/liquidity_manager/tests/helper.rs @@ -121,6 +121,17 @@ fn stable_pair_contract() -> Box> { ) } +fn pcl_pair_contract() -> Box> { + Box::new( + ContractWrapper::new_with_empty( + astroport_pair_concentrated::contract::execute, + astroport_pair_concentrated::contract::instantiate, + astroport_pair_concentrated::queries::query, + ) + .with_reply_empty(astroport_pair_concentrated::contract::reply), + ) +} + fn coin_registry_contract() -> Box> { Box::new(ContractWrapper::new_with_empty( astroport_native_coin_registry::contract::execute, @@ -208,11 +219,10 @@ impl Helper { pair_type = PairType::Stable {}; inner_params = to_json_binary(inner).unwrap(); } - PoolParams::Concentrated(_) => { - unimplemented!("Concentrated pool is not supported yet"); - // pair_code_id = app.store_code(pcl_pair_contract()); - // pair_type = PairType::Custom("concentrated".to_owned()); - // inner_params = to_json_binary(inner).unwrap(); + PoolParams::Concentrated(inner) => { + pair_code_id = app.store_code(pcl_pair_contract()); + pair_type = PairType::Custom("concentrated".to_owned()); + inner_params = to_json_binary(inner).unwrap(); } } diff --git a/contracts/periphery/liquidity_manager/tests/liquidity_manager_integration.rs b/contracts/periphery/liquidity_manager/tests/liquidity_manager_integration.rs index 57b934b8d..8ed10e480 100644 --- a/contracts/periphery/liquidity_manager/tests/liquidity_manager_integration.rs +++ b/contracts/periphery/liquidity_manager/tests/liquidity_manager_integration.rs @@ -1,9 +1,10 @@ #![cfg(not(tarpaulin_include))] -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::{Addr, Decimal, Uint128}; use astroport::asset::{native_asset, AssetInfoExt}; use astroport::pair::{StablePoolParams, XYKPoolParams}; +use astroport::pair_concentrated::ConcentratedPoolParams; use astroport_liquidity_manager::error::ContractError; use crate::helper::{f64_to_dec, Helper, PoolParams, TestCoin}; @@ -304,6 +305,62 @@ fn test_stableswap_with_manager() { ); } +#[test] +fn test_pcl_with_manager() { + let owner = Addr::unchecked("owner"); + let test_coins = vec![TestCoin::native("uusd"), TestCoin::cw20("UST")]; + let mut helper = Helper::new( + &owner, + test_coins.clone(), + PoolParams::Concentrated(ConcentratedPoolParams { + amp: f64_to_dec(10f64), + gamma: f64_to_dec(0.000145), + mid_fee: f64_to_dec(0.0026), + out_fee: f64_to_dec(0.0045), + fee_gamma: f64_to_dec(0.00023), + repeg_profit_threshold: f64_to_dec(0.000002), + min_price_scale_delta: f64_to_dec(0.000146), + price_scale: Decimal::one(), + ma_half_time: 600, + track_asset_balances: None, + fee_share: None, + }), + ) + .unwrap(); + + helper + .provide_liquidity( + &owner, + &[ + helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), + helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), + ], + None, + ) + .unwrap(); + + // Simulating LP tokens amount before provide + let provide_assets = [ + helper.assets[&test_coins[0]].with_balance(100_000000u128), + helper.assets[&test_coins[1]].with_balance(100_000000u128), + ]; + let sim_lp_amount = helper.simulate_provide(None, &provide_assets).unwrap(); + + // Provide with zero slippage allowance to ensure simulation gives exact same amount as PCL contract + let user2 = Addr::unchecked("user2"); + helper.give_me_money(&provide_assets, &user2); + helper + .provide_liquidity_with_slip_tolerance( + &user2, + &provide_assets, + Some(Decimal::zero()), + Some(sim_lp_amount), + false, + None, + ) + .unwrap(); +} + #[test] fn test_auto_stake_and_receiver() { let owner = Addr::unchecked("owner"); diff --git a/packages/astroport/Cargo.toml b/packages/astroport/Cargo.toml index 5aef75cc8..6d2988a08 100644 --- a/packages/astroport/Cargo.toml +++ b/packages/astroport/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport" -version = "4.0.2" +version = "4.0.3" authors = ["Astroport"] edition = "2021" description = "Common Astroport types, queriers and other utils" diff --git a/packages/astroport_pcl_common/Cargo.toml b/packages/astroport_pcl_common/Cargo.toml index 1afc34771..3f91bcbf8 100644 --- a/packages/astroport_pcl_common/Cargo.toml +++ b/packages/astroport_pcl_common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-pcl-common" -version = "2.0.0" +version = "2.0.1" edition = "2021" description = "Common package contains math tools and utils for Astroport PCL pairs" license = "GPL-3.0-only" diff --git a/packages/astroport_pcl_common/src/state.rs b/packages/astroport_pcl_common/src/state.rs index d18e070e4..a671fd62f 100644 --- a/packages/astroport_pcl_common/src/state.rs +++ b/packages/astroport_pcl_common/src/state.rs @@ -385,7 +385,7 @@ pub struct Precisions(Vec<(String, u8)>); impl<'a> Precisions { /// Stores map of AssetInfo (as String) -> precision - const PRECISIONS: Map<'a, String, u8> = Map::new("precisions"); + pub const PRECISIONS: Map<'a, String, u8> = Map::new("precisions"); pub fn new(storage: &dyn Storage) -> StdResult { let items = Self::PRECISIONS .range(storage, None, None, Order::Ascending) diff --git a/schemas/astroport-liquidity-manager/astroport-liquidity-manager.json b/schemas/astroport-liquidity-manager/astroport-liquidity-manager.json index dee07ae2a..6bfceecb5 100644 --- a/schemas/astroport-liquidity-manager/astroport-liquidity-manager.json +++ b/schemas/astroport-liquidity-manager/astroport-liquidity-manager.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-liquidity-manager", - "contract_version": "1.1.0", + "contract_version": "1.2.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#",