use const_format::concatcp;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
entry_point, from_json, to_json_binary, Addr, Binary, Decimal, Decimal256, Deps, DepsMut, Env, Event, MessageInfo, Response, StdError, StdResult, Uint128, Uint256, Uint64
};
use cw2::{get_contract_version, set_contract_version};
use cw_storage_plus::Item;
use itertools::Itertools;
use std::collections::HashMap;
use std::vec;
use crate::error::ContractError;
use crate::math::{compute_d, calc_spot_price, AMP_PRECISION, MAX_AMP, MAX_AMP_CHANGE, MIN_AMP_CHANGING_TIME};
use crate::state::{get_precision, AssetScalingFactor, MathConfig, StablePoolParams, StablePoolUpdateParams, StableSwapConfig, StableSwapConfigV1, Twap, CONFIG, MATHCONFIG, PRECISIONS, STABLESWAP_CONFIG, TWAPINFO};
use crate::utils::{accumulate_prices, compute_offer_amount, compute_swap};
use dexter::pool::{return_exit_failure, return_join_failure, return_swap_failure, store_precisions, update_fee, AfterExitResponse, AfterJoinResponse, Config, ConfigResponse, CumulativePriceResponse, CumulativePricesResponse, ExecuteMsg, ExitType, FeeResponse, InstantiateMsg, MigrateMsg, QueryMsg, ResponseType, SpotPrice, SwapResponse, Trade};
use dexter::asset::{Asset, AssetExchangeRate, AssetInfo, Decimal256Ext, DecimalAsset};
use dexter::helper::{calculate_underlying_fees, get_share_in_assets, select_pools, EventExt};
use dexter::querier::{query_supply, query_vault_config};
use dexter::vault::{SwapType, FEE_PRECISION};
const CONTRACT_NAME: &str = "dexter-stable-pool";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
const CONTRACT_VERSION_V1: &str = "1.0.0";
const MAX_ASSETS: usize = 5;
const MIN_ASSETS: usize = 2;
type ContractResult<T> = Result<T, ContractError>;
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
mut deps: DepsMut,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
if msg.asset_infos.len() > MAX_ASSETS || msg.asset_infos.len() < MIN_ASSETS {
return Err(ContractError::InvalidNumberOfAssets {});
}
let params: StablePoolParams = from_json(&msg.init_params.unwrap())?;
if params.amp == 0 || params.amp > MAX_AMP {
return Err(ContractError::IncorrectAmp {});
}
let scaling_factor_asset_infos = params
.scaling_factors
.iter()
.map(|a| a.asset_info.clone())
.sorted()
.collect_vec();
if scaling_factor_asset_infos.len() != scaling_factor_asset_infos.iter().unique().count() {
return Err(ContractError::DuplicateAssetInfoInScalingFactors);
}
let pool_assets = msg.asset_infos.iter().sorted().cloned().collect_vec();
for asset_info in scaling_factor_asset_infos {
if !pool_assets.contains(&asset_info) {
return Err(ContractError::InvalidScalingFactorAssetInfo);
}
}
for asset_scaling_factor in ¶ms.scaling_factors {
if asset_scaling_factor.scaling_factor == Decimal256::zero() {
return Err(ContractError::InvalidScalingFactor);
}
}
if params.scaling_factor_manager.is_none() && params.supports_scaling_factors_update {
return Err(ContractError::ScalingFactorManagerNotSpecified);
}
if params.scaling_factor_manager.is_some() {
if !params.supports_scaling_factors_update {
return Err(ContractError::ScalingFactorManagerSpecified);
}
deps.api
.addr_validate(¶ms.scaling_factor_manager.clone().unwrap().to_string())?;
}
let greatest_precision = store_precisions(
deps.branch(),
&msg.native_asset_precisions,
&msg.asset_infos,
PRECISIONS,
)?;
if greatest_precision > (Decimal::DECIMAL_PLACES as u8) {
return Err(ContractError::InvalidGreatestPrecision);
}
let mut cumulative_prices = vec![];
for from_asset in &msg.asset_infos {
for to_asset in &msg.asset_infos {
if from_asset.as_string() != to_asset.as_string() {
cumulative_prices.push((from_asset.clone(), to_asset.clone(), Uint128::zero()))
}
}
}
let assets = msg
.asset_infos
.iter()
.map(|a| Asset {
info: a.clone(),
amount: Uint128::zero(),
})
.collect();
let config = Config {
pool_id: msg.pool_id.clone(),
lp_token_addr: msg.lp_token_addr.clone(),
vault_addr: msg.vault_addr.clone(),
assets,
pool_type: msg.pool_type.clone(),
fee_info: msg.fee_info.clone(),
block_time_last: env.block.time.seconds(),
};
let twap = Twap {
cumulative_prices,
block_time_last: 0,
};
let math_config = MathConfig {
init_amp: params.amp * AMP_PRECISION,
init_amp_time: env.block.time.seconds(),
next_amp: params.amp * AMP_PRECISION,
next_amp_time: env.block.time.seconds(),
greatest_precision,
};
let stableswap_config = StableSwapConfig {
scaling_factor_manager: params.scaling_factor_manager.clone(),
supports_scaling_factors_update: params.supports_scaling_factors_update,
scaling_factors: params.scaling_factors.clone()
};
CONFIG.save(deps.storage, &config)?;
MATHCONFIG.save(deps.storage, &math_config)?;
TWAPINFO.save(deps.storage, &twap)?;
STABLESWAP_CONFIG.save(deps.storage, &stableswap_config)?;
let event = Event::from_info(concatcp!(CONTRACT_NAME, "::instantiate"), &info)
.add_attribute("pool_id", msg.pool_id)
.add_attribute("vault_addr", msg.vault_addr)
.add_attribute("lp_token_addr", msg.lp_token_addr.to_string())
.add_attribute(
"asset_infos",
serde_json_wasm::to_string(&msg.asset_infos).unwrap(),
)
.add_attribute(
"native_asset_precisions",
serde_json_wasm::to_string(&msg.native_asset_precisions).unwrap(),
)
.add_attribute("fee_info", msg.fee_info.to_string())
.add_attribute("amp", params.amp.to_string())
.add_attribute(
"supports_scaling_factors_update",
params.supports_scaling_factors_update.to_string(),
)
.add_attribute(
"scaling_factors",
serde_json_wasm::to_string(¶ms.scaling_factors).unwrap(),
);
let event = if params.scaling_factor_manager.is_some() {
event.add_attribute(
"scaling_factor_manager",
params.scaling_factor_manager.unwrap().to_string(),
)
} else {
event
};
let response = Response::new().add_event(event);
Ok(response)
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::UpdateConfig { params } => update_config(deps, env, info, params),
ExecuteMsg::UpdateFee { total_fee_bps } => {
update_fee(deps, env, info, total_fee_bps, CONFIG, CONTRACT_NAME).map_err(|e| e.into())
}
ExecuteMsg::UpdateLiquidity { assets } => {
execute_update_liquidity(deps, env, info, assets)
}
}
}
fn update_scaling_factor(
deps: DepsMut,
info: MessageInfo,
asset_info: AssetInfo,
scaling_factor: Decimal256,
) -> Result<Response, ContractError> {
let config = CONFIG.load(deps.storage)?;
let mut stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
if !stableswap_config.supports_scaling_factors_update {
return Err(ContractError::ScalingFactorUpdateNotSupported);
}
if let Some(scaling_factor_manager) = &stableswap_config.scaling_factor_manager {
if &info.sender != scaling_factor_manager {
return Err(ContractError::Unauthorized {});
}
} else {
return Err(ContractError::Unauthorized {});
}
let mut scaling_factors = stableswap_config.scaling_factors;
let asset_found = config
.assets
.iter()
.find(|a| a.info.as_string() == asset_info.as_string());
if asset_found.is_none() {
return Err(ContractError::UnsupportedAsset);
}
if scaling_factor == Decimal256::zero() {
return Err(ContractError::InvalidScalingFactor);
}
let asset_index = scaling_factors
.iter()
.position(|a| a.asset_info == asset_info);
if let Some(asset_index) = asset_index {
scaling_factors[asset_index].scaling_factor = scaling_factor;
} else {
scaling_factors.push(AssetScalingFactor {
asset_info: asset_info.clone(),
scaling_factor,
});
}
stableswap_config.scaling_factors = scaling_factors;
STABLESWAP_CONFIG.save(deps.storage, &stableswap_config)?;
Ok(Response::new().add_event(
Event::from_info(
concatcp!(CONTRACT_NAME, "::update_config::update_scaling_factor"),
&info,
)
.add_attribute(
"asset_info",
serde_json_wasm::to_string(&asset_info).unwrap(),
)
.add_attribute("scaling_factor", scaling_factor.to_string()),
))
}
fn update_scaling_factor_manager(
deps: DepsMut,
info: MessageInfo,
scaling_factor_manager: Addr,
) -> Result<Response, ContractError> {
let config: Config = CONFIG.load(deps.storage)?;
let vault_config = query_vault_config(&deps.querier, config.vault_addr.clone().to_string())?;
let mut stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
deps.api.addr_validate(scaling_factor_manager.as_str())?;
if info.sender != vault_config.owner && info.sender != config.vault_addr {
return Err(ContractError::Unauthorized {});
}
if !stableswap_config.supports_scaling_factors_update {
return Err(ContractError::ScalingFactorUpdateNotSupported);
}
stableswap_config.scaling_factor_manager = Some(scaling_factor_manager.clone());
STABLESWAP_CONFIG.save(deps.storage, &stableswap_config)?;
Ok(Response::new().add_event(
Event::from_info(
concatcp!(
CONTRACT_NAME,
"::update_config::update_scaling_factor_manager"
),
&info,
)
.add_attribute("scaling_factor_manager", scaling_factor_manager.to_string()),
))
}
pub fn execute_update_liquidity(
deps: DepsMut,
env: Env,
info: MessageInfo,
assets: Vec<Asset>,
) -> Result<Response, ContractError> {
let mut config: Config = CONFIG.load(deps.storage)?;
let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
let mut twap: Twap = TWAPINFO.load(deps.storage)?;
if info.sender != config.vault_addr {
return Err(ContractError::Unauthorized {});
}
let decimal_assets: Vec<DecimalAsset> =
transform_to_scaled_decimal_asset(deps.as_ref(), config.assets.clone())?;
let res = accumulate_prices(
deps.as_ref(),
env.clone(),
math_config,
&mut twap,
&decimal_assets,
);
if res.is_ok()
{
TWAPINFO.save(deps.storage, &twap)?;
}
config.assets = assets;
config.block_time_last = env.block.time.seconds();
CONFIG.save(deps.storage, &config)?;
Ok(Response::new().add_event(
Event::from_info(concatcp!(CONTRACT_NAME, "::update_liquidity"), &info)
.add_attribute(
"assets",
serde_json_wasm::to_string(&config.assets).unwrap(),
)
.add_attribute("pool_id", config.pool_id.to_string())
.add_attribute("twap_block_time_last", twap.block_time_last.to_string()),
))
}
pub fn update_config(
deps: DepsMut,
env: Env,
info: MessageInfo,
params: Binary,
) -> Result<Response, ContractError> {
match from_json::<StablePoolUpdateParams>(¶ms)? {
StablePoolUpdateParams::StartChangingAmp {
next_amp,
next_amp_time,
} => start_changing_amp(deps, env, info, next_amp, next_amp_time),
StablePoolUpdateParams::StopChangingAmp {} => stop_changing_amp(deps, env, info),
StablePoolUpdateParams::UpdateScalingFactor {
asset_info,
scaling_factor,
} => update_scaling_factor(deps, info, asset_info, scaling_factor),
StablePoolUpdateParams::UpdateScalingFactorManager { manager } => {
update_scaling_factor_manager(deps, info, manager)
}
}
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::Config {} => to_json_binary(&query_config(deps, env)?),
QueryMsg::FeeParams {} => to_json_binary(&query_fee_params(deps)?),
QueryMsg::PoolId {} => to_json_binary(&query_pool_id(deps)?),
QueryMsg::OnJoinPool {
assets_in,
mint_amount,
} => to_json_binary(&query_on_join_pool(deps, env, assets_in, mint_amount)?),
QueryMsg::OnExitPool { exit_type } => to_json_binary(&query_on_exit_pool(deps, env, exit_type)?),
QueryMsg::OnSwap {
swap_type,
offer_asset,
ask_asset,
amount,
} => to_json_binary(&query_on_swap(
deps,
env,
swap_type,
offer_asset,
ask_asset,
amount,
)?),
QueryMsg::CumulativePrice {
offer_asset,
ask_asset,
} => to_json_binary(&query_cumulative_price(deps, env, offer_asset, ask_asset)?),
QueryMsg::SpotPrice { offer_asset, ask_asset } => {
to_json_binary(&query_spot_price(deps, env, offer_asset, ask_asset)?)
}
QueryMsg::CumulativePrices {} => to_json_binary(&query_cumulative_prices(deps, env)?),
}
}
pub fn query_config(deps: Deps, env: Env) -> StdResult<ConfigResponse> {
let config: Config = CONFIG.load(deps.storage)?;
let stable_swap_config: StableSwapConfig = STABLESWAP_CONFIG.load(deps.storage)?;
let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
let cur_amp = compute_current_amp(&math_config, &env)?;
Ok(ConfigResponse {
pool_id: config.pool_id,
lp_token_addr: config.lp_token_addr,
vault_addr: config.vault_addr,
assets: config.assets,
pool_type: config.pool_type,
fee_info: config.fee_info,
block_time_last: config.block_time_last,
math_params: Some(to_json_binary(&math_config).unwrap()),
additional_params: Some(
to_json_binary(&StablePoolParams {
amp: cur_amp.checked_div(AMP_PRECISION).unwrap(),
scaling_factors: stable_swap_config.scaling_factors,
scaling_factor_manager: stable_swap_config.scaling_factor_manager,
supports_scaling_factors_update: stable_swap_config.supports_scaling_factors_update,
})
.unwrap(),
),
})
}
pub fn query_fee_params(deps: Deps) -> StdResult<FeeResponse> {
let config: Config = CONFIG.load(deps.storage)?;
Ok(FeeResponse {
total_fee_bps: config.fee_info.total_fee_bps,
})
}
pub fn query_pool_id(deps: Deps) -> StdResult<Uint128> {
let config: Config = CONFIG.load(deps.storage)?;
Ok(config.pool_id)
}
pub fn query_on_join_pool(
deps: Deps,
env: Env,
assets_in: Option<Vec<Asset>>,
_mint_amount: Option<Uint128>,
) -> StdResult<AfterJoinResponse> {
if assets_in.is_none() {
return Ok(return_join_failure("No assets provided".to_string()));
}
let config: Config = CONFIG.load(deps.storage)?;
let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
let stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
let mut act_assets_in = assets_in.unwrap();
let token_pools: HashMap<_, _> = config
.assets
.clone()
.into_iter()
.map(|pool| (pool.info, pool.amount))
.collect();
let mut non_zero_flag = false;
let mut assets_collection = act_assets_in
.clone()
.into_iter()
.map(|asset| {
if !asset.amount.is_zero() {
non_zero_flag = true;
}
let token_pool = token_pools.get(&asset.info).copied().unwrap();
Ok((asset, token_pool))
})
.collect::<Result<Vec<_>, ContractError>>()
.unwrap();
if !non_zero_flag {
return Ok(return_join_failure(
"No non-zero assets provided".to_string(),
));
}
token_pools.iter().for_each(|(pool_info, pool_amount)| {
if !act_assets_in.iter().any(|asset| asset.info.eq(pool_info)) {
assets_collection.push((
Asset {
amount: Uint128::zero(),
info: pool_info.clone(),
},
*pool_amount,
));
act_assets_in.push(Asset {
amount: Uint128::zero(),
info: pool_info.clone(),
});
}
});
act_assets_in.sort_by_key(|a| a.info.clone());
let mut previous_asset: String = "".to_string();
for asset in act_assets_in.iter() {
if previous_asset == asset.info.as_string() {
return Ok(return_join_failure(
"Repeated assets in asset_in".to_string(),
));
}
previous_asset = asset.info.as_string();
}
for (deposit, pool) in assets_collection.iter_mut() {
if deposit.amount.is_zero() && pool.is_zero() {
return Ok(return_join_failure(
"Cannot deposit zero into an empty pool".to_string(),
));
}
}
let scaling_factors = stableswap_config.scaling_factors();
let assets_collection = assets_collection
.iter()
.cloned()
.map(|(asset, pool)| {
let coin_precision = get_precision(deps.storage, &asset.info)?;
let scaling_factor = scaling_factors
.get(&asset.info)
.cloned()
.unwrap_or(Decimal256::one());
Ok((
asset.to_scaled_decimal_asset(coin_precision, scaling_factor)?,
Decimal256::with_precision(pool, coin_precision)?
.with_scaling_factor(scaling_factor)?,
))
})
.collect::<StdResult<Vec<(DecimalAsset, Decimal256)>>>()?;
let amp = compute_current_amp(&math_config, &env).unwrap_or(0u64.into());
if amp == 0u64 {
return Ok(return_join_failure("Invalid amp value".to_string()));
}
let n_coins = config.assets.len() as u8;
let old_balances = assets_collection
.iter()
.map(|(_, pool)| *pool)
.collect_vec();
let init_d = compute_d(amp.into(), &old_balances)?;
let mut new_balances = assets_collection
.iter()
.map(|(deposit, pool)| Ok(pool + deposit.amount))
.collect::<StdResult<Vec<_>>>()?;
let deposit_d = compute_d(amp.into(), &new_balances)?;
let total_share = query_supply(&deps.querier, config.lp_token_addr)?;
let mut fee_tokens: Vec<Asset> = vec![];
let mint_amount = if total_share.is_zero() {
deposit_d
} else {
let fee_info = config.fee_info.clone();
let fee = Decimal::from_ratio(fee_info.total_fee_bps, FEE_PRECISION)
.checked_mul(Decimal::from_ratio(n_coins, 4 * (n_coins - 1)))?;
let fee = Decimal256::new(fee.atomics().into());
for i in 0..n_coins as usize {
let asset_info = assets_collection[i].0.info.clone();
let ideal_balance = deposit_d.checked_multiply_ratio(old_balances[i], init_d)?;
let difference = if ideal_balance > new_balances[i] {
ideal_balance - new_balances[i]
} else {
new_balances[i] - ideal_balance
};
let fee_charged = fee.checked_mul(difference)?;
let scaling_factor = scaling_factors
.get(&asset_info)
.cloned()
.unwrap_or(Decimal256::one());
fee_tokens.push(Asset {
amount: fee_charged
.without_scaling_factor(scaling_factor)?
.to_uint128_with_precision(get_precision(deps.storage, &asset_info)?)?,
info: asset_info.clone(),
});
new_balances[i] -= fee_charged;
}
let after_fee_d = compute_d(Uint64::from(amp), &new_balances)?;
let tokens_to_mint = Decimal256::with_precision(total_share, Decimal256::DECIMAL_PLACES)?
.checked_multiply_ratio(after_fee_d.saturating_sub(init_d), init_d)?;
tokens_to_mint
};
let mint_amount = mint_amount.to_uint128_with_precision(Decimal256::DECIMAL_PLACES)?;
if mint_amount.is_zero() {
return Ok(return_join_failure("Mint amount is zero".to_string()));
}
let res = AfterJoinResponse {
provided_assets: act_assets_in,
new_shares: mint_amount,
response: dexter::pool::ResponseType::Success {},
fee: Some(fee_tokens),
};
Ok(res)
}
pub fn query_on_exit_pool(
deps: Deps,
env: Env,
exit_type: ExitType,
) -> StdResult<AfterExitResponse> {
let config: Config = CONFIG.load(deps.storage)?;
let total_share = query_supply(&deps.querier, config.lp_token_addr.clone())?;
let act_burn_amount;
let mut fees: Vec<Asset> = vec![];
let mut refund_assets;
match exit_type {
ExitType::ExactLpBurn(burn_amount) => {
if burn_amount.is_zero() {
return Ok(return_exit_failure("Burn amount is zero".to_string()));
}
act_burn_amount = burn_amount;
refund_assets =
get_share_in_assets(config.assets.clone(), act_burn_amount, total_share);
}
ExitType::ExactAssetsOut(assets_out) => {
let mut assets_out_ = assets_out.clone();
assets_out_.sort_by_key(|asset| asset.info.clone());
let mut previous_asset: String = "".to_string();
for asset in assets_out_.iter() {
if previous_asset == asset.info.as_string() {
return Ok(return_exit_failure(
"Repeated assets in exact_assets_out".to_string(),
));
}
previous_asset = asset.info.as_string();
}
let imb_wd_res: ImbalancedWithdrawResponse = match imbalanced_withdraw(
deps,
&env,
&config,
&MATHCONFIG.load(deps.storage)?,
&assets_out.clone(),
total_share,
) {
Ok(res) => res,
Err(err) => {
return Ok(return_exit_failure(format!(
"Error during imbalanced_withdraw: {}",
err.to_string()
)));
}
};
act_burn_amount = imb_wd_res.burn_amount;
fees = imb_wd_res.fee;
refund_assets = assets_out;
}
}
if act_burn_amount.is_zero() {
return Ok(return_exit_failure("Burn amount is zero".to_string()));
}
config.assets.iter().for_each(|pool_info| {
if !refund_assets
.iter()
.any(|asset| asset.info.eq(&pool_info.info))
{
refund_assets.push(Asset {
amount: Uint128::zero(),
info: pool_info.info.clone(),
});
}
});
refund_assets.sort_by_key(|a| a.info.clone());
Ok(AfterExitResponse {
assets_out: refund_assets,
burn_shares: act_burn_amount,
response: dexter::pool::ResponseType::Success {},
fee: Some(fees),
})
}
pub fn query_on_swap(
deps: Deps,
env: Env,
swap_type: SwapType,
offer_asset_info: AssetInfo,
ask_asset_info: AssetInfo,
amount: Uint128,
) -> StdResult<SwapResponse> {
let config: Config = CONFIG.load(deps.storage)?;
let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
let stableswap_config: StableSwapConfig = STABLESWAP_CONFIG.load(deps.storage)?;
let scaling_factors = stableswap_config.scaling_factors().clone();
let pools = transform_to_scaled_decimal_asset(deps, config.assets)?;
let (offer_pool, ask_pool) =
match select_pools(&offer_asset_info.clone(), &ask_asset_info, &pools) {
Ok(res) => res,
Err(err) => {
return Ok(return_swap_failure(format!(
"Error during pool selection: {}",
err
)))
}
};
if offer_pool.amount.is_zero() || ask_pool.amount.is_zero() {
return Ok(return_swap_failure(
"Swap pool balances cannot be zero".to_string(),
));
}
let offer_precision = get_precision(deps.storage, &offer_pool.info)?;
let ask_precision = get_precision(deps.storage, &ask_pool.info)?;
let offer_asset: Asset;
let ask_asset: Asset;
let (calc_amount, spread_amount): (Uint128, Uint128);
let total_fee: Uint128;
let ask_asset_scaling_factor = scaling_factors
.get(&ask_asset_info)
.cloned()
.unwrap_or(Decimal256::one());
let offer_asset_scaling_factor = scaling_factors
.get(&offer_asset_info)
.cloned()
.unwrap_or(Decimal256::one());
match swap_type {
SwapType::GiveIn {} => {
offer_asset = Asset {
info: offer_asset_info.clone(),
amount,
};
total_fee = calculate_underlying_fees(amount, config.fee_info.total_fee_bps);
let offer_amount_after_fee = amount.checked_sub(total_fee)?;
let offer_asset_after_fee = Asset {
info: offer_asset_info.clone(),
amount: offer_amount_after_fee,
}
.to_scaled_decimal_asset(offer_precision, offer_asset_scaling_factor)?;
(calc_amount, spread_amount) = match compute_swap(
deps.storage,
&env,
&math_config,
&offer_asset_after_fee,
&offer_pool,
&ask_pool,
&pools,
ask_asset_scaling_factor,
) {
Ok(res) => res,
Err(err) => {
return Ok(return_swap_failure(format!(
"Error during swap calculation: {}",
err
)))
}
};
ask_asset = Asset {
info: ask_asset_info.clone(),
amount: calc_amount,
};
}
SwapType::GiveOut {} => {
ask_asset = Asset {
info: ask_asset_info.clone(),
amount,
};
let ask_asset_scaled = Asset {
info: ask_asset_info.clone(),
amount,
}
.to_scaled_decimal_asset(ask_precision, ask_asset_scaling_factor)?;
(calc_amount, spread_amount, total_fee) = match compute_offer_amount(
deps.storage,
&env,
&math_config,
&ask_asset_scaled,
&offer_pool,
&ask_pool,
&pools,
config.fee_info.total_fee_bps,
ask_asset_scaling_factor,
offer_asset_scaling_factor,
) {
Ok(res) => res,
Err(err) => {
return Ok(return_swap_failure(format!(
"Error during offer amount calculation: {}",
err
)))
}
};
offer_asset = Asset {
info: offer_asset_info.clone(),
amount: calc_amount,
};
}
SwapType::Custom(_) => {
return Ok(return_swap_failure("SwapType not supported".to_string()))
}
}
if calc_amount.is_zero() {
return Ok(return_swap_failure(
"Computation error - calc_amount is zero".to_string(),
));
}
Ok(SwapResponse {
trade_params: Trade {
amount_in: offer_asset.amount,
amount_out: ask_asset.amount,
spread: spread_amount,
},
response: ResponseType::Success {},
fee: Some(Asset {
info: offer_asset_info.clone(),
amount: total_fee,
}),
})
}
pub fn query_cumulative_price(
deps: Deps,
env: Env,
offer_asset_info: AssetInfo,
ask_asset_info: AssetInfo,
) -> StdResult<CumulativePriceResponse> {
let mut twap: Twap = TWAPINFO.load(deps.storage)?;
let config: Config = CONFIG.load(deps.storage)?;
let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
let total_share = query_supply(&deps.querier, config.lp_token_addr)?;
let decimal_assets: Vec<DecimalAsset> = transform_to_scaled_decimal_asset(deps, config.assets)?;
accumulate_prices(deps, env, math_config, &mut twap, &decimal_assets)
.map_err(|err| StdError::generic_err(format!("{err}")))?;
let res_exchange_rate = twap
.cumulative_prices
.into_iter()
.find_position(|(offer_asset, ask_asset, _)| {
offer_asset.eq(&offer_asset_info) && ask_asset.eq(&ask_asset_info)
})
.unwrap();
let resp = CumulativePriceResponse {
exchange_info: AssetExchangeRate {
offer_info: res_exchange_rate.1 .0.clone(),
ask_info: res_exchange_rate.1 .1.clone(),
rate: res_exchange_rate.1 .2.clone(),
},
total_share,
};
Ok(resp)
}
pub fn query_spot_price(
deps: Deps,
env: Env,
offer_asset_info: AssetInfo,
ask_asset_info: AssetInfo,
) -> StdResult<SpotPrice> {
let config: Config = CONFIG.load(deps.storage)?;
let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
let stableswap_config: StableSwapConfig = STABLESWAP_CONFIG.load(deps.storage)?;
let decimal_assets: Vec<DecimalAsset> = transform_to_scaled_decimal_asset(deps, config.assets)?;
let fee = config.fee_info;
let amp = compute_current_amp(&math_config, &env)?;
let offer_asset_scaling_factor = stableswap_config.get_scaling_factor_for(&offer_asset_info)
.unwrap_or(Decimal256::one());
let ask_asset_scaling_factor = stableswap_config.get_scaling_factor_for(&ask_asset_info)
.unwrap_or(Decimal256::one());
let spot_price = calc_spot_price(
&offer_asset_info,
&ask_asset_info,
&offer_asset_scaling_factor,
&ask_asset_scaling_factor,
&decimal_assets,
fee,
amp,
)?;
Ok(spot_price)
}
pub fn query_cumulative_prices(deps: Deps, env: Env) -> StdResult<CumulativePricesResponse> {
let mut twap: Twap = TWAPINFO.load(deps.storage)?;
let config: Config = CONFIG.load(deps.storage)?;
let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
let total_share = query_supply(&deps.querier, config.lp_token_addr)?;
let decimal_assets: Vec<DecimalAsset> = transform_to_scaled_decimal_asset(deps, config.assets)?;
accumulate_prices(deps, env, math_config, &mut twap, &decimal_assets)
.map_err(|err| StdError::generic_err(format!("{err}")))?;
let mut asset_exchange_rates: Vec<AssetExchangeRate> = Vec::new();
for (offer_asset, ask_asset, rate) in twap.cumulative_prices.clone() {
asset_exchange_rates.push(AssetExchangeRate {
offer_info: offer_asset.clone(),
ask_info: ask_asset.clone(),
rate: rate.clone(),
});
}
Ok(CumulativePricesResponse {
exchange_infos: asset_exchange_rates,
total_share,
})
}
fn start_changing_amp(
deps: DepsMut,
env: Env,
info: MessageInfo,
next_amp: u64,
next_amp_time: u64,
) -> Result<Response, ContractError> {
let mut math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
let config: Config = CONFIG.load(deps.storage)?;
let vault_config = query_vault_config(&deps.querier, config.vault_addr.clone().to_string())?;
if info.sender != vault_config.owner && info.sender != config.vault_addr {
return Err(ContractError::Unauthorized {});
}
if next_amp == 0 || next_amp > MAX_AMP {
return Err(ContractError::IncorrectAmp {});
}
let current_amp = compute_current_amp(&math_config, &env)?;
let next_amp_with_precision = next_amp * AMP_PRECISION;
if next_amp_with_precision * MAX_AMP_CHANGE < current_amp
|| next_amp_with_precision > current_amp * MAX_AMP_CHANGE
{
return Err(ContractError::MaxAmpChangeAssertion {});
}
let block_time = env.block.time.seconds();
if block_time < math_config.init_amp_time + MIN_AMP_CHANGING_TIME
|| next_amp_time < block_time + MIN_AMP_CHANGING_TIME
{
return Err(ContractError::MinAmpChangingTimeAssertion {});
}
math_config.init_amp = current_amp;
math_config.next_amp = next_amp_with_precision;
math_config.init_amp_time = block_time;
math_config.next_amp_time = next_amp_time;
MATHCONFIG.save(deps.storage, &math_config)?;
Ok(Response::new().add_event(
Event::from_info(
concatcp!(CONTRACT_NAME, "::update_config::start_changing_amp"),
&info,
)
.add_attribute("next_amp", next_amp.to_string())
.add_attribute("next_amp_time", next_amp_time.to_string()),
))
}
fn stop_changing_amp(
deps: DepsMut,
env: Env,
info: MessageInfo,
) -> Result<Response, ContractError> {
let mut math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
let config: Config = CONFIG.load(deps.storage)?;
let vault_config = query_vault_config(&deps.querier, config.vault_addr.clone().to_string())?;
if info.sender != vault_config.owner && info.sender != config.vault_addr {
return Err(ContractError::Unauthorized {});
}
let current_amp = compute_current_amp(&math_config, &env)?;
let block_time = env.block.time.seconds();
math_config.init_amp = current_amp;
math_config.next_amp = current_amp;
math_config.init_amp_time = block_time;
math_config.next_amp_time = block_time;
MATHCONFIG.save(deps.storage, &math_config)?;
Ok(Response::new().add_event(Event::from_info(
concatcp!(CONTRACT_NAME, "::update_config::stop_changing_amp"),
&info,
)))
}
#[cw_serde]
pub struct ImbalancedWithdrawResponse {
pub burn_amount: Uint128,
pub fee: Vec<Asset>,
}
fn imbalanced_withdraw(
deps: Deps,
env: &Env,
config: &Config,
math_config: &MathConfig,
assets: &[Asset],
total_share: Uint128,
) -> Result<ImbalancedWithdrawResponse, ContractError> {
if assets.len() > config.assets.len() {
return Err(ContractError::InvalidNumberOfAssets {});
}
let stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
let pools: HashMap<_, _> = config
.assets
.clone()
.into_iter()
.map(|pool| (pool.info, pool.amount))
.collect();
let scaling_factors = stableswap_config.scaling_factors();
let mut assets_collection = assets
.iter()
.cloned()
.map(|asset| {
let precision = get_precision(deps.storage, &asset.info)?;
let pool = pools
.get(&asset.info)
.copied()
.ok_or_else(|| ContractError::InvalidAsset(asset.info.to_string()))?;
let scaling_factor = scaling_factors
.get(&asset.info)
.cloned()
.unwrap_or(Decimal256::one());
Ok((
asset.to_scaled_decimal_asset(precision, scaling_factor)?,
Decimal256::with_precision(pool, precision)?.with_scaling_factor(scaling_factor)?,
))
})
.collect::<Result<Vec<_>, ContractError>>()?;
pools
.into_iter()
.try_for_each(|(pool_info, pool_amount)| -> StdResult<()> {
if !assets.iter().any(|asset| asset.info == pool_info) {
let precision = get_precision(deps.storage, &pool_info)?;
let scaling_factor = scaling_factors
.get(&pool_info)
.cloned()
.unwrap_or(Decimal256::one());
assets_collection.push((
DecimalAsset {
amount: Decimal256::zero(),
info: pool_info,
},
Decimal256::with_precision(pool_amount, precision)?
.with_scaling_factor(scaling_factor)?,
));
}
Ok(())
})?;
let n_coins = config.assets.len() as u8;
let amp = Uint64::from(compute_current_amp(math_config, env)?);
let old_balances = assets_collection
.iter()
.map(|(_, pool)| *pool)
.collect_vec();
let init_d = compute_d(amp, &old_balances)?;
let mut new_balances = assets_collection
.iter()
.map(|(withdraw, pool)| Ok(pool.checked_sub(withdraw.amount)?))
.collect::<StdResult<Vec<_>>>()?;
let withdraw_d = compute_d(amp, &new_balances)?;
let fee = Decimal::from_ratio(config.fee_info.total_fee_bps, FEE_PRECISION)
.checked_mul(Decimal::from_ratio(n_coins, 4 * (n_coins - 1)))?;
let fee = Decimal256::new(fee.atomics().into());
let mut fee_tokens: Vec<Asset> = vec![];
for i in 0..n_coins as usize {
let asset_info = assets_collection[i].0.info.clone();
let ideal_balance = withdraw_d.checked_multiply_ratio(old_balances[i], init_d)?;
let difference = if ideal_balance > new_balances[i] {
ideal_balance - new_balances[i]
} else {
new_balances[i] - ideal_balance
};
let fee_charged = fee.checked_mul(difference)?;
new_balances[i] -= fee_charged;
let scaling_factor = scaling_factors
.get(&asset_info)
.cloned()
.unwrap_or(Decimal256::one());
fee_tokens.push(Asset {
amount: fee_charged
.without_scaling_factor(scaling_factor)?
.to_uint128_with_precision(get_precision(deps.storage, &asset_info)?)?,
info: asset_info,
});
}
let after_fee_d = compute_d(amp, &new_balances)?;
let total_share = Uint256::from(total_share);
let burn_amount = total_share
.checked_multiply_ratio(
init_d.atomics().checked_sub(after_fee_d.atomics())?,
init_d.atomics(),
)?
.checked_add(Uint256::from(1u8))?;
let burn_amount = burn_amount.try_into()?;
Ok(ImbalancedWithdrawResponse {
burn_amount,
fee: fee_tokens,
})
}
fn compute_current_amp(math_config: &MathConfig, env: &Env) -> StdResult<u64> {
let block_time = env.block.time.seconds();
if block_time < math_config.next_amp_time {
let init_amp = Uint128::from(math_config.init_amp);
let next_amp = Uint128::from(math_config.next_amp);
let elapsed_time =
Uint128::from(block_time).checked_sub(Uint128::from(math_config.init_amp_time))?;
let time_range = Uint128::from(math_config.next_amp_time)
.checked_sub(Uint128::from(math_config.init_amp_time))?;
if math_config.next_amp > math_config.init_amp {
let amp_range = next_amp - init_amp;
let res = init_amp + (amp_range * elapsed_time).checked_div(time_range)?;
Ok(res.u128() as u64)
} else {
let amp_range = init_amp - next_amp;
let res = init_amp - (amp_range * elapsed_time).checked_div(time_range)?;
Ok(res.u128() as u64)
}
} else {
Ok(math_config.next_amp)
}
}
pub fn transform_to_scaled_decimal_asset(
deps: Deps,
assets: Vec<Asset>,
) -> StdResult<Vec<DecimalAsset>> {
let stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
let scaling_factors = stableswap_config.scaling_factors();
assets
.iter()
.cloned()
.map(|asset| {
let precision = get_precision(deps.storage, &asset.info)?;
let scaling_factor = scaling_factors
.get(&asset.info)
.cloned()
.unwrap_or(Decimal256::one());
asset.to_scaled_decimal_asset(precision, scaling_factor)
})
.collect::<StdResult<Vec<DecimalAsset>>>()
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> ContractResult<Response> {
match msg {
MigrateMsg::V1_1 {} => {
let version = get_contract_version(deps.storage)?;
if version.version != CONTRACT_VERSION_V1 {
return Err(ContractError::InvalidContractVersion {
expected_version: CONTRACT_VERSION_V1.to_string(),
current_version: version.version,
});
}
if version.contract != CONTRACT_NAME {
return Err(ContractError::InvalidContractName {
expected_name: CONTRACT_NAME.to_string(),
contract_name: version.contract,
});
}
let stableswap_config: StableSwapConfigV1 = Item::new("stableswap_config").load(deps.storage)?;
let stableswap_config_new: StableSwapConfig = StableSwapConfig {
supports_scaling_factors_update: stableswap_config.supports_scaling_factors_update,
scaling_factors: stableswap_config.scaling_factors,
scaling_factor_manager: stableswap_config.scaling_factor_manager
};
STABLESWAP_CONFIG.save(deps.storage, &stableswap_config_new)?;
set_contract_version(deps.storage, CONTRACT_VERSION, CONTRACT_NAME)?;
Ok(Response::default())
}
}
}