1use const_format::concatcp;
2use cosmwasm_schema::cw_serde;
3use cosmwasm_std::{
4 entry_point, from_json, to_json_binary, Addr, Binary, Decimal, Decimal256, Deps, DepsMut, Env, Event, MessageInfo, Response, StdError, StdResult, Uint128, Uint256, Uint64
5};
6use cw2::{get_contract_version, set_contract_version};
7use cw_storage_plus::Item;
8use itertools::Itertools;
9use std::collections::HashMap;
10use std::vec;
11
12use crate::error::ContractError;
13use crate::math::{compute_d, calc_spot_price, AMP_PRECISION, MAX_AMP, MAX_AMP_CHANGE, MIN_AMP_CHANGING_TIME};
14use crate::state::{get_precision, AssetScalingFactor, MathConfig, StablePoolParams, StablePoolUpdateParams, StableSwapConfig, StableSwapConfigV1, Twap, CONFIG, MATHCONFIG, PRECISIONS, STABLESWAP_CONFIG, TWAPINFO};
15use crate::utils::{accumulate_prices, compute_offer_amount, compute_swap};
16use 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};
17
18use dexter::asset::{Asset, AssetExchangeRate, AssetInfo, Decimal256Ext, DecimalAsset};
19use dexter::helper::{calculate_underlying_fees, get_share_in_assets, select_pools, EventExt};
20use dexter::querier::{query_supply, query_vault_config};
21use dexter::vault::{SwapType, FEE_PRECISION};
22
23const CONTRACT_NAME: &str = "dexter-stable-pool";
25const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
27const CONTRACT_VERSION_V1: &str = "1.0.0";
28
29const MAX_ASSETS: usize = 5;
31const MIN_ASSETS: usize = 2;
33
34type ContractResult<T> = Result<T, ContractError>;
35
36#[cfg_attr(not(feature = "library"), entry_point)]
47pub fn instantiate(
48 mut deps: DepsMut,
49 env: Env,
50 info: MessageInfo,
51 msg: InstantiateMsg,
52) -> Result<Response, ContractError> {
53 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
54
55 if msg.asset_infos.len() > MAX_ASSETS || msg.asset_infos.len() < MIN_ASSETS {
57 return Err(ContractError::InvalidNumberOfAssets {});
58 }
59
60 let params: StablePoolParams = from_json(&msg.init_params.unwrap())?;
62 if params.amp == 0 || params.amp > MAX_AMP {
63 return Err(ContractError::IncorrectAmp {});
64 }
65
66 let scaling_factor_asset_infos = params
68 .scaling_factors
69 .iter()
70 .map(|a| a.asset_info.clone())
71 .sorted()
72 .collect_vec();
73
74 if scaling_factor_asset_infos.len() != scaling_factor_asset_infos.iter().unique().count() {
76 return Err(ContractError::DuplicateAssetInfoInScalingFactors);
77 }
78
79 let pool_assets = msg.asset_infos.iter().sorted().cloned().collect_vec();
80
81 for asset_info in scaling_factor_asset_infos {
84 if !pool_assets.contains(&asset_info) {
85 return Err(ContractError::InvalidScalingFactorAssetInfo);
86 }
87 }
88
89 for asset_scaling_factor in ¶ms.scaling_factors {
91 if asset_scaling_factor.scaling_factor == Decimal256::zero() {
92 return Err(ContractError::InvalidScalingFactor);
93 }
94 }
95
96 if params.scaling_factor_manager.is_none() && params.supports_scaling_factors_update {
98 return Err(ContractError::ScalingFactorManagerNotSpecified);
99 }
100
101 if params.scaling_factor_manager.is_some() {
102 if !params.supports_scaling_factors_update {
103 return Err(ContractError::ScalingFactorManagerSpecified);
104 }
105 deps.api
107 .addr_validate(¶ms.scaling_factor_manager.clone().unwrap().to_string())?;
108 }
109
110 let greatest_precision = store_precisions(
112 deps.branch(),
113 &msg.native_asset_precisions,
114 &msg.asset_infos,
115 PRECISIONS,
116 )?;
117 if greatest_precision > (Decimal::DECIMAL_PLACES as u8) {
119 return Err(ContractError::InvalidGreatestPrecision);
120 }
121
122 let mut cumulative_prices = vec![];
124 for from_asset in &msg.asset_infos {
125 for to_asset in &msg.asset_infos {
126 if from_asset.as_string() != to_asset.as_string() {
127 cumulative_prices.push((from_asset.clone(), to_asset.clone(), Uint128::zero()))
128 }
129 }
130 }
131
132 let assets = msg
134 .asset_infos
135 .iter()
136 .map(|a| Asset {
137 info: a.clone(),
138 amount: Uint128::zero(),
139 })
140 .collect();
141
142 let config = Config {
143 pool_id: msg.pool_id.clone(),
144 lp_token_addr: msg.lp_token_addr.clone(),
145 vault_addr: msg.vault_addr.clone(),
146 assets,
147 pool_type: msg.pool_type.clone(),
148 fee_info: msg.fee_info.clone(),
149 block_time_last: env.block.time.seconds(),
150 };
151
152 let twap = Twap {
153 cumulative_prices,
154 block_time_last: 0,
155 };
156
157 let math_config = MathConfig {
158 init_amp: params.amp * AMP_PRECISION,
159 init_amp_time: env.block.time.seconds(),
160 next_amp: params.amp * AMP_PRECISION,
161 next_amp_time: env.block.time.seconds(),
162 greatest_precision,
163 };
164
165 let stableswap_config = StableSwapConfig {
166 scaling_factor_manager: params.scaling_factor_manager.clone(),
167 supports_scaling_factors_update: params.supports_scaling_factors_update,
168 scaling_factors: params.scaling_factors.clone()
169 };
170
171 CONFIG.save(deps.storage, &config)?;
173 MATHCONFIG.save(deps.storage, &math_config)?;
174 TWAPINFO.save(deps.storage, &twap)?;
175 STABLESWAP_CONFIG.save(deps.storage, &stableswap_config)?;
176
177 let event = Event::from_info(concatcp!(CONTRACT_NAME, "::instantiate"), &info)
178 .add_attribute("pool_id", msg.pool_id)
179 .add_attribute("vault_addr", msg.vault_addr)
180 .add_attribute("lp_token_addr", msg.lp_token_addr.to_string())
181 .add_attribute(
182 "asset_infos",
183 serde_json_wasm::to_string(&msg.asset_infos).unwrap(),
184 )
185 .add_attribute(
186 "native_asset_precisions",
187 serde_json_wasm::to_string(&msg.native_asset_precisions).unwrap(),
188 )
189 .add_attribute("fee_info", msg.fee_info.to_string())
190 .add_attribute("amp", params.amp.to_string())
191 .add_attribute(
192 "supports_scaling_factors_update",
193 params.supports_scaling_factors_update.to_string(),
194 )
195 .add_attribute(
196 "scaling_factors",
197 serde_json_wasm::to_string(¶ms.scaling_factors).unwrap(),
198 );
199
200 let event = if params.scaling_factor_manager.is_some() {
202 event.add_attribute(
203 "scaling_factor_manager",
204 params.scaling_factor_manager.unwrap().to_string(),
205 )
206 } else {
207 event
208 };
209
210 let response = Response::new().add_event(event);
211
212 Ok(response)
213}
214
215#[cfg_attr(not(feature = "library"), entry_point)]
228pub fn execute(
229 deps: DepsMut,
230 env: Env,
231 info: MessageInfo,
232 msg: ExecuteMsg,
233) -> Result<Response, ContractError> {
234 match msg {
235 ExecuteMsg::UpdateConfig { params } => update_config(deps, env, info, params),
236 ExecuteMsg::UpdateFee { total_fee_bps } => {
237 update_fee(deps, env, info, total_fee_bps, CONFIG, CONTRACT_NAME).map_err(|e| e.into())
238 }
239 ExecuteMsg::UpdateLiquidity { assets } => {
240 execute_update_liquidity(deps, env, info, assets)
241 }
242 }
243}
244
245fn update_scaling_factor(
248 deps: DepsMut,
249 info: MessageInfo,
250 asset_info: AssetInfo,
251 scaling_factor: Decimal256,
252) -> Result<Response, ContractError> {
253 let config = CONFIG.load(deps.storage)?;
254 let mut stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
256
257 if !stableswap_config.supports_scaling_factors_update {
258 return Err(ContractError::ScalingFactorUpdateNotSupported);
259 }
260
261 if let Some(scaling_factor_manager) = &stableswap_config.scaling_factor_manager {
262 if &info.sender != scaling_factor_manager {
263 return Err(ContractError::Unauthorized {});
264 }
265 } else {
266 return Err(ContractError::Unauthorized {});
267 }
268
269 let mut scaling_factors = stableswap_config.scaling_factors;
270
271 let asset_found = config
272 .assets
273 .iter()
274 .find(|a| a.info.as_string() == asset_info.as_string());
275
276 if asset_found.is_none() {
277 return Err(ContractError::UnsupportedAsset);
278 }
279
280 if scaling_factor == Decimal256::zero() {
281 return Err(ContractError::InvalidScalingFactor);
282 }
283
284 let asset_index = scaling_factors
286 .iter()
287 .position(|a| a.asset_info == asset_info);
288
289 if let Some(asset_index) = asset_index {
290 scaling_factors[asset_index].scaling_factor = scaling_factor;
291 } else {
292 scaling_factors.push(AssetScalingFactor {
293 asset_info: asset_info.clone(),
294 scaling_factor,
295 });
296 }
297
298 stableswap_config.scaling_factors = scaling_factors;
300 STABLESWAP_CONFIG.save(deps.storage, &stableswap_config)?;
301
302 Ok(Response::new().add_event(
304 Event::from_info(
305 concatcp!(CONTRACT_NAME, "::update_config::update_scaling_factor"),
306 &info,
307 )
308 .add_attribute(
309 "asset_info",
310 serde_json_wasm::to_string(&asset_info).unwrap(),
311 )
312 .add_attribute("scaling_factor", scaling_factor.to_string()),
313 ))
314}
315
316fn update_scaling_factor_manager(
319 deps: DepsMut,
320 info: MessageInfo,
321 scaling_factor_manager: Addr,
322) -> Result<Response, ContractError> {
323 let config: Config = CONFIG.load(deps.storage)?;
324 let vault_config = query_vault_config(&deps.querier, config.vault_addr.clone().to_string())?;
325 let mut stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
326
327 deps.api.addr_validate(scaling_factor_manager.as_str())?;
329
330 if info.sender != vault_config.owner && info.sender != config.vault_addr {
332 return Err(ContractError::Unauthorized {});
333 }
334
335 if !stableswap_config.supports_scaling_factors_update {
336 return Err(ContractError::ScalingFactorUpdateNotSupported);
337 }
338
339 stableswap_config.scaling_factor_manager = Some(scaling_factor_manager.clone());
341 STABLESWAP_CONFIG.save(deps.storage, &stableswap_config)?;
342
343 Ok(Response::new().add_event(
345 Event::from_info(
346 concatcp!(
347 CONTRACT_NAME,
348 "::update_config::update_scaling_factor_manager"
349 ),
350 &info,
351 )
352 .add_attribute("scaling_factor_manager", scaling_factor_manager.to_string()),
353 ))
354}
355
356pub fn execute_update_liquidity(
363 deps: DepsMut,
364 env: Env,
365 info: MessageInfo,
366 assets: Vec<Asset>,
367) -> Result<Response, ContractError> {
368 let mut config: Config = CONFIG.load(deps.storage)?;
370 let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
371 let mut twap: Twap = TWAPINFO.load(deps.storage)?;
372
373 if info.sender != config.vault_addr {
375 return Err(ContractError::Unauthorized {});
376 }
377
378 let decimal_assets: Vec<DecimalAsset> =
380 transform_to_scaled_decimal_asset(deps.as_ref(), config.assets.clone())?;
381
382 let res = accumulate_prices(
384 deps.as_ref(),
385 env.clone(),
386 math_config,
387 &mut twap,
388 &decimal_assets,
389 );
390
391 if res.is_ok()
392 {
399 TWAPINFO.save(deps.storage, &twap)?;
400 }
401
402 config.assets = assets;
404
405 config.block_time_last = env.block.time.seconds();
406 CONFIG.save(deps.storage, &config)?;
407
408 Ok(Response::new().add_event(
409 Event::from_info(concatcp!(CONTRACT_NAME, "::update_liquidity"), &info)
410 .add_attribute(
411 "assets",
412 serde_json_wasm::to_string(&config.assets).unwrap(),
413 )
414 .add_attribute("pool_id", config.pool_id.to_string())
415 .add_attribute("twap_block_time_last", twap.block_time_last.to_string()),
416 ))
417}
418
419pub fn update_config(
430 deps: DepsMut,
431 env: Env,
432 info: MessageInfo,
433 params: Binary,
434) -> Result<Response, ContractError> {
435 match from_json::<StablePoolUpdateParams>(¶ms)? {
436 StablePoolUpdateParams::StartChangingAmp {
437 next_amp,
438 next_amp_time,
439 } => start_changing_amp(deps, env, info, next_amp, next_amp_time),
440 StablePoolUpdateParams::StopChangingAmp {} => stop_changing_amp(deps, env, info),
441 StablePoolUpdateParams::UpdateScalingFactor {
442 asset_info,
443 scaling_factor,
444 } => update_scaling_factor(deps, info, asset_info, scaling_factor),
445 StablePoolUpdateParams::UpdateScalingFactorManager { manager } => {
446 update_scaling_factor_manager(deps, info, manager)
447 }
448 }
449}
450#[cfg_attr(not(feature = "library"), entry_point)]
463pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
464 match msg {
465 QueryMsg::Config {} => to_json_binary(&query_config(deps, env)?),
466 QueryMsg::FeeParams {} => to_json_binary(&query_fee_params(deps)?),
467 QueryMsg::PoolId {} => to_json_binary(&query_pool_id(deps)?),
468 QueryMsg::OnJoinPool {
469 assets_in,
470 mint_amount,
471 } => to_json_binary(&query_on_join_pool(deps, env, assets_in, mint_amount)?),
472 QueryMsg::OnExitPool { exit_type } => to_json_binary(&query_on_exit_pool(deps, env, exit_type)?),
473 QueryMsg::OnSwap {
474 swap_type,
475 offer_asset,
476 ask_asset,
477 amount,
478 } => to_json_binary(&query_on_swap(
479 deps,
480 env,
481 swap_type,
482 offer_asset,
483 ask_asset,
484 amount,
485 )?),
486 QueryMsg::CumulativePrice {
487 offer_asset,
488 ask_asset,
489 } => to_json_binary(&query_cumulative_price(deps, env, offer_asset, ask_asset)?),
490 QueryMsg::SpotPrice { offer_asset, ask_asset } => {
491 to_json_binary(&query_spot_price(deps, env, offer_asset, ask_asset)?)
492 }
493 QueryMsg::CumulativePrices {} => to_json_binary(&query_cumulative_prices(deps, env)?),
494 }
495}
496
497pub fn query_config(deps: Deps, env: Env) -> StdResult<ConfigResponse> {
502 let config: Config = CONFIG.load(deps.storage)?;
503 let stable_swap_config: StableSwapConfig = STABLESWAP_CONFIG.load(deps.storage)?;
504 let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
505 let cur_amp = compute_current_amp(&math_config, &env)?;
506
507 Ok(ConfigResponse {
508 pool_id: config.pool_id,
509 lp_token_addr: config.lp_token_addr,
510 vault_addr: config.vault_addr,
511 assets: config.assets,
512 pool_type: config.pool_type,
513 fee_info: config.fee_info,
514 block_time_last: config.block_time_last,
515 math_params: Some(to_json_binary(&math_config).unwrap()),
516 additional_params: Some(
517 to_json_binary(&StablePoolParams {
518 amp: cur_amp.checked_div(AMP_PRECISION).unwrap(),
519 scaling_factors: stable_swap_config.scaling_factors,
520 scaling_factor_manager: stable_swap_config.scaling_factor_manager,
521 supports_scaling_factors_update: stable_swap_config.supports_scaling_factors_update,
522 })
523 .unwrap(),
524 ),
525 })
526}
527
528pub fn query_fee_params(deps: Deps) -> StdResult<FeeResponse> {
533 let config: Config = CONFIG.load(deps.storage)?;
534 Ok(FeeResponse {
535 total_fee_bps: config.fee_info.total_fee_bps,
536 })
537}
538
539pub fn query_pool_id(deps: Deps) -> StdResult<Uint128> {
544 let config: Config = CONFIG.load(deps.storage)?;
545 Ok(config.pool_id)
546}
547
548pub fn query_on_join_pool(
566 deps: Deps,
567 env: Env,
568 assets_in: Option<Vec<Asset>>,
569 _mint_amount: Option<Uint128>,
570) -> StdResult<AfterJoinResponse> {
571 if assets_in.is_none() {
573 return Ok(return_join_failure("No assets provided".to_string()));
574 }
575
576 let config: Config = CONFIG.load(deps.storage)?;
578 let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
579 let stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
580
581 let mut act_assets_in = assets_in.unwrap();
583
584 let token_pools: HashMap<_, _> = config
586 .assets
587 .clone()
588 .into_iter()
589 .map(|pool| (pool.info, pool.amount))
590 .collect();
591
592 let mut non_zero_flag = false;
593 let mut assets_collection = act_assets_in
595 .clone()
596 .into_iter()
597 .map(|asset| {
598 if !asset.amount.is_zero() {
600 non_zero_flag = true;
601 }
602
603 let token_pool = token_pools.get(&asset.info).copied().unwrap();
605 Ok((asset, token_pool))
606 })
607 .collect::<Result<Vec<_>, ContractError>>()
608 .unwrap();
609
610 if !non_zero_flag {
612 return Ok(return_join_failure(
613 "No non-zero assets provided".to_string(),
614 ));
615 }
616
617 token_pools.iter().for_each(|(pool_info, pool_amount)| {
619 if !act_assets_in.iter().any(|asset| asset.info.eq(pool_info)) {
620 assets_collection.push((
621 Asset {
622 amount: Uint128::zero(),
623 info: pool_info.clone(),
624 },
625 *pool_amount,
626 ));
627 act_assets_in.push(Asset {
628 amount: Uint128::zero(),
629 info: pool_info.clone(),
630 });
631 }
632 });
633
634 act_assets_in.sort_by_key(|a| a.info.clone());
636
637 let mut previous_asset: String = "".to_string();
639 for asset in act_assets_in.iter() {
640 if previous_asset == asset.info.as_string() {
641 return Ok(return_join_failure(
642 "Repeated assets in asset_in".to_string(),
643 ));
644 }
645 previous_asset = asset.info.as_string();
646 }
647
648 for (deposit, pool) in assets_collection.iter_mut() {
651 if deposit.amount.is_zero() && pool.is_zero() {
652 return Ok(return_join_failure(
653 "Cannot deposit zero into an empty pool".to_string(),
654 ));
655 }
656 }
657
658 let scaling_factors = stableswap_config.scaling_factors();
659
660 let assets_collection = assets_collection
662 .iter()
663 .cloned()
664 .map(|(asset, pool)| {
665 let coin_precision = get_precision(deps.storage, &asset.info)?;
666 let scaling_factor = scaling_factors
667 .get(&asset.info)
668 .cloned()
669 .unwrap_or(Decimal256::one());
670 Ok((
671 asset.to_scaled_decimal_asset(coin_precision, scaling_factor)?,
672 Decimal256::with_precision(pool, coin_precision)?
673 .with_scaling_factor(scaling_factor)?,
674 ))
675 })
676 .collect::<StdResult<Vec<(DecimalAsset, Decimal256)>>>()?;
677
678 let amp = compute_current_amp(&math_config, &env).unwrap_or(0u64.into());
680
681 if amp == 0u64 {
683 return Ok(return_join_failure("Invalid amp value".to_string()));
684 }
685
686 let n_coins = config.assets.len() as u8;
687
688 let old_balances = assets_collection
690 .iter()
691 .map(|(_, pool)| *pool)
692 .collect_vec();
693 let init_d = compute_d(amp.into(), &old_balances)?;
694
695 let mut new_balances = assets_collection
697 .iter()
698 .map(|(deposit, pool)| Ok(pool + deposit.amount))
699 .collect::<StdResult<Vec<_>>>()?;
700 let deposit_d = compute_d(amp.into(), &new_balances)?;
701
702 let total_share = query_supply(&deps.querier, config.lp_token_addr)?;
704
705 let mut fee_tokens: Vec<Asset> = vec![];
707
708 let mint_amount = if total_share.is_zero() {
710 deposit_d
711 } else {
712 let fee_info = config.fee_info.clone();
714
715 let fee = Decimal::from_ratio(fee_info.total_fee_bps, FEE_PRECISION)
726 .checked_mul(Decimal::from_ratio(n_coins, 4 * (n_coins - 1)))?;
727
728 let fee = Decimal256::new(fee.atomics().into());
729
730 for i in 0..n_coins as usize {
731 let asset_info = assets_collection[i].0.info.clone();
733 let ideal_balance = deposit_d.checked_multiply_ratio(old_balances[i], init_d)?;
734
735 let difference = if ideal_balance > new_balances[i] {
736 ideal_balance - new_balances[i]
737 } else {
738 new_balances[i] - ideal_balance
739 };
740
741 let fee_charged = fee.checked_mul(difference)?;
743 let scaling_factor = scaling_factors
744 .get(&asset_info)
745 .cloned()
746 .unwrap_or(Decimal256::one());
747 fee_tokens.push(Asset {
748 amount: fee_charged
749 .without_scaling_factor(scaling_factor)?
750 .to_uint128_with_precision(get_precision(deps.storage, &asset_info)?)?,
751 info: asset_info.clone(),
752 });
753 new_balances[i] -= fee_charged;
754 }
755
756 let after_fee_d = compute_d(Uint64::from(amp), &new_balances)?;
757
758 let tokens_to_mint = Decimal256::with_precision(total_share, Decimal256::DECIMAL_PLACES)?
759 .checked_multiply_ratio(after_fee_d.saturating_sub(init_d), init_d)?;
760 tokens_to_mint
761 };
762
763 let mint_amount = mint_amount.to_uint128_with_precision(Decimal256::DECIMAL_PLACES)?;
764
765 if mint_amount.is_zero() {
767 return Ok(return_join_failure("Mint amount is zero".to_string()));
768 }
769
770 let res = AfterJoinResponse {
771 provided_assets: act_assets_in,
772 new_shares: mint_amount,
773 response: dexter::pool::ResponseType::Success {},
774 fee: Some(fee_tokens),
775 };
776
777 Ok(res)
778}
779
780pub fn query_on_exit_pool(
792 deps: Deps,
793 env: Env,
794 exit_type: ExitType,
795) -> StdResult<AfterExitResponse> {
796 let config: Config = CONFIG.load(deps.storage)?;
797
798 let total_share = query_supply(&deps.querier, config.lp_token_addr.clone())?;
800
801 let act_burn_amount;
802 let mut fees: Vec<Asset> = vec![];
803 let mut refund_assets;
804
805 match exit_type {
806 ExitType::ExactLpBurn(burn_amount) => {
807 if burn_amount.is_zero() {
809 return Ok(return_exit_failure("Burn amount is zero".to_string()));
810 }
811
812 act_burn_amount = burn_amount;
814 refund_assets =
815 get_share_in_assets(config.assets.clone(), act_burn_amount, total_share);
816 }
817 ExitType::ExactAssetsOut(assets_out) => {
818 let mut assets_out_ = assets_out.clone();
820 assets_out_.sort_by_key(|asset| asset.info.clone());
822 let mut previous_asset: String = "".to_string();
823 for asset in assets_out_.iter() {
824 if previous_asset == asset.info.as_string() {
825 return Ok(return_exit_failure(
826 "Repeated assets in exact_assets_out".to_string(),
827 ));
828 }
829 previous_asset = asset.info.as_string();
830 }
831
832 let imb_wd_res: ImbalancedWithdrawResponse = match imbalanced_withdraw(
834 deps,
835 &env,
836 &config,
837 &MATHCONFIG.load(deps.storage)?,
838 &assets_out.clone(),
839 total_share,
840 ) {
841 Ok(res) => res,
842 Err(err) => {
843 return Ok(return_exit_failure(format!(
844 "Error during imbalanced_withdraw: {}",
845 err.to_string()
846 )));
847 }
848 };
849 act_burn_amount = imb_wd_res.burn_amount;
850 fees = imb_wd_res.fee;
851 refund_assets = assets_out;
852 }
853 }
854
855 if act_burn_amount.is_zero() {
857 return Ok(return_exit_failure("Burn amount is zero".to_string()));
858 }
859
860 config.assets.iter().for_each(|pool_info| {
862 if !refund_assets
863 .iter()
864 .any(|asset| asset.info.eq(&pool_info.info))
865 {
866 refund_assets.push(Asset {
867 amount: Uint128::zero(),
868 info: pool_info.info.clone(),
869 });
870 }
871 });
872
873 refund_assets.sort_by_key(|a| a.info.clone());
875
876 Ok(AfterExitResponse {
877 assets_out: refund_assets,
878 burn_shares: act_burn_amount,
879 response: dexter::pool::ResponseType::Success {},
880 fee: Some(fees),
881 })
882}
883
884pub fn query_on_swap(
895 deps: Deps,
896 env: Env,
897 swap_type: SwapType,
898 offer_asset_info: AssetInfo,
899 ask_asset_info: AssetInfo,
900 amount: Uint128,
901) -> StdResult<SwapResponse> {
902 let config: Config = CONFIG.load(deps.storage)?;
904 let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
905 let stableswap_config: StableSwapConfig = STABLESWAP_CONFIG.load(deps.storage)?;
906
907 let scaling_factors = stableswap_config.scaling_factors().clone();
908 let pools = transform_to_scaled_decimal_asset(deps, config.assets)?;
909
910 let (offer_pool, ask_pool) =
912 match select_pools(&offer_asset_info.clone(), &ask_asset_info, &pools) {
913 Ok(res) => res,
914 Err(err) => {
915 return Ok(return_swap_failure(format!(
916 "Error during pool selection: {}",
917 err
918 )))
919 }
920 };
921
922 if offer_pool.amount.is_zero() || ask_pool.amount.is_zero() {
924 return Ok(return_swap_failure(
925 "Swap pool balances cannot be zero".to_string(),
926 ));
927 }
928
929 let offer_precision = get_precision(deps.storage, &offer_pool.info)?;
931 let ask_precision = get_precision(deps.storage, &ask_pool.info)?;
932
933 let offer_asset: Asset;
934 let ask_asset: Asset;
935 let (calc_amount, spread_amount): (Uint128, Uint128);
936 let total_fee: Uint128;
937
938 let ask_asset_scaling_factor = scaling_factors
939 .get(&ask_asset_info)
940 .cloned()
941 .unwrap_or(Decimal256::one());
942 let offer_asset_scaling_factor = scaling_factors
943 .get(&offer_asset_info)
944 .cloned()
945 .unwrap_or(Decimal256::one());
946
947 match swap_type {
949 SwapType::GiveIn {} => {
950 offer_asset = Asset {
951 info: offer_asset_info.clone(),
952 amount,
953 };
954
955 total_fee = calculate_underlying_fees(amount, config.fee_info.total_fee_bps);
957 let offer_amount_after_fee = amount.checked_sub(total_fee)?;
958
959 let offer_asset_after_fee = Asset {
960 info: offer_asset_info.clone(),
961 amount: offer_amount_after_fee,
962 }
963 .to_scaled_decimal_asset(offer_precision, offer_asset_scaling_factor)?;
964
965 (calc_amount, spread_amount) = match compute_swap(
967 deps.storage,
968 &env,
969 &math_config,
970 &offer_asset_after_fee,
971 &offer_pool,
972 &ask_pool,
973 &pools,
974 ask_asset_scaling_factor,
975 ) {
976 Ok(res) => res,
977 Err(err) => {
978 return Ok(return_swap_failure(format!(
979 "Error during swap calculation: {}",
980 err
981 )))
982 }
983 };
984
985 ask_asset = Asset {
986 info: ask_asset_info.clone(),
987 amount: calc_amount,
988 };
989 }
990 SwapType::GiveOut {} => {
991 ask_asset = Asset {
992 info: ask_asset_info.clone(),
993 amount,
994 };
995
996 let ask_asset_scaled = Asset {
997 info: ask_asset_info.clone(),
998 amount,
999 }
1000 .to_scaled_decimal_asset(ask_precision, ask_asset_scaling_factor)?;
1001
1002 (calc_amount, spread_amount, total_fee) = match compute_offer_amount(
1004 deps.storage,
1005 &env,
1006 &math_config,
1007 &ask_asset_scaled,
1008 &offer_pool,
1009 &ask_pool,
1010 &pools,
1011 config.fee_info.total_fee_bps,
1012 ask_asset_scaling_factor,
1013 offer_asset_scaling_factor,
1014 ) {
1015 Ok(res) => res,
1016 Err(err) => {
1017 return Ok(return_swap_failure(format!(
1018 "Error during offer amount calculation: {}",
1019 err
1020 )))
1021 }
1022 };
1023
1024 offer_asset = Asset {
1025 info: offer_asset_info.clone(),
1026 amount: calc_amount,
1027 };
1028 }
1029 SwapType::Custom(_) => {
1030 return Ok(return_swap_failure("SwapType not supported".to_string()))
1031 }
1032 }
1033
1034 if calc_amount.is_zero() {
1037 return Ok(return_swap_failure(
1038 "Computation error - calc_amount is zero".to_string(),
1039 ));
1040 }
1041
1042 Ok(SwapResponse {
1043 trade_params: Trade {
1044 amount_in: offer_asset.amount,
1045 amount_out: ask_asset.amount,
1046 spread: spread_amount,
1047 },
1048 response: ResponseType::Success {},
1049 fee: Some(Asset {
1050 info: offer_asset_info.clone(),
1051 amount: total_fee,
1052 }),
1053 })
1054}
1055
1056pub fn query_cumulative_price(
1064 deps: Deps,
1065 env: Env,
1066 offer_asset_info: AssetInfo,
1067 ask_asset_info: AssetInfo,
1068) -> StdResult<CumulativePriceResponse> {
1069 let mut twap: Twap = TWAPINFO.load(deps.storage)?;
1071 let config: Config = CONFIG.load(deps.storage)?;
1072 let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
1073
1074 let total_share = query_supply(&deps.querier, config.lp_token_addr)?;
1075
1076 let decimal_assets: Vec<DecimalAsset> = transform_to_scaled_decimal_asset(deps, config.assets)?;
1078
1079 accumulate_prices(deps, env, math_config, &mut twap, &decimal_assets)
1081 .map_err(|err| StdError::generic_err(format!("{err}")))?;
1082
1083 let res_exchange_rate = twap
1085 .cumulative_prices
1086 .into_iter()
1087 .find_position(|(offer_asset, ask_asset, _)| {
1088 offer_asset.eq(&offer_asset_info) && ask_asset.eq(&ask_asset_info)
1089 })
1090 .unwrap();
1091
1092 let resp = CumulativePriceResponse {
1094 exchange_info: AssetExchangeRate {
1095 offer_info: res_exchange_rate.1 .0.clone(),
1096 ask_info: res_exchange_rate.1 .1.clone(),
1097 rate: res_exchange_rate.1 .2.clone(),
1098 },
1099 total_share,
1100 };
1101
1102 Ok(resp)
1103}
1104
1105pub fn query_spot_price(
1106 deps: Deps,
1107 env: Env,
1108 offer_asset_info: AssetInfo,
1109 ask_asset_info: AssetInfo,
1110) -> StdResult<SpotPrice> {
1111 let config: Config = CONFIG.load(deps.storage)?;
1112 let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
1113 let stableswap_config: StableSwapConfig = STABLESWAP_CONFIG.load(deps.storage)?;
1114
1115 let decimal_assets: Vec<DecimalAsset> = transform_to_scaled_decimal_asset(deps, config.assets)?;
1116 let fee = config.fee_info;
1117 let amp = compute_current_amp(&math_config, &env)?;
1118
1119 let offer_asset_scaling_factor = stableswap_config.get_scaling_factor_for(&offer_asset_info)
1120 .unwrap_or(Decimal256::one());
1121 let ask_asset_scaling_factor = stableswap_config.get_scaling_factor_for(&ask_asset_info)
1122 .unwrap_or(Decimal256::one());
1123
1124 let spot_price = calc_spot_price(
1125 &offer_asset_info,
1126 &ask_asset_info,
1127 &offer_asset_scaling_factor,
1128 &ask_asset_scaling_factor,
1129 &decimal_assets,
1130 fee,
1131 amp,
1132 )?;
1133
1134 Ok(spot_price)
1135}
1136
1137pub fn query_cumulative_prices(deps: Deps, env: Env) -> StdResult<CumulativePricesResponse> {
1143 let mut twap: Twap = TWAPINFO.load(deps.storage)?;
1144 let config: Config = CONFIG.load(deps.storage)?;
1145 let math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
1146
1147 let total_share = query_supply(&deps.querier, config.lp_token_addr)?;
1148
1149 let decimal_assets: Vec<DecimalAsset> = transform_to_scaled_decimal_asset(deps, config.assets)?;
1151
1152 accumulate_prices(deps, env, math_config, &mut twap, &decimal_assets)
1154 .map_err(|err| StdError::generic_err(format!("{err}")))?;
1155
1156 let mut asset_exchange_rates: Vec<AssetExchangeRate> = Vec::new();
1158 for (offer_asset, ask_asset, rate) in twap.cumulative_prices.clone() {
1159 asset_exchange_rates.push(AssetExchangeRate {
1160 offer_info: offer_asset.clone(),
1161 ask_info: ask_asset.clone(),
1162 rate: rate.clone(),
1163 });
1164 }
1165
1166 Ok(CumulativePricesResponse {
1167 exchange_infos: asset_exchange_rates,
1168 total_share,
1169 })
1170}
1171
1172fn start_changing_amp(
1184 deps: DepsMut,
1185 env: Env,
1186 info: MessageInfo,
1187 next_amp: u64,
1188 next_amp_time: u64,
1189) -> Result<Response, ContractError> {
1190 let mut math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
1192 let config: Config = CONFIG.load(deps.storage)?;
1193 let vault_config = query_vault_config(&deps.querier, config.vault_addr.clone().to_string())?;
1194
1195 if info.sender != vault_config.owner && info.sender != config.vault_addr {
1197 return Err(ContractError::Unauthorized {});
1198 }
1199
1200 if next_amp == 0 || next_amp > MAX_AMP {
1202 return Err(ContractError::IncorrectAmp {});
1203 }
1204
1205 let current_amp = compute_current_amp(&math_config, &env)?;
1207 let next_amp_with_precision = next_amp * AMP_PRECISION;
1208
1209 if next_amp_with_precision * MAX_AMP_CHANGE < current_amp
1211 || next_amp_with_precision > current_amp * MAX_AMP_CHANGE
1212 {
1213 return Err(ContractError::MaxAmpChangeAssertion {});
1214 }
1215
1216 let block_time = env.block.time.seconds();
1218 if block_time < math_config.init_amp_time + MIN_AMP_CHANGING_TIME
1219 || next_amp_time < block_time + MIN_AMP_CHANGING_TIME
1220 {
1221 return Err(ContractError::MinAmpChangingTimeAssertion {});
1222 }
1223
1224 math_config.init_amp = current_amp;
1226 math_config.next_amp = next_amp_with_precision;
1227 math_config.init_amp_time = block_time;
1228 math_config.next_amp_time = next_amp_time;
1229
1230 MATHCONFIG.save(deps.storage, &math_config)?;
1232
1233 Ok(Response::new().add_event(
1234 Event::from_info(
1235 concatcp!(CONTRACT_NAME, "::update_config::start_changing_amp"),
1236 &info,
1237 )
1238 .add_attribute("next_amp", next_amp.to_string())
1239 .add_attribute("next_amp_time", next_amp_time.to_string()),
1240 ))
1241}
1242
1243fn stop_changing_amp(
1248 deps: DepsMut,
1249 env: Env,
1250 info: MessageInfo,
1251) -> Result<Response, ContractError> {
1252 let mut math_config: MathConfig = MATHCONFIG.load(deps.storage)?;
1254 let config: Config = CONFIG.load(deps.storage)?;
1255 let vault_config = query_vault_config(&deps.querier, config.vault_addr.clone().to_string())?;
1256
1257 if info.sender != vault_config.owner && info.sender != config.vault_addr {
1259 return Err(ContractError::Unauthorized {});
1260 }
1261
1262 let current_amp = compute_current_amp(&math_config, &env)?;
1263 let block_time = env.block.time.seconds();
1264
1265 math_config.init_amp = current_amp;
1266 math_config.next_amp = current_amp;
1267 math_config.init_amp_time = block_time;
1268 math_config.next_amp_time = block_time;
1269
1270 MATHCONFIG.save(deps.storage, &math_config)?;
1272
1273 Ok(Response::new().add_event(Event::from_info(
1274 concatcp!(CONTRACT_NAME, "::update_config::stop_changing_amp"),
1275 &info,
1276 )))
1277}
1278
1279#[cw_serde]
1285pub struct ImbalancedWithdrawResponse {
1286 pub burn_amount: Uint128,
1287 pub fee: Vec<Asset>,
1288}
1289
1290fn imbalanced_withdraw(
1300 deps: Deps,
1301 env: &Env,
1302 config: &Config,
1303 math_config: &MathConfig,
1304 assets: &[Asset],
1305 total_share: Uint128,
1306) -> Result<ImbalancedWithdrawResponse, ContractError> {
1307 if assets.len() > config.assets.len() {
1308 return Err(ContractError::InvalidNumberOfAssets {});
1309 }
1310
1311 let stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
1312
1313 let pools: HashMap<_, _> = config
1315 .assets
1316 .clone()
1317 .into_iter()
1318 .map(|pool| (pool.info, pool.amount))
1319 .collect();
1320
1321 let scaling_factors = stableswap_config.scaling_factors();
1322 let mut assets_collection = assets
1323 .iter()
1324 .cloned()
1325 .map(|asset| {
1326 let precision = get_precision(deps.storage, &asset.info)?;
1327 let pool = pools
1329 .get(&asset.info)
1330 .copied()
1331 .ok_or_else(|| ContractError::InvalidAsset(asset.info.to_string()))?;
1332
1333 let scaling_factor = scaling_factors
1334 .get(&asset.info)
1335 .cloned()
1336 .unwrap_or(Decimal256::one());
1337 Ok((
1338 asset.to_scaled_decimal_asset(precision, scaling_factor)?,
1339 Decimal256::with_precision(pool, precision)?.with_scaling_factor(scaling_factor)?,
1340 ))
1341 })
1342 .collect::<Result<Vec<_>, ContractError>>()?;
1343
1344 pools
1346 .into_iter()
1347 .try_for_each(|(pool_info, pool_amount)| -> StdResult<()> {
1348 if !assets.iter().any(|asset| asset.info == pool_info) {
1349 let precision = get_precision(deps.storage, &pool_info)?;
1350
1351 let scaling_factor = scaling_factors
1352 .get(&pool_info)
1353 .cloned()
1354 .unwrap_or(Decimal256::one());
1355 assets_collection.push((
1356 DecimalAsset {
1357 amount: Decimal256::zero(),
1358 info: pool_info,
1359 },
1360 Decimal256::with_precision(pool_amount, precision)?
1361 .with_scaling_factor(scaling_factor)?,
1362 ));
1363 }
1364 Ok(())
1365 })?;
1366
1367 let n_coins = config.assets.len() as u8;
1368 let amp = Uint64::from(compute_current_amp(math_config, env)?);
1369
1370 let old_balances = assets_collection
1372 .iter()
1373 .map(|(_, pool)| *pool)
1374 .collect_vec();
1375
1376 let init_d = compute_d(amp, &old_balances)?;
1377
1378 let mut new_balances = assets_collection
1380 .iter()
1381 .map(|(withdraw, pool)| Ok(pool.checked_sub(withdraw.amount)?))
1382 .collect::<StdResult<Vec<_>>>()?;
1383 let withdraw_d = compute_d(amp, &new_balances)?;
1384
1385 let fee = Decimal::from_ratio(config.fee_info.total_fee_bps, FEE_PRECISION)
1387 .checked_mul(Decimal::from_ratio(n_coins, 4 * (n_coins - 1)))?;
1388
1389 let fee = Decimal256::new(fee.atomics().into());
1390
1391 let mut fee_tokens: Vec<Asset> = vec![];
1393
1394 for i in 0..n_coins as usize {
1396 let asset_info = assets_collection[i].0.info.clone();
1398
1399 let ideal_balance = withdraw_d.checked_multiply_ratio(old_balances[i], init_d)?;
1400 let difference = if ideal_balance > new_balances[i] {
1401 ideal_balance - new_balances[i]
1402 } else {
1403 new_balances[i] - ideal_balance
1404 };
1405 let fee_charged = fee.checked_mul(difference)?;
1406
1407 new_balances[i] -= fee_charged;
1408 let scaling_factor = scaling_factors
1409 .get(&asset_info)
1410 .cloned()
1411 .unwrap_or(Decimal256::one());
1412 fee_tokens.push(Asset {
1413 amount: fee_charged
1414 .without_scaling_factor(scaling_factor)?
1415 .to_uint128_with_precision(get_precision(deps.storage, &asset_info)?)?,
1416 info: asset_info,
1417 });
1418 }
1419
1420 let after_fee_d = compute_d(amp, &new_balances)?;
1422
1423 let total_share = Uint256::from(total_share);
1424
1425 let burn_amount = total_share
1427 .checked_multiply_ratio(
1428 init_d.atomics().checked_sub(after_fee_d.atomics())?,
1429 init_d.atomics(),
1430 )?
1431 .checked_add(Uint256::from(1u8))?; let burn_amount = burn_amount.try_into()?;
1434
1435 Ok(ImbalancedWithdrawResponse {
1436 burn_amount,
1437 fee: fee_tokens,
1438 })
1439}
1440
1441
1442fn compute_current_amp(math_config: &MathConfig, env: &Env) -> StdResult<u64> {
1452 let block_time = env.block.time.seconds();
1454
1455 if block_time < math_config.next_amp_time {
1457 let init_amp = Uint128::from(math_config.init_amp);
1459 let next_amp = Uint128::from(math_config.next_amp);
1460
1461 let elapsed_time =
1463 Uint128::from(block_time).checked_sub(Uint128::from(math_config.init_amp_time))?;
1464 let time_range = Uint128::from(math_config.next_amp_time)
1465 .checked_sub(Uint128::from(math_config.init_amp_time))?;
1466
1467 if math_config.next_amp > math_config.init_amp {
1469 let amp_range = next_amp - init_amp;
1470 let res = init_amp + (amp_range * elapsed_time).checked_div(time_range)?;
1471 Ok(res.u128() as u64)
1472 } else {
1473 let amp_range = init_amp - next_amp;
1474 let res = init_amp - (amp_range * elapsed_time).checked_div(time_range)?;
1475 Ok(res.u128() as u64)
1476 }
1477 } else {
1478 Ok(math_config.next_amp)
1479 }
1480}
1481
1482pub fn transform_to_scaled_decimal_asset(
1489 deps: Deps,
1490 assets: Vec<Asset>,
1491) -> StdResult<Vec<DecimalAsset>> {
1492 let stableswap_config = STABLESWAP_CONFIG.load(deps.storage)?;
1493 let scaling_factors = stableswap_config.scaling_factors();
1494
1495 assets
1496 .iter()
1497 .cloned()
1498 .map(|asset| {
1499 let precision = get_precision(deps.storage, &asset.info)?;
1500 let scaling_factor = scaling_factors
1501 .get(&asset.info)
1502 .cloned()
1503 .unwrap_or(Decimal256::one());
1504 asset.to_scaled_decimal_asset(precision, scaling_factor)
1505 })
1506 .collect::<StdResult<Vec<DecimalAsset>>>()
1507}
1508
1509#[cfg_attr(not(feature = "library"), entry_point)]
1511pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> ContractResult<Response> {
1512 match msg {
1513 MigrateMsg::V1_1 {} => {
1514 let version = get_contract_version(deps.storage)?;
1516 if version.version != CONTRACT_VERSION_V1 {
1517 return Err(ContractError::InvalidContractVersion {
1518 expected_version: CONTRACT_VERSION_V1.to_string(),
1519 current_version: version.version,
1520 });
1521 }
1522
1523 if version.contract != CONTRACT_NAME {
1524 return Err(ContractError::InvalidContractName {
1525 expected_name: CONTRACT_NAME.to_string(),
1526 contract_name: version.contract,
1527 });
1528 }
1529
1530 let stableswap_config: StableSwapConfigV1 = Item::new("stableswap_config").load(deps.storage)?;
1532
1533 let stableswap_config_new: StableSwapConfig = StableSwapConfig {
1535 supports_scaling_factors_update: stableswap_config.supports_scaling_factors_update,
1536 scaling_factors: stableswap_config.scaling_factors,
1537 scaling_factor_manager: stableswap_config.scaling_factor_manager
1538 };
1539
1540 STABLESWAP_CONFIG.save(deps.storage, &stableswap_config_new)?;
1541 set_contract_version(deps.storage, CONTRACT_VERSION, CONTRACT_NAME)?;
1542
1543 Ok(Response::default())
1544 }
1545 }
1546}