diff --git a/contracts/transmuter/src/contract.rs b/contracts/transmuter/src/contract.rs index 344df49..8c7ebe6 100644 --- a/contracts/transmuter/src/contract.rs +++ b/contracts/transmuter/src/contract.rs @@ -679,7 +679,8 @@ impl Transmuter<'_> { swap_fee: Decimal, ) -> Result { self.ensure_valid_swap_fee(swap_fee)?; - let (_pool, token_out) = self.out_amt_given_in(deps, token_in, &token_out_denom)?; + let pool = self.pool.load(deps.storage)?; + let (_pool, token_out) = self.out_amt_given_in(deps, pool, token_in, &token_out_denom)?; Ok(CalcOutAmtGivenInResponse { token_out }) } @@ -693,7 +694,8 @@ impl Transmuter<'_> { swap_fee: Decimal, ) -> Result { self.ensure_valid_swap_fee(swap_fee)?; - let (_pool, token_in) = self.in_amt_given_out(deps, token_out, token_in_denom)?; + let pool = self.pool.load(deps.storage)?; + let (_pool, token_in) = self.in_amt_given_out(deps, pool, token_out, token_in_denom)?; Ok(CalcInAmtGivenOutResponse { token_in }) } diff --git a/contracts/transmuter/src/limiter/limiters.rs b/contracts/transmuter/src/limiter/limiters.rs index 31ffccd..9f3b3d8 100644 --- a/contracts/transmuter/src/limiter/limiters.rs +++ b/contracts/transmuter/src/limiter/limiters.rs @@ -521,24 +521,33 @@ impl<'a> Limiters<'a> { pub fn check_limits_and_update( &self, storage: &mut dyn Storage, - denom_value_pairs: Vec<(String, Decimal)>, + denom_value_pairs: Vec<(String, (Decimal, Decimal))>, block_time: Timestamp, ) -> Result<(), ContractError> { - for (denom, value) in denom_value_pairs { + for (denom, (prev_value, value)) in denom_value_pairs { let limiters = self.list_limiters_by_denom(storage, denom.as_str())?; + let is_not_decreasing = value >= prev_value; for (label, limiter) in limiters { - // match limiter type - + // Enforce limiter only if value is increasing, because if the value is decreasing from the previous value, + // for the specific denom, it is a balancing act to move away from the limit. let limiter = match limiter { - Limiter::ChangeLimiter(limiter) => Limiter::ChangeLimiter( - limiter - .ensure_upper_limit(block_time, denom.as_str(), value)? - .update(block_time, value)?, - ), - Limiter::StaticLimiter(limiter) => { - Limiter::StaticLimiter(limiter.ensure_upper_limit(denom.as_str(), value)?) - } + Limiter::ChangeLimiter(limiter) => Limiter::ChangeLimiter({ + if is_not_decreasing { + limiter + .ensure_upper_limit(block_time, denom.as_str(), value)? + .update(block_time, value)? + } else { + limiter.update(block_time, value)? + } + }), + Limiter::StaticLimiter(limiter) => Limiter::StaticLimiter({ + if is_not_decreasing { + limiter.ensure_upper_limit(denom.as_str(), value)? + } else { + limiter + } + }), }; // save updated limiter @@ -648,6 +657,8 @@ mod tests { use super::*; + const EPSILON: Decimal = Decimal::raw(1); + mod registration { use super::*; @@ -1897,7 +1908,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), value)], + vec![("denoma".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -1915,7 +1926,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), value)], + vec![("denoma".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -1931,7 +1942,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), value)], + vec![("denoma".to_string(), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -1957,7 +1968,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), value)], + vec![("denoma".to_string(), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -1980,7 +1991,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), value)], + vec![("denoma".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -1996,7 +2007,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), value)], + vec![("denoma".to_string(), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -2017,7 +2028,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), value)], + vec![("denoma".to_string(), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -2040,7 +2051,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), value)], + vec![("denoma".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2086,7 +2097,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2101,7 +2112,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2116,7 +2127,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -2139,7 +2150,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2155,7 +2166,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -2180,7 +2191,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2236,7 +2247,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2246,7 +2257,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2256,7 +2267,7 @@ mod tests { limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); @@ -2267,7 +2278,7 @@ mod tests { let err = limiter .check_limits_and_update( &mut deps.storage, - vec![("denomb".to_string(), value)], + vec![("denomb".to_string(), (value - EPSILON, value))], block_time, ) .unwrap_err(); @@ -2282,6 +2293,116 @@ mod tests { ); } + #[test] + fn test_change_limiters_away_from_limit() { + let mut deps = mock_dependencies(); + let limiter = Limiters::new("limiters"); + + limiter + .register( + &mut deps.storage, + "denom", + "1h", + LimiterParams::ChangeLimiter { + window_config: WindowConfig { + window_size: Uint64::from(3_600_000_000_000u64), + division_count: Uint64::from(4u64), + }, + boundary_offset: Decimal::percent(1), + }, + ) + .unwrap(); + + let block_time = Timestamp::from_nanos(1661231280000000000); + + // Start and set the limit + let value = Decimal::percent(55); // starting limit = 56 + limiter + .check_limits_and_update( + &mut deps.storage, + vec![("denom".to_string(), (Decimal::zero(), value))], + block_time, + ) + .unwrap(); + + // Increasing value should fail + let new_block_time = block_time.plus_nanos(900_000_000_000); // 15 minutes later + let new_value = Decimal::percent(57); + let err = limiter + .check_limits_and_update( + &mut deps.storage, + vec![("denom".to_string(), (value, new_value))], + new_block_time, + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::UpperLimitExceeded { + denom: "denom".to_string(), + upper_limit: Decimal::percent(56), + value: new_value, + } + ); + + // Move away from limit but still above limit + let value = Decimal::percent(58); + let new_value = Decimal::percent(57); + limiter + .check_limits_and_update( + &mut deps.storage, + vec![("denom".to_string(), (value, new_value))], + new_block_time, + ) + .unwrap(); + + // Move away from limit within the window + let new_block_time = block_time.plus_nanos(900_000_000_000); // 15 minutes later + let value = Decimal::percent(58); + let new_value = Decimal::percent(54); // Moving away from the limit + + limiter + .check_limits_and_update( + &mut deps.storage, + vec![("denom".to_string(), (value, new_value))], + new_block_time, + ) + .unwrap(); + + // Try to move further away from the limit + let final_block_time = new_block_time.plus_nanos(900_000_000_000); // Another 15 minutes later + let value = Decimal::percent(58); + let final_value = Decimal::percent(52); // Moving even further away from the limit + + limiter + .check_limits_and_update( + &mut deps.storage, + vec![("denom".to_string(), (value, final_value))], + final_block_time, + ) + .unwrap(); + + // Increasing the value from there should fail + let value = Decimal::percent(58); + let new_value = Decimal::percent(59); + let err = limiter + .check_limits_and_update( + &mut deps.storage, + vec![("denom".to_string(), (value, new_value))], + final_block_time, + ) + .unwrap_err(); + + assert_eq!( + err, + ContractError::UpperLimitExceeded { + denom: "denom".to_string(), + upper_limit: Decimal::from_str("0.555").unwrap(), + value: new_value, + } + ); + } + #[test] fn test_static_limiter() { let mut deps = mock_dependencies(); @@ -2317,8 +2438,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a - EPSILON, value_a)), + ("denomb".to_string(), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2331,8 +2452,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a - EPSILON, value_a)), + ("denomb".to_string(), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2354,8 +2475,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a + EPSILON, value_a)), + ("denomb".to_string(), (value_b - EPSILON, value_b)), ], block_time, ) @@ -2377,8 +2498,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a - EPSILON, value_a)), + ("denomb".to_string(), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2391,8 +2512,46 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a - EPSILON, value_a)), + ("denomb".to_string(), (value_b + EPSILON, value_b)), + ], + block_time, + ) + .unwrap(); + + // Test case where start value is over limit but decreasing, even if not yet under limit + let value_b = Decimal::from_str("0.75").unwrap(); // Start above 0.7 limit + let value_a = Decimal::one() - value_b; + + let new_value_b = Decimal::from_str("0.72").unwrap(); // Decrease, but still above 0.7 limit + let new_value_a = Decimal::one() - new_value_b; + + // This should not error, as we're moving in the right direction + limiter + .check_limits_and_update( + &mut deps.storage, + vec![ + ("denoma".to_string(), (value_a, new_value_a)), + ("denomb".to_string(), (value_b, new_value_b)), + ], + block_time, + ) + .unwrap(); + + // Test case where start value is over limit but decreasing for denom a + let value_a = Decimal::from_str("0.65").unwrap(); // Start above 0.6 limit + let value_b = Decimal::one() - value_a; + + let new_value_a = Decimal::from_str("0.62").unwrap(); // Decrease, but still above 0.6 limit + let new_value_b = Decimal::one() - new_value_a; + + // This should not error, as we're moving in the right direction for denom a + limiter + .check_limits_and_update( + &mut deps.storage, + vec![ + ("denoma".to_string(), (value_a, new_value_a)), + ("denomb".to_string(), (value_b, new_value_b)), ], block_time, ) @@ -2481,8 +2640,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a, value_a)), + ("denomb".to_string(), (value_b, value_b)), ], block_time, ) @@ -2495,8 +2654,11 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value), - ("denomb".to_string(), Decimal::one() - value), + ("denoma".to_string(), (value - EPSILON, value)), + ( + "denomb".to_string(), + (Decimal::one() - value + EPSILON, Decimal::one() - value), + ), ], block_time, ) @@ -2518,8 +2680,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a + EPSILON, value_a)), + ("denomb".to_string(), (value_b - EPSILON, value_b)), ], block_time, ) @@ -2541,8 +2703,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a, value_a)), + ("denomb".to_string(), (value_b, value_b)), ], block_time, ) @@ -2558,8 +2720,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a - EPSILON, value_a)), + ("denomb".to_string(), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2581,8 +2743,8 @@ mod tests { .check_limits_and_update( &mut deps.storage, vec![ - ("denoma".to_string(), value_a), - ("denomb".to_string(), value_b), + ("denoma".to_string(), (value_a - EPSILON, value_a)), + ("denomb".to_string(), (value_b + EPSILON, value_b)), ], block_time, ) @@ -2802,7 +2964,7 @@ mod tests { limiters .check_limits_and_update( &mut deps.storage, - vec![("denoma".to_string(), value)], + vec![("denoma".to_string(), (value - EPSILON, value))], block_time, ) .unwrap(); diff --git a/contracts/transmuter/src/swap.rs b/contracts/transmuter/src/swap.rs index 003fcea..9a542dd 100644 --- a/contracts/transmuter/src/swap.rs +++ b/contracts/transmuter/src/swap.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{ ensure, ensure_eq, to_json_binary, Addr, BankMsg, Coin, Decimal, Deps, DepsMut, Env, Response, @@ -122,13 +124,15 @@ impl Transmuter<'_> { ContractError::ZeroValueOperation {} ); + let prev_weights = pool.weights_map()?; + pool.join_pool(&tokens_in)?; // check and update limiters only if pool assets are not zero - if let Some(denom_weight_pairs) = pool.weights()? { + if let Some(updated_weights) = pool.weights()? { self.limiters.check_limits_and_update( deps.storage, - denom_weight_pairs, + pair_weights_by_denom(prev_weights, updated_weights), env.block.time, )?; } @@ -292,13 +296,15 @@ impl Transmuter<'_> { pool.weights()?.unwrap_or_default(), )?; } else { + let prev_weights = pool.weights_map()?; + pool.exit_pool(&tokens_out)?; // check and update limiters only if pool assets are not zero - if let Some(denom_weight_pairs) = pool.weights()? { + if let Some(updated_weights) = pool.weights()? { self.limiters.check_limits_and_update( deps.storage, - denom_weight_pairs, + pair_weights_by_denom(prev_weights, updated_weights), env.block.time, )?; } @@ -338,8 +344,11 @@ impl Transmuter<'_> { deps: DepsMut, env: Env, ) -> Result { + let pool = self.pool.load(deps.storage)?; + let prev_weights = pool.weights_map()?; + let (mut pool, actual_token_out) = - self.out_amt_given_in(deps.as_ref(), token_in, token_out_denom)?; + self.out_amt_given_in(deps.as_ref(), pool, token_in, token_out_denom)?; // ensure token_out amount is greater than or equal to token_out_min_amount ensure!( @@ -351,10 +360,10 @@ impl Transmuter<'_> { ); // check and update limiters only if pool assets are not zero - if let Some(denom_weight_pairs) = pool.weights()? { + if let Some(updated_weights) = pool.weights()? { self.limiters.check_limits_and_update( deps.storage, - denom_weight_pairs, + pair_weights_by_denom(prev_weights, updated_weights), env.block.time, )?; } @@ -387,8 +396,15 @@ impl Transmuter<'_> { deps: DepsMut, env: Env, ) -> Result { - let (mut pool, actual_token_in) = - self.in_amt_given_out(deps.as_ref(), token_out.clone(), token_in_denom.to_string())?; + let pool = self.pool.load(deps.storage)?; + let prev_weights = pool.weights_map()?; + + let (mut pool, actual_token_in) = self.in_amt_given_out( + deps.as_ref(), + pool, + token_out.clone(), + token_in_denom.to_string(), + )?; ensure!( actual_token_in.amount <= token_in_max_amount, @@ -399,10 +415,10 @@ impl Transmuter<'_> { ); // check and update limiters only if pool assets are not zero - if let Some(denom_weight_pairs) = pool.weights()? { + if let Some(updated_weights) = pool.weights()? { self.limiters.check_limits_and_update( deps.storage, - denom_weight_pairs, + pair_weights_by_denom(prev_weights, updated_weights), env.block.time, )?; } @@ -429,11 +445,11 @@ impl Transmuter<'_> { pub fn in_amt_given_out( &self, deps: Deps, + mut pool: TransmuterPool, token_out: Coin, token_in_denom: String, ) -> Result<(TransmuterPool, Coin), ContractError> { let swap_variant = self.swap_variant(&token_in_denom, &token_out.denom, deps)?; - let mut pool = self.pool.load(deps.storage)?; Ok(match swap_variant { SwapVariant::TokenToAlloyed => { @@ -490,10 +506,10 @@ impl Transmuter<'_> { pub fn out_amt_given_in( &self, deps: Deps, + mut pool: TransmuterPool, token_in: Coin, token_out_denom: &str, ) -> Result<(TransmuterPool, Coin), ContractError> { - let mut pool = self.pool.load(deps.storage)?; let swap_variant = self.swap_variant(&token_in.denom, token_out_denom, deps)?; Ok(match swap_variant { @@ -581,6 +597,20 @@ impl Transmuter<'_> { } } +fn pair_weights_by_denom( + prev_weights: BTreeMap, + updated_weights: Vec<(String, Decimal)>, +) -> Vec<(String, (Decimal, Decimal))> { + let mut denom_weight_pairs = Vec::new(); + + for (denom, weight) in updated_weights { + let prev_weight = prev_weights.get(denom.as_str()).unwrap_or(&weight); + denom_weight_pairs.push((denom, (*prev_weight, weight))); + } + + denom_weight_pairs +} + /// Possible variants of swap, depending on the input and output tokens #[derive(PartialEq, Debug)] pub enum SwapVariant { diff --git a/contracts/transmuter/src/test/cases/scenarios.rs b/contracts/transmuter/src/test/cases/scenarios.rs index 7a76501..e487955 100644 --- a/contracts/transmuter/src/test/cases/scenarios.rs +++ b/contracts/transmuter/src/test/cases/scenarios.rs @@ -1275,3 +1275,191 @@ fn test_limiters() { err, ); } + +#[test] +fn test_register_limiter_after_having_liquidity() { + let app = OsmosisTestApp::new(); + let cp = CosmwasmPool::new(&app); + + let admin = app + .init_account(&[Coin::new(100_000u128, "uosmo")]) + .unwrap(); + + let t = TestEnvBuilder::new() + .with_account( + "alice", + vec![ + Coin::new(1_000_000, AXL_USDC), + Coin::new(1_000_000, COSMOS_USDC), + ], + ) + .with_account( + "bob", + vec![ + Coin::new(1_000_000, AXL_USDC), + Coin::new(1_000_000, COSMOS_USDC), + ], + ) + .with_account("admin", vec![]) + .with_account( + "provider", + vec![ + Coin::new(1_000_000, AXL_USDC), + Coin::new(1_000_000, COSMOS_USDC), + ], + ) + .with_instantiate_msg(InstantiateMsg { + pool_asset_configs: vec![ + AssetConfig::from_denom_str(AXL_USDC), + AssetConfig::from_denom_str(COSMOS_USDC), + ], + alloyed_asset_subdenom: "usdc".to_string(), + alloyed_asset_normalization_factor: Uint128::one(), + admin: Some(admin.address()), + moderator: "osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks".to_string(), + }) + .build(&app); + + // query share denom + let GetShareDenomResponse { share_denom } = + t.contract.query(&QueryMsg::GetShareDenom {}).unwrap(); + + let alloyed_denom = share_denom; + + cp.swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: t.accounts["provider"].address(), + token_in: Some(Coin::new(200_000, COSMOS_USDC).into()), + routes: vec![SwapAmountInRoute { + pool_id: t.contract.pool_id, + token_out_denom: alloyed_denom.clone(), + }], + token_out_min_amount: Uint128::from(200_000u128).to_string(), + }, + &t.accounts["provider"], + ) + .unwrap(); + + t.contract + .execute( + &ExecMsg::RegisterLimiter { + denom: COSMOS_USDC.to_string(), + label: "static".to_string(), + limiter_params: LimiterParams::StaticLimiter { + upper_limit: Decimal::percent(60), + }, + }, + &[], + &t.accounts["admin"], + ) + .unwrap(); + + let err = cp + .swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: t.accounts["provider"].address(), + token_in: Some(Coin::new(1, COSMOS_USDC).into()), + routes: vec![SwapAmountInRoute { + pool_id: t.contract.pool_id, + token_out_denom: alloyed_denom.clone(), + }], + token_out_min_amount: Uint128::from(1u128).to_string(), + }, + &t.accounts["provider"], + ) + .unwrap_err(); + + assert_contract_err( + ContractError::UpperLimitExceeded { + denom: COSMOS_USDC.to_string(), + upper_limit: Decimal::from_str("0.6").unwrap(), + value: Decimal::from_str("1").unwrap(), + }, + err, + ); + + // Swap AXL USDC to alloyed asset should be successful + cp.swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: t.accounts["provider"].address(), + token_in: Some(Coin::new(1, AXL_USDC).into()), + routes: vec![SwapAmountInRoute { + pool_id: t.contract.pool_id, + token_out_denom: alloyed_denom.clone(), + }], + token_out_min_amount: Uint128::from(1u128).to_string(), + }, + &t.accounts["provider"], + ) + .unwrap(); + + // Swap alloyed asset to COSMOS USDC should be successful + cp.swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: t.accounts["provider"].address(), + token_in: Some(Coin::new(1, &alloyed_denom).into()), + routes: vec![SwapAmountInRoute { + pool_id: t.contract.pool_id, + token_out_denom: COSMOS_USDC.to_string(), + }], + token_out_min_amount: Uint128::from(1u128).to_string(), + }, + &t.accounts["provider"], + ) + .unwrap(); + + // Swap AXL USDC to COSMOS USDC should be successful and reduce COSMOS USDC composition + cp.swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: t.accounts["provider"].address(), + token_in: Some(Coin::new(1, AXL_USDC).into()), + routes: vec![SwapAmountInRoute { + pool_id: t.contract.pool_id, + token_out_denom: COSMOS_USDC.to_string(), + }], + token_out_min_amount: Uint128::from(1u128).to_string(), + }, + &t.accounts["provider"], + ) + .unwrap(); + + // Swap the other way around should fail + let err = cp + .swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: t.accounts["provider"].address(), + token_in: Some(Coin::new(1, COSMOS_USDC).into()), + routes: vec![SwapAmountInRoute { + pool_id: t.contract.pool_id, + token_out_denom: AXL_USDC.to_string(), + }], + token_out_min_amount: Uint128::from(1u128).to_string(), + }, + &t.accounts["provider"], + ) + .unwrap_err(); + + assert_contract_err( + ContractError::UpperLimitExceeded { + denom: COSMOS_USDC.to_string(), + upper_limit: Decimal::from_str("0.6").unwrap(), + value: Decimal::from_str("0.999995").unwrap(), + }, + err, + ); + + // Swap alloyed asset to COSMOS USDC should be successful + cp.swap_exact_amount_in( + MsgSwapExactAmountIn { + sender: t.accounts["provider"].address(), + token_in: Some(Coin::new(1, alloyed_denom).into()), + routes: vec![SwapAmountInRoute { + pool_id: t.contract.pool_id, + token_out_denom: COSMOS_USDC.to_string(), + }], + token_out_min_amount: Uint128::from(1u128).to_string(), + }, + &t.accounts["provider"], + ) + .unwrap(); +} diff --git a/contracts/transmuter/src/transmuter_pool/weight.rs b/contracts/transmuter/src/transmuter_pool/weight.rs index 53eb8eb..5125ca8 100644 --- a/contracts/transmuter/src/transmuter_pool/weight.rs +++ b/contracts/transmuter/src/transmuter_pool/weight.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use cosmwasm_std::{Decimal, Uint128}; use crate::{ @@ -49,6 +51,10 @@ impl TransmuterPool { Ok(Some(ratios)) } + pub fn weights_map(&self) -> Result, ContractError> { + Ok(self.weights()?.unwrap_or_default().into_iter().collect()) + } + fn normalized_asset_values( &self, std_norm_factor: Uint128,