dexter_stable_pool/
contract.rs

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
23/// Contract name that is used for migration.
24const CONTRACT_NAME: &str = "dexter-stable-pool";
25/// Contract version that is used for migration.
26const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
27const CONTRACT_VERSION_V1: &str = "1.0.0";
28
29/// Maximum number of assets supported by the pool
30const MAX_ASSETS: usize = 5;
31/// Minimum number of assets supported by the pool
32const MIN_ASSETS: usize = 2;
33
34type ContractResult<T> = Result<T, ContractError>;
35
36// ----------------x----------------x----------------x----------------x----------------x----------------
37// ----------------x----------------x      Instantiate Contract : Execute function     x----------------
38// ----------------x----------------x----------------x----------------x----------------x----------------
39
40/// ## Description
41/// Creates a new contract with the specified parameters in the [`InstantiateMsg`].
42/// Returns the [`Response`] with the specified attributes if the operation was successful, or a [`ContractError`] if the contract was not created
43///
44/// ## Params
45/// * **msg** is a message of type [`InstantiateMsg`] which contains the basic settings for creating a contract
46#[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    // Validate number of assets
56    if msg.asset_infos.len() > MAX_ASSETS || msg.asset_infos.len() < MIN_ASSETS {
57        return Err(ContractError::InvalidNumberOfAssets {});
58    }
59
60    // StableSwap Pool parameters
61    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    // Validate that scaling factor is specified for all the assets in the pool
67    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    // validate that no duplicate asset_infos are present in scaling_factor_asset_infos
75    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    // validate that scaling factor is a subset of the pool_assets by iterating
82    // over the scaling_factor_asset_infos and checking if the asset_info is present in pool_assets
83    for asset_info in scaling_factor_asset_infos {
84        if !pool_assets.contains(&asset_info) {
85            return Err(ContractError::InvalidScalingFactorAssetInfo);
86        }
87    }
88
89    // validate that scaling factor is greater than 0 for all the assets
90    for asset_scaling_factor in &params.scaling_factors {
91        if asset_scaling_factor.scaling_factor == Decimal256::zero() {
92            return Err(ContractError::InvalidScalingFactor);
93        }
94    }
95
96    // Validate scaling factor manager address
97    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        // Validate address
106        deps.api
107            .addr_validate(&params.scaling_factor_manager.clone().unwrap().to_string())?;
108    }
109
110    // store precisions for assets in storage
111    let greatest_precision = store_precisions(
112        deps.branch(),
113        &msg.native_asset_precisions,
114        &msg.asset_infos,
115        PRECISIONS,
116    )?;
117    // We cannot have precision greater than what is supported by Decimal type
118    if greatest_precision > (Decimal::DECIMAL_PLACES as u8) {
119        return Err(ContractError::InvalidGreatestPrecision);
120    }
121
122    // Initializing cumulative prices
123    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    // Create [`Asset`] from [`AssetInfo`]
133    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    // Store config, MathConfig and twap in storage
172    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(&params.scaling_factors).unwrap(),
198        );
199
200    // Add scaling_factor_manager attribute if present
201    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// ----------------x----------------x----------------x------------------x----------------x----------------
216// ----------------x----------------x  Execute function :: Entry Point  x----------------x----------------
217// ----------------x----------------x----------------x------------------x----------------x----------------
218
219/// ## Description
220/// Available the execute messages of the contract.
221///
222/// ## Params
223/// * **deps** is the object of type [`Deps`].
224/// * **env** is the object of type [`Env`].
225/// * **info** is the object of type [`MessageInfo`].
226/// * **msg** is the object of type [`ExecuteMsg`].
227#[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
245/// Updates the scaling factor of the asset in the pool.
246/// Only scaling factor manager can execute this function.
247fn 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    // Access Check :: Only scaling factor manager can execute this function
255    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    // Update or insert scaling factor for this asset in scaling factors vector
285    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    // Update config
299    stableswap_config.scaling_factors = scaling_factors;
300    STABLESWAP_CONFIG.save(deps.storage, &stableswap_config)?;
301
302    // Emit an event
303    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
316/// Updates the scaling factor manager of the pool.
317/// Only vault owner can execute this function.
318fn 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    // validate scaling factor manager address
328    deps.api.addr_validate(scaling_factor_manager.as_str())?;
329
330    // Access Check :: Only Vault's Owner can execute this function
331    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    // Update config
340    stableswap_config.scaling_factor_manager = Some(scaling_factor_manager.clone());
341    STABLESWAP_CONFIG.save(deps.storage, &stableswap_config)?;
342
343    // Emit an event
344    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
356/// ## Description
357/// Admin Access by Vault :: Callable only by Dexter::Vault --> Updates locally stored asset balances state. Operation --> Updates locally stored [`Asset`] state
358///                          Returns an [`ContractError`] on failure, otherwise returns the [`Response`] with the specified attributes if the operation was successful.
359///
360/// ## Params
361/// * **assets** is a field of type [`Vec<Asset>`]. It is a sorted list of `Asset` which contain the token type details and new updates balances of tokens as accounted by the pool
362pub fn execute_update_liquidity(
363    deps: DepsMut,
364    env: Env,
365    info: MessageInfo,
366    assets: Vec<Asset>,
367) -> Result<Response, ContractError> {
368    // Get config and twap info
369    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    // Acess Check :: Only Vault can execute this function
374    if info.sender != config.vault_addr {
375        return Err(ContractError::Unauthorized {});
376    }
377
378    // Convert Vec<Asset> to Vec<DecimalAsset> type
379    let decimal_assets: Vec<DecimalAsset> =
380        transform_to_scaled_decimal_asset(deps.as_ref(), config.assets.clone())?;
381
382    // Accumulate prices for the assets in the pool
383    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    // TWAP computation can fail in certain edge cases (when pool is empty for eg), for which you need
393    // to allow tx to be successful rather than failing the tx. Accumulated prices can be used to
394    // calculate TWAP oracle prices later and letting the tx be successful even when price accumulation
395    // fails doesn't cause any issues.
396    // UPDATE: We have now handled the edge case of pool join operation so this should ideally never fail.
397    // But we keep this check here for safety, so the pool operations don't fail because of this
398    {
399        TWAPINFO.save(deps.storage, &twap)?;
400    }
401
402    // Update state
403    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
419/// ## Description
420/// Updates the pool's math configuration with the specified parameters in the `params` variable.
421/// Returns a [`ContractError`] as a failure, otherwise returns a [`Response`] with the specified
422/// attributes if the operation was successful
423///
424/// ## Params
425/// * **deps** is an object of type [`DepsMut`].
426/// * **env** is an object of type [`Env`].
427/// * **info** is an object of type [`MessageInfo`].
428/// * **params** is an object of type [`Binary`]. These are the the new parameter values.
429pub fn update_config(
430    deps: DepsMut,
431    env: Env,
432    info: MessageInfo,
433    params: Binary,
434) -> Result<Response, ContractError> {
435    match from_json::<StablePoolUpdateParams>(&params)? {
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// ----------------x----------------x---------------------x-----------------------x----------------x----------------
451// ----------------x----------------x  :::: StableSwap POOL::QUERIES Implementation   ::::  x----------------x----------------
452// ----------------x----------------x---------------------x-----------------------x----------------x----------------
453
454/// ## Description
455/// Available the query messages of the contract.
456///
457/// ## Params
458/// * **deps** is the object of type [`Deps`].
459/// * **_env** is the object of type [`Env`].
460/// * **msg** is the object of type [`QueryMsg`].
461/// ## Queries
462#[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
497/// ## Description
498/// Returns information about the controls settings in a [`ConfigResponse`] object.
499/// ## Params
500/// * **deps** is the object of type [`Deps`].
501pub 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
528/// ## Description
529/// Returns information about the Fees settings in a [`FeeResponse`] object.
530/// ## Params
531/// * **deps** is the object of type [`Deps`].
532pub 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
539/// ## Description
540/// Returns information Pool ID which is of type [`Uint128`]
541/// ## Params
542/// * **deps** is the object of type [`Deps`].
543pub fn query_pool_id(deps: Deps) -> StdResult<Uint128> {
544    let config: Config = CONFIG.load(deps.storage)?;
545    Ok(config.pool_id)
546}
547
548//--------x------------------x--------------x-----x-----
549//--------x    Query :: OnJoin, OnExit, OnSwap    x-----
550//--------x------------------x--------------x-----x-----
551
552/// ## Description
553/// Returns [`AfterJoinResponse`] type which contains -
554/// return_assets - Is of type [`Vec<Asset>`] and is a sorted list consisting of amount and info of tokens which are to be subtracted from
555/// the token balances provided by the user to the Vault, to get the final list of token balances to be provided as Liquiditiy against the minted LP shares
556/// new_shares - New LP shares which are to be minted
557/// response - A [`ResponseType`] which is either `Success` or `Failure`, deteriming if the tx is accepted by the Pool's math computations or not
558///
559/// ## Params
560/// assets_in - Of type [`Vec<Asset>`], a sorted list containing amount / info of token balances to be supplied as liquidity to the pool
561/// _mint_amount - Of type [`Option<Uint128>`], optional parameter which tells the number of LP tokens to be minted
562/// STABLE-5-SWAP POOL -::- MATH LOGIC
563/// -- Implementation - For STABLE-5-swap, user provides the exact number of assets he/she wants to supply as liquidity to the pool. We simply calculate the number of LP shares to be minted and return it to the user.
564/// T.B.A
565pub 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 the user has not provided any assets to be provided, then return a `Failure` response
572    if assets_in.is_none() {
573        return Ok(return_join_failure("No assets provided".to_string()));
574    }
575
576    // Load the config and math config from the storage
577    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    // Sort the assets in the order of the assets in the config
582    let mut act_assets_in = assets_in.unwrap();
583
584    // Get Asset stored in state for each asset in a HashMap
585    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    // get asset info for each asset in the list provided by the user to its pool mapping
594    let mut assets_collection = act_assets_in
595        .clone()
596        .into_iter()
597        .map(|asset| {
598            // Check that at least one asset is non-zero
599            if !asset.amount.is_zero() {
600                non_zero_flag = true;
601            }
602
603            // Get appropriate pool
604            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 there's no non-zero assets in the list provided by the user, then return a `Failure` response
611    if !non_zero_flag {
612        return Ok(return_join_failure(
613            "No non-zero assets provided".to_string(),
614        ));
615    }
616
617    // If some assets are omitted then add them explicitly with 0 deposit
618    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    // Sort assets because the vault expects them in order
635    act_assets_in.sort_by_key(|a| a.info.clone());
636
637    // Check asset definitions and make sure no asset is repeated
638    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    // We cannot put a zero amount into an empty pool.
649    // This means that the first time we bootstrap the pool, we have to provide liquidity for all the assets in the pool.
650    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    // Convert to Decimal types
661    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    // Compute amp parameter
679    let amp = compute_current_amp(&math_config, &env).unwrap_or(0u64.into());
680
681    // If AMP value is invalid, then return a `Failure` response
682    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    // Initial invariant (D)
689    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    // Invariant (D) after deposit added
696    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    // Total share of LP tokens minted by the pool
703    let total_share = query_supply(&deps.querier, config.lp_token_addr)?;
704
705    // Tokens to be charged as Fee
706    let mut fee_tokens: Vec<Asset> = vec![];
707
708    // Calculate the number of LP shares to be minted
709    let mint_amount = if total_share.is_zero() {
710        deposit_d
711    } else {
712        // Get fee info from the factory
713        let fee_info = config.fee_info.clone();
714
715        // Calculate fee using the curve formula:
716        // fee = total_fee_bps * N_COINS / (4 * (N_COINS - 1))
717        // specified here:
718        // https://github.com/curvefi/curve-contract/blob/master/contracts/pools/3pool/StableSwap3Pool.vy#L274
719        //
720        // Based on the docs here:
721        // https://resources.curve.fi/lp/understanding-curve-pools#what-are-curve-fees
722        // It seems fees are calculated so as to keep it in between (0, sawp_fee/2).
723        // If pool has two coins, then deposit/withdraw fee is exactly half of the swap fee.
724        // As number of coins increases, this would keep decreasing to 0.
725        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            // Asset Info for token i
732            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            // Fee will be charged only during imbalanced provide i.e. if invariant D was changed
742            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 the mint amount is zero, then return a `Failure` response
766    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
780/// ## Description
781/// Returns [`AfterExitResponse`] type which contains -
782/// assets_out - Is of type [`Vec<Asset>`] and is a sorted list consisting of amount and info of tokens which are to be subtracted from the PoolInfo state stored in the Vault contract and transfer from the Vault to the user
783/// burn_shares - Number of LP shares to be burnt
784/// response - A [`ResponseType`] which is either `Success` or `Failure`, deteriming if the tx is accepted by the Pool's math computations or not
785///
786/// ## Params
787/// assets_out - Of type [`Vec<Asset>`], a sorted list containing amount / info of token balances user wants against the LP tokens transferred by the user to the Vault contract
788/// * **deps** is the object of type [`Deps`].
789/// STABLE-5-SWAP POOL -::- MATH LOGIC
790/// T.B.A
791pub 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    // Total share of LP tokens minted by the pool
799    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 the user has not provided number of LP tokens to be burnt, then return a `Failure` response
808            if burn_amount.is_zero() {
809                return Ok(return_exit_failure("Burn amount is zero".to_string()));
810            }
811
812            // For ExactLpBurn, we just burn the LP tokens and return the underlying assets based on their share in the pool
813            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            // Check asset definitions and make sure no asset is repeated
819            let mut assets_out_ = assets_out.clone();
820            // first sort the assets
821            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            // Imbalanced withdraw
833            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    // although this check isn't required given the current state of code, but better to be safe than sorry.
856    if act_burn_amount.is_zero() {
857        return Ok(return_exit_failure("Burn amount is zero".to_string()));
858    }
859
860    // If some assets are omitted then add them explicitly with 0 deposit
861    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    // Sort the refund assets
874    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
884/// ## Description
885/// Returns [`SwapResponse`] type which contains -
886/// trade_params - Is of type [`Trade`] which contains all params related with the trade, including the number of assets to be traded, spread, and the fees to be paid
887/// response - A [`ResponseType`] which is either `Success` or `Failure`, deteriming if the tx is accepted by the Pool's math computations or not
888///
889/// ## Params
890///  swap_type - Is of type [`SwapType`] which is either `GiveIn`, `GiveOut` or `Custom`
891///  offer_asset_info - Of type [`AssetInfo`] which is the asset info of the asset to be traded in the offer side of the trade
892/// ask_asset_info - Of type [`AssetInfo`] which is the asset info of the asset to be traded in the ask side of the trade
893/// amount - Of type [`Uint128`] which is the amount of assets to be traded on ask or offer side, based on the swap type
894pub 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    // Load the config and math config from the storage
903    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    // Get the current balances of the Offer and ask assets from the supported assets list
911    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 there's 0 assets balance return failure response
923    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    // Offer and ask asset precisions
930    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    // Based on swap_type, we set the amount to either offer_asset or ask_asset pool
948    match swap_type {
949        SwapType::GiveIn {} => {
950            offer_asset = Asset {
951                info: offer_asset_info.clone(),
952                amount,
953            };
954
955            // Calculate the commission fees
956            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            // Calculate the number of ask_asset tokens to be transferred to the recipient from the Vault contract
966            (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            // Calculate the number of offer_asset tokens to be transferred from the trader from the Vault contract
1003            (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    // Check if the calculated amount is valid
1035    // although this check isn't required given the current state of code, but better to be safe than sorry.
1036    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
1056/// ## Description
1057/// Returns information about the cumulative price of the asset in a [`CumulativePriceResponse`] object.
1058/// ## Params
1059/// * **deps** is the object of type [`Deps`].
1060/// * **env** is the object of type [`Env`].
1061/// * **offer_asset** is the object of type [`AssetInfo`].
1062/// * **ask_asset** is the object of type [`AssetInfo`].
1063pub fn query_cumulative_price(
1064    deps: Deps,
1065    env: Env,
1066    offer_asset_info: AssetInfo,
1067    ask_asset_info: AssetInfo,
1068) -> StdResult<CumulativePriceResponse> {
1069    // Load the config, mathconfig  and twap from the storage
1070    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    // Convert Vec<Asset> to Vec<DecimalAsset> type
1077    let decimal_assets: Vec<DecimalAsset> = transform_to_scaled_decimal_asset(deps, config.assets)?;
1078
1079    // Accumulate prices of all assets in the config
1080    accumulate_prices(deps, env, math_config, &mut twap, &decimal_assets)
1081        .map_err(|err| StdError::generic_err(format!("{err}")))?;
1082
1083    // Find the `cumulative_price` for the provided offer and ask asset in the stored TWAP. Error if not found
1084    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    // Return the cumulative price response
1093    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
1137/// ## Description
1138/// Returns information about the cumulative prices in a [`CumulativePricesResponse`] object.
1139/// ## Params
1140/// * **deps** is the object of type [`Deps`].
1141/// * **env** is the object of type [`Env`].
1142pub 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    // Convert Vec<Asset> to Vec<DecimalAsset> type
1150    let decimal_assets: Vec<DecimalAsset> = transform_to_scaled_decimal_asset(deps, config.assets)?;
1151
1152    // Accumulate prices of all assets in the config
1153    accumulate_prices(deps, env, math_config, &mut twap, &decimal_assets)
1154        .map_err(|err| StdError::generic_err(format!("{err}")))?;
1155
1156    // Prepare the cumulative prices response
1157    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
1172// --------x--------x--------x--------x--------x--------x------
1173// --------x--------x AMP UPDATE Helpers  x--------x--------x--
1174// --------x--------x--------x--------x--------x--------x------
1175
1176/// ## Description
1177/// Start changing the AMP value. Returns a [`ContractError`] on failure, otherwise returns [`Ok`].
1178///
1179/// ## Params
1180/// * **mut math_config** is an object of type [`Config`]. This is a mutable reference to the pool's custom math configuration.
1181/// * **next_amp** is an object of type [`u64`]. This is the new value for AMP.
1182/// * **next_amp_time** is an object of type [`u64`]. This is the end time when the pool amplification will be equal to `next_amp`.
1183fn 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    // Load the math config from the storage
1191    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    // Access Check :: Only Vault's Owner can execute this function
1196    if info.sender != vault_config.owner && info.sender != config.vault_addr {
1197        return Err(ContractError::Unauthorized {});
1198    }
1199
1200    // Validation checks
1201    if next_amp == 0 || next_amp > MAX_AMP {
1202        return Err(ContractError::IncorrectAmp {});
1203    }
1204
1205    // Current and next AMP values
1206    let current_amp = compute_current_amp(&math_config, &env)?;
1207    let next_amp_with_precision = next_amp * AMP_PRECISION;
1208
1209    // Max allowed AMP change checks
1210    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    // Time gap for AMP change checks
1217    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    // Update the math config
1225    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    // Update the storage
1231    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
1243/// ## Description
1244/// Stop changing the AMP value. Returns [`Ok`].
1245/// ## Params
1246/// * **mut math_config** is an object of type [`MathConfig`]. This is a mutable reference to the pool's custom math configuration.
1247fn stop_changing_amp(
1248    deps: DepsMut,
1249    env: Env,
1250    info: MessageInfo,
1251) -> Result<Response, ContractError> {
1252    // Load the math config from the storage
1253    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    // Access Check :: Only Vault's Owner can execute this function
1258    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    // now (block_time < next_amp_time) is always False, so we return the saved AMP
1271    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// --------x--------x--------x--------x--------x--------
1280// --------x--------x COMPUTATATIONS  x--------x--------
1281// --------x--------x--------x--------x--------x--------
1282
1283/// ## Description - This struct describes the Fee configuration supported by a particular pool type.
1284#[cw_serde]
1285pub struct ImbalancedWithdrawResponse {
1286    pub burn_amount: Uint128,
1287    pub fee: Vec<Asset>,
1288}
1289
1290/// ## Description
1291/// Imbalanced withdraw liquidity from the pool. Returns a [`ContractError`] on failure,
1292/// otherwise returns the number of LP tokens to burn.
1293/// ## Params
1294/// * **deps** is an object of type [`Deps`].
1295/// * **env** is an object of type [`Env`].
1296/// * **config** is an object of type [`Config`].
1297/// * **provided_amount** is an object of type [`Uint128`]. This is the amount of provided LP tokens to withdraw liquidity with.
1298/// * **assets** is array with objects of type [`Asset`]. It specifies the assets amount to withdraw. It is assumed that the assets are unique.
1299fn 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    // Store Pool balances in a hashMap
1314    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            // Get appropriate pool
1328            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    // If some assets are omitted then add them explicitly with 0 withdraw amount
1345    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    // Initial invariant (D)
1371    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    // Invariant (D) after assets withdrawn
1379    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    // total_fee_bps * N_COINS / (4 * (N_COINS - 1))
1386    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    // Tokens to be charged as Fee
1392    let mut fee_tokens: Vec<Asset> = vec![];
1393
1394    // Fee is applied
1395    for i in 0..n_coins as usize {
1396        // Asset Info for token i
1397        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    // New invariant (D) after fee applied
1421    let after_fee_d = compute_d(amp, &new_balances)?;
1422
1423    let total_share = Uint256::from(total_share);
1424
1425    // How many tokens do we need to burn to withdraw asked assets?
1426    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))?; // In case of rounding errors - make it unfavorable for the "attacker"
1432
1433    let burn_amount = burn_amount.try_into()?;
1434
1435    Ok(ImbalancedWithdrawResponse {
1436        burn_amount,
1437        fee: fee_tokens,
1438    })
1439}
1440
1441
1442// --------x--------x--------x--------x--------x--------x--------
1443// --------x--------x AMP COMPUTE Functions   x--------x---------
1444// --------x--------x--------x--------x--------x--------x--------
1445
1446/// ## Description
1447/// Compute the current pool amplification coefficient (AMP).
1448///
1449/// ## Params
1450/// * **math_config** is an object of type [`MathConfig`].
1451fn compute_current_amp(math_config: &MathConfig, env: &Env) -> StdResult<u64> {
1452    // Get block time
1453    let block_time = env.block.time.seconds();
1454
1455    // If we are in the period of AMP change, we calculate the latest new AMP
1456    if block_time < math_config.next_amp_time {
1457        // initial and final AMPs
1458        let init_amp = Uint128::from(math_config.init_amp);
1459        let next_amp = Uint128::from(math_config.next_amp);
1460
1461        // time elapsed since AMP change init and the total time range of AMP change
1462        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        // Calculate AMP based on if AMP is being increased or decreased
1468        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
1482// --------x--------x--------x--------x--------x--------x---
1483// --------x--------x Helper Functions   x--------x---------
1484// --------x--------x--------x--------x--------x--------x---
1485
1486/// ## Description
1487/// Converts [`Vec<Asset>`] to [`Vec<DecimalAsset>`].
1488pub 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// migrate msg
1510#[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            // fetch current version to ensure it's v1
1515            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            // fix the stableswap config
1531            let stableswap_config: StableSwapConfigV1 =  Item::new("stableswap_config").load(deps.storage)?;
1532
1533            // remove the max_allowed_spread field
1534            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}