From 97d701b70739e2472a93974f406c9d1dc37ea3da Mon Sep 17 00:00:00 2001 From: Khuzama Shahid Date: Thu, 16 Apr 2026 10:49:30 +0500 Subject: [PATCH 1/9] test(stableswap): upgrade peg ordering tests to 3-asset pools with generic helpers --- pallets/stableswap/src/tests/mod.rs | 1 + pallets/stableswap/src/tests/peg_ordering.rs | 1039 ++++++++++++++++++ 2 files changed, 1040 insertions(+) create mode 100644 pallets/stableswap/src/tests/peg_ordering.rs diff --git a/pallets/stableswap/src/tests/mod.rs b/pallets/stableswap/src/tests/mod.rs index 241ab5f54..6edbef766 100644 --- a/pallets/stableswap/src/tests/mod.rs +++ b/pallets/stableswap/src/tests/mod.rs @@ -13,6 +13,7 @@ mod invariants; pub(crate) mod mock; mod peg; mod peg_one; +mod peg_ordering; mod pegs_with_different_decimals; mod price; mod remove_liquidity; diff --git a/pallets/stableswap/src/tests/peg_ordering.rs b/pallets/stableswap/src/tests/peg_ordering.rs new file mode 100644 index 000000000..16bbbbfb1 --- /dev/null +++ b/pallets/stableswap/src/tests/peg_ordering.rs @@ -0,0 +1,1039 @@ +#![allow(deprecated)] + +use crate::tests::mock::*; +use crate::types::{BoundedPegSources, PegSource, PoolPegInfo}; +use crate::{Event, PoolPegs, Pools}; +use frame_support::{assert_ok, BoundedVec}; +use hydradx_traits::stableswap::AssetAmount; +use hydradx_traits::OraclePeriod; +use sp_runtime::{Perbill, Permill}; + +// Re-use ONE from mock; TVL sized for balanced initial liquidity in peg pools. +const TVL: u128 = 2_000_000 * ONE; + +type PoolId = AssetId; + +// ── helpers ────────────────────────────────────────────────────────────────── + +/// Create a peg pool with `Value`-typed pegs. `assets` and `pegs` are +/// positionally paired — any ordering of assets is accepted. +fn make_peg_pool(pool_id: PoolId, assets: Vec, pegs: Vec<(u128, u128)>) { + let assets_bv: BoundedVec = assets.try_into().unwrap(); + let pegs_bv: BoundedPegSources = + BoundedVec::try_from(pegs.into_iter().map(PegSource::Value).collect::>()).unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + assets_bv, + 1000, + Permill::zero(), + pegs_bv, + Perbill::from_percent(100), + )); +} + +/// Add liquidity to a pool. Zero-amount entries are excluded so callers can +/// omit assets by passing 0 (respects MinTradingLimit). +fn add_liquidity(pool_id: PoolId, who: AccountId, amounts: Vec<(AssetId, Balance)>) { + let entries: Vec> = amounts + .into_iter() + .filter(|(_, a)| *a > 0) + .map(|(asset, amount)| AssetAmount::new(asset, amount)) + .collect(); + assert_ok!(Stableswap::add_assets_liquidity( + RuntimeOrigin::signed(who), + pool_id, + BoundedVec::try_from(entries).unwrap(), + Balance::zero(), + )); +} + +// ── Phase 1 & 2: storage correctness ───────────────────────────────────────── + +/// RED/GREEN – Task 1.3 / 2.1 +/// +/// After creating a pool with assets provided in unsorted order, both +/// `PoolPegs.source` and `PoolPegs.current` must be co-sorted with the sorted +/// `Pools.assets`. Read storage immediately after pool creation, before any +/// operation that could mutate PoolPegs. +/// +/// Uses three assets [10, 5, 20] (partially unsorted) as specified in the plan. +#[test] +fn pool_pegs_should_be_cosorted_with_assets() { + // Input [10, 5, 20] is partially unsorted; sorted order is [5, 10, 20]. + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let asset_20: AssetId = 20; + let pool_id: PoolId = 100; + + ExtBuilder::default() + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("a20".as_bytes().to_vec(), asset_20, 12) + .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) + .build() + .execute_with(|| { + // Caller intent: asset_10 → (2,1), asset_5 → (1,1), asset_20 → (3,1). + // Input order: [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. + let unsorted_assets: BoundedVec = + vec![asset_10, asset_5, asset_20].try_into().unwrap(); + let unsorted_pegs: BoundedPegSources = BoundedVec::try_from(vec![ + PegSource::Value((2, 1)), + PegSource::Value((1, 1)), + PegSource::Value((3, 1)), + ]) + .unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + unsorted_assets, + 1000, + Permill::zero(), + unsorted_pegs, + Perbill::from_percent(100), + )); + + // Read immediately — no other operation touches PoolPegs yet. + let pool = Pools::::get(pool_id).expect("pool must exist"); + let peg_info = PoolPegs::::get(pool_id).expect("peg info must exist"); + + // Sorted pool assets: [5, 10, 20]. + assert_eq!( + pool.assets.to_vec(), + vec![asset_5, asset_10, asset_20], + "pool assets must be sorted", + ); + + // Peg sources must be co-sorted to match sorted assets [5, 10, 20]: + // asset_5 → (1,1), asset_10 → (2,1), asset_20 → (3,1). + // BUG: currently stored as input order [(2,1), (1,1), (3,1)]. + assert_eq!( + peg_info.source.to_vec(), + vec![ + PegSource::Value((1, 1)), + PegSource::Value((2, 1)), + PegSource::Value((3, 1)), + ], + "peg sources must be co-sorted with pool assets; got {:?}", + peg_info.source.to_vec(), + ); + + // PoolPegs.current holds resolved peg values; must also be co-sorted. + // PegSource::Value is resolved verbatim, so current[i] == source[i] value. + assert_eq!( + peg_info.current.to_vec(), + vec![(1u128, 1u128), (2u128, 1u128), (3u128, 1u128)], + "current pegs must be co-sorted with pool assets; got {:?}", + peg_info.current.to_vec(), + ); + }); +} + +/// RED/GREEN – Task 1.3 variant: three assets with maximally wrong order. +/// +/// Input [20, 10, 5] (reverse-sorted) with pegs [(3,1), (2,1), (1,1)]. +/// After sorting, pool stores [5, 10, 20]; pegs must follow as [(1,1), (2,1), (3,1)]. +#[test] +fn pool_pegs_should_be_cosorted_with_three_assets_reverse_order() { + let asset_20: AssetId = 20; + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let pool_id: PoolId = 100; + + ExtBuilder::default() + .with_registered_asset("a20".as_bytes().to_vec(), asset_20, 12) + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) + .build() + .execute_with(|| { + // Reverse-sorted input. + let assets: BoundedVec = vec![asset_20, asset_10, asset_5].try_into().unwrap(); + let pegs: BoundedPegSources = BoundedVec::try_from(vec![ + PegSource::Value((3, 1)), // for asset_20 + PegSource::Value((2, 1)), // for asset_10 + PegSource::Value((1, 1)), // for asset_5 + ]) + .unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + assets, + 1000, + Permill::zero(), + pegs, + Perbill::from_percent(100), + )); + + let pool = Pools::::get(pool_id).unwrap(); + let peg_info = PoolPegs::::get(pool_id).unwrap(); + + assert_eq!(pool.assets.to_vec(), vec![asset_5, asset_10, asset_20]); + + assert_eq!( + peg_info.source.to_vec(), + vec![ + PegSource::Value((1, 1)), // asset_5 + PegSource::Value((2, 1)), // asset_10 + PegSource::Value((3, 1)), // asset_20 + ], + "sources must be co-sorted; got {:?}", + peg_info.source.to_vec(), + ); + + assert_eq!( + peg_info.current.to_vec(), + vec![(1u128, 1u128), (2u128, 1u128), (3u128, 1u128)], + "current pegs must be co-sorted; got {:?}", + peg_info.current.to_vec(), + ); + }); +} + +// ── Phase 1 & 2: trade correctness ─────────────────────────────────────────── + +/// RED/GREEN – Task 1.1 / 2.1 +/// +/// Sell through an unsorted pool must yield the same output as an identical +/// pool created with assets already in sorted order. +/// +/// Three assets [10, 5, 20] as specified in the plan to exercise the +/// partial-permutation mismatch (positions 0 and 1 swapped, position 2 correct). +#[test] +fn sell_should_use_correct_pegs_when_assets_are_unsorted() { + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let asset_20: AssetId = 20; + let pool_id: PoolId = 100; + let pool_id_ref: PoolId = 101; + let liquid = TVL / 2; + let trade = 100 * ONE; + + ExtBuilder::default() + .with_endowed_accounts(vec![ + (ALICE, asset_10, liquid * 2), + (ALICE, asset_5, liquid * 2), + (ALICE, asset_20, liquid * 2), + (BOB, asset_10, trade * 2), + ]) + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("a20".as_bytes().to_vec(), asset_20, 12) + .with_registered_asset("pool1".as_bytes().to_vec(), pool_id, 12) + .with_registered_asset("pool2".as_bytes().to_vec(), pool_id_ref, 12) + .build() + .execute_with(|| { + // Unsorted pool: input [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. + // Intent: asset_10 → (2,1), asset_5 → (1,1), asset_20 → (3,1). + make_peg_pool(pool_id, vec![asset_10, asset_5, asset_20], vec![(2, 1), (1, 1), (3, 1)]); + add_liquidity(pool_id, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + + let bal_before = Tokens::free_balance(asset_5, &BOB); + assert_ok!(Stableswap::sell(RuntimeOrigin::signed(BOB), pool_id, asset_10, asset_5, trade, 0)); + let received_unsorted = Tokens::free_balance(asset_5, &BOB) - bal_before; + + // Reference pool: sorted [5, 10, 20] with co-sorted pegs [(1,1), (2,1), (3,1)]. + make_peg_pool( + pool_id_ref, + vec![asset_5, asset_10, asset_20], + vec![(1, 1), (2, 1), (3, 1)], + ); + add_liquidity(pool_id_ref, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + + let bal_before2 = Tokens::free_balance(asset_5, &BOB); + assert_ok!(Stableswap::sell( + RuntimeOrigin::signed(BOB), + pool_id_ref, + asset_10, + asset_5, + trade, + 0, + )); + let received_sorted = Tokens::free_balance(asset_5, &BOB) - bal_before2; + + assert_eq!( + received_unsorted, received_sorted, + "unsorted pool sell output ({}) must equal sorted pool output ({})", + received_unsorted, received_sorted, + ); + }); +} + +/// RED/GREEN – Task 1.2 / 2.1 (buy variant) +/// +/// Buy through an unsorted pool must yield the same cost as the reference pool. +/// +/// Three assets [10, 5, 20] as per the plan. +#[test] +fn buy_should_use_correct_pegs_when_assets_are_unsorted() { + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let asset_20: AssetId = 20; + let pool_id: PoolId = 100; + let pool_id_ref: PoolId = 101; + let liquid = TVL / 2; + let buy_amount = 50 * ONE; + + ExtBuilder::default() + .with_endowed_accounts(vec![ + (ALICE, asset_10, liquid * 2), + (ALICE, asset_5, liquid * 2), + (ALICE, asset_20, liquid * 2), + (BOB, asset_10, TVL), + ]) + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("a20".as_bytes().to_vec(), asset_20, 12) + .with_registered_asset("pool1".as_bytes().to_vec(), pool_id, 12) + .with_registered_asset("pool2".as_bytes().to_vec(), pool_id_ref, 12) + .build() + .execute_with(|| { + // Unsorted pool: input [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. + make_peg_pool(pool_id, vec![asset_10, asset_5, asset_20], vec![(2, 1), (1, 1), (3, 1)]); + add_liquidity(pool_id, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + + let bal_before = Tokens::free_balance(asset_10, &BOB); + assert_ok!(Stableswap::buy( + RuntimeOrigin::signed(BOB), + pool_id, + asset_5, + asset_10, + buy_amount, + TVL, + )); + let spent_unsorted = bal_before - Tokens::free_balance(asset_10, &BOB); + + // Reference pool: sorted [5, 10, 20] with pegs [(1,1), (2,1), (3,1)]. + make_peg_pool( + pool_id_ref, + vec![asset_5, asset_10, asset_20], + vec![(1, 1), (2, 1), (3, 1)], + ); + add_liquidity(pool_id_ref, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + + let bal_before2 = Tokens::free_balance(asset_10, &BOB); + assert_ok!(Stableswap::buy( + RuntimeOrigin::signed(BOB), + pool_id_ref, + asset_5, + asset_10, + buy_amount, + TVL, + )); + let spent_sorted = bal_before2 - Tokens::free_balance(asset_10, &BOB); + + assert_eq!( + spent_unsorted, spent_sorted, + "unsorted pool buy cost ({}) must equal sorted pool buy cost ({})", + spent_unsorted, spent_sorted, + ); + }); +} + +/// RED/GREEN – Task 1.4 / 2.1 (Oracle peg variant) +/// +/// Most real peg pools use oracle sources, where each asset has a distinct oracle +/// pair. When assets are unsorted, the oracle lookups are misrouted to the wrong +/// asset, silently applying the wrong price feed. +/// +/// Three assets [10, 5, 20]: asset_10 and asset_20 use Oracle sources, asset_5 +/// uses Value. The unsorted pool mis-assigns Oracle to asset_5 (wrong) and +/// Value to asset_10 (wrong), while asset_20 coincidentally stays correct. +#[test] +fn sell_should_use_correct_oracle_pegs_when_assets_are_unsorted() { + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let asset_20: AssetId = 20; + // oracle_asset is the "denominator" asset used in the oracle pair lookup. + // set_peg_oracle_value(oracle_asset, peg_asset, price, ts) registers + // the price for (oracle_asset, peg_asset) in the mock oracle. + let oracle_source = *b"testtest"; + let pool_id: PoolId = 100; + let pool_id_ref: PoolId = 101; + let liquid = TVL / 2; + let trade = 100 * ONE; + + ExtBuilder::default() + .with_endowed_accounts(vec![ + (ALICE, asset_10, liquid * 2), + (ALICE, asset_5, liquid * 2), + (ALICE, asset_20, liquid * 2), + (BOB, asset_10, trade * 2), + ]) + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("a20".as_bytes().to_vec(), asset_20, 12) + .with_registered_asset("pool1".as_bytes().to_vec(), pool_id, 12) + .with_registered_asset("pool2".as_bytes().to_vec(), pool_id_ref, 12) + .build() + .execute_with(|| { + // Register oracle entries: asset_10 priced in asset_5 → (2,1). + set_peg_oracle_value(asset_5, asset_10, (2, 1), 1); + // asset_20 priced in asset_5 → (3,1). + set_peg_oracle_value(asset_5, asset_20, (3, 1), 1); + // Fallback for the misaligned lookup (asset_5, asset_5) that the buggy + // pool will request when asset_5 incorrectly inherits the Oracle source. + // Returns (1,1) — same as the intended Value peg for asset_5, making + // add_liquidity succeed so the sell can expose the misalignment. + set_peg_oracle_value(asset_5, asset_5, (1, 1), 1); + + // ── Unsorted pool ──────────────────────────────────────────────────── + // Intent: asset_10 → Oracle(2,1), asset_5 → Value(1,1), asset_20 → Oracle(3,1). + // Input order: [10, 5, 20]. Bug stores source as-is, so sorted pool + // maps: asset_5 → Oracle (wrong), asset_10 → Value (wrong), asset_20 → Oracle (correct by coincidence). + let unsorted_assets: BoundedVec = + vec![asset_10, asset_5, asset_20].try_into().unwrap(); + let unsorted_pegs: BoundedPegSources = BoundedVec::try_from(vec![ + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // intended for asset_10 + PegSource::Value((1, 1)), // intended for asset_5 + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // intended for asset_20 + ]) + .unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + unsorted_assets, + 1000, + Permill::zero(), + unsorted_pegs, + Perbill::from_percent(100), + )); + add_liquidity(pool_id, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + + let bal_before = Tokens::free_balance(asset_5, &BOB); + assert_ok!(Stableswap::sell(RuntimeOrigin::signed(BOB), pool_id, asset_10, asset_5, trade, 0)); + let received_unsorted = Tokens::free_balance(asset_5, &BOB) - bal_before; + + // ── Reference pool (sorted) ────────────────────────────────────────── + // Sorted [5, 10, 20], pegs: [Value(1,1), Oracle(..), Oracle(..)]. + let sorted_assets: BoundedVec = + vec![asset_5, asset_10, asset_20].try_into().unwrap(); + let sorted_pegs: BoundedPegSources = BoundedVec::try_from(vec![ + PegSource::Value((1, 1)), + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), + ]) + .unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id_ref, + sorted_assets, + 1000, + Permill::zero(), + sorted_pegs, + Perbill::from_percent(100), + )); + add_liquidity(pool_id_ref, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + + let bal_before2 = Tokens::free_balance(asset_5, &BOB); + assert_ok!(Stableswap::sell( + RuntimeOrigin::signed(BOB), + pool_id_ref, + asset_10, + asset_5, + trade, + 0, + )); + let received_sorted = Tokens::free_balance(asset_5, &BOB) - bal_before2; + + assert_eq!( + received_unsorted, received_sorted, + "unsorted oracle-peg pool sell output ({}) must equal sorted pool output ({})", + received_unsorted, received_sorted, + ); + }); +} + +// ── Phase 1 & 2: update_asset_peg_source ───────────────────────────────────── + +/// RED/GREEN – Task 1.5 / 2.1 +/// +/// `update_asset_peg_source` derives the storage index from the *sorted* pool +/// assets via `pool.find_asset(asset_id)`. If `PoolPegs.source` is stored in +/// unsorted (input) order, the update writes to the wrong position. +/// +/// Three assets [10, 5, 20]: sorted order is [5, 10, 20], so asset_10 is at +/// index 1 and asset_20 at index 2. After updating asset_10, both other +/// slots must remain unchanged. +#[test] +fn update_asset_peg_source_should_target_correct_asset_after_unsorted_creation() { + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let asset_20: AssetId = 20; + let pool_id: PoolId = 100; + + ExtBuilder::default() + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("a20".as_bytes().to_vec(), asset_20, 12) + .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) + .build() + .execute_with(|| { + // Unsorted input [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. + make_peg_pool(pool_id, vec![asset_10, asset_5, asset_20], vec![(2, 1), (1, 1), (3, 1)]); + + let new_peg = PegSource::Value((5, 1)); + assert_ok!(Stableswap::update_asset_peg_source( + RuntimeOrigin::root(), + pool_id, + asset_10, + new_peg.clone(), + )); + + let pool = Pools::::get(pool_id).unwrap(); + let peg_info = PoolPegs::::get(pool_id).unwrap(); + + // Derive the expected index the same way the extrinsic does. + let expected_idx = pool.find_asset(asset_10).expect("asset_10 must be in pool"); + // After sorting [10, 5, 20] → [5, 10, 20], asset_10 is at index 1. + assert_eq!(expected_idx, 1, "asset_10 must be at sorted index 1"); + + assert_eq!( + peg_info.source[expected_idx], new_peg, + "peg source at sorted index {} must be updated; got {:?}", + expected_idx, peg_info.source[expected_idx], + ); + + // asset_5 at index 0 must remain unchanged. + assert_eq!( + peg_info.source[0], + PegSource::Value((1, 1)), + "asset_5 peg source must be unchanged; got {:?}", + peg_info.source[0], + ); + + // asset_20 at index 2 must remain unchanged. + assert_eq!( + peg_info.source[2], + PegSource::Value((3, 1)), + "asset_20 peg source must be unchanged; got {:?}", + peg_info.source[2], + ); + }); +} + +// ── Phase 1 & 2: remove_liquidity ──────────────────────────────────────────── + +/// RED/GREEN – Task 1.6 / 2.1 +/// +/// Removing liquidity uses peg-adjusted reserves to price the withdrawal. +/// With misaligned pegs the withdrawal amount differs from the reference pool. +/// +/// Three assets [10, 5, 20] as per the plan. +#[test] +fn remove_liquidity_should_use_correct_pegs_when_assets_unsorted() { + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let asset_20: AssetId = 20; + let pool_id: PoolId = 100; + let pool_id_ref: PoolId = 101; + let liquid = TVL / 2; + let add_liq = 10_000 * ONE; + + ExtBuilder::default() + .with_endowed_accounts(vec![ + (ALICE, asset_10, liquid * 2), + (ALICE, asset_5, liquid * 2), + (ALICE, asset_20, liquid * 2), + (BOB, asset_10, add_liq * 2), + ]) + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("a20".as_bytes().to_vec(), asset_20, 12) + .with_registered_asset("pool1".as_bytes().to_vec(), pool_id, 12) + .with_registered_asset("pool2".as_bytes().to_vec(), pool_id_ref, 12) + .build() + .execute_with(|| { + // Unsorted pool: input [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. + make_peg_pool(pool_id, vec![asset_10, asset_5, asset_20], vec![(2, 1), (1, 1), (3, 1)]); + add_liquidity(pool_id, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity(pool_id, BOB, vec![(asset_10, add_liq), (asset_5, 0), (asset_20, 0)]); + + let shares = Tokens::free_balance(pool_id, &BOB); + let bal_before = Tokens::free_balance(asset_5, &BOB); + assert_ok!(Stableswap::remove_liquidity_one_asset( + RuntimeOrigin::signed(BOB), + pool_id, + asset_5, + shares / 2, + 0, + )); + let received_unsorted = Tokens::free_balance(asset_5, &BOB) - bal_before; + + // Reference pool: sorted [5, 10, 20] with pegs [(1,1), (2,1), (3,1)]. + make_peg_pool( + pool_id_ref, + vec![asset_5, asset_10, asset_20], + vec![(1, 1), (2, 1), (3, 1)], + ); + add_liquidity(pool_id_ref, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity(pool_id_ref, BOB, vec![(asset_10, add_liq), (asset_5, 0), (asset_20, 0)]); + + let shares2 = Tokens::free_balance(pool_id_ref, &BOB); + let bal_before2 = Tokens::free_balance(asset_5, &BOB); + assert_ok!(Stableswap::remove_liquidity_one_asset( + RuntimeOrigin::signed(BOB), + pool_id_ref, + asset_5, + shares2 / 2, + 0, + )); + let received_sorted = Tokens::free_balance(asset_5, &BOB) - bal_before2; + + assert_eq!( + received_unsorted, received_sorted, + "unsorted pool remove_liquidity_one_asset ({}) must equal sorted pool ({})", + received_unsorted, received_sorted, + ); + }); +} + +// ── Phase 2: edge cases ─────────────────────────────────────────────────────── + +/// GREEN – already-sorted assets are unaffected (regression guard). +/// +/// When assets are provided in sorted order the co-sort is a no-op; behaviour +/// must be identical to the pre-fix baseline. +#[test] +fn pool_pegs_are_correct_when_assets_provided_already_sorted() { + let asset_5: AssetId = 5; + let asset_10: AssetId = 10; + let pool_id: PoolId = 100; + + ExtBuilder::default() + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) + .build() + .execute_with(|| { + // Already sorted: [5, 10]. + let assets: BoundedVec = vec![asset_5, asset_10].try_into().unwrap(); + let pegs: BoundedPegSources = + BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((2, 1))]).unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + assets, + 1000, + Permill::zero(), + pegs, + Perbill::from_percent(100), + )); + + let pool = Pools::::get(pool_id).unwrap(); + let peg_info = PoolPegs::::get(pool_id).unwrap(); + + assert_eq!(pool.assets.to_vec(), vec![asset_5, asset_10]); + assert_eq!( + peg_info.source.to_vec(), + vec![PegSource::Value((1, 1)), PegSource::Value((2, 1))], + ); + assert_eq!(peg_info.current.to_vec(), vec![(1u128, 1u128), (2u128, 1u128)]); + }); +} + +/// GREEN – five assets in random order (MAX_ASSETS_IN_POOL). +/// +/// Verifies the co-sort works at the maximum pool size with a non-trivial +/// permutation (not already sorted, not reverse-sorted). +#[test] +fn pool_pegs_should_be_cosorted_with_five_assets_random_order() { + // Sorted order would be [1, 3, 5, 7, 9]; provide as [5, 1, 9, 3, 7]. + let asset_1: AssetId = 1; + let asset_3: AssetId = 3; + let asset_5: AssetId = 5; + let asset_7: AssetId = 7; + let asset_9: AssetId = 9; + let pool_id: PoolId = 100; + + ExtBuilder::default() + .with_registered_asset("a1".as_bytes().to_vec(), asset_1, 12) + .with_registered_asset("a3".as_bytes().to_vec(), asset_3, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("a7".as_bytes().to_vec(), asset_7, 12) + .with_registered_asset("a9".as_bytes().to_vec(), asset_9, 12) + .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) + .build() + .execute_with(|| { + // Input order [5, 1, 9, 3, 7]; pegs paired to this order. + let assets: BoundedVec = vec![asset_5, asset_1, asset_9, asset_3, asset_7] + .try_into() + .unwrap(); + let pegs: BoundedPegSources = BoundedVec::try_from(vec![ + PegSource::Value((5, 1)), // for asset_5 + PegSource::Value((1, 1)), // for asset_1 + PegSource::Value((9, 1)), // for asset_9 + PegSource::Value((3, 1)), // for asset_3 + PegSource::Value((7, 1)), // for asset_7 + ]) + .unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + assets, + 1000, + Permill::zero(), + pegs, + Perbill::from_percent(100), + )); + + let pool = Pools::::get(pool_id).unwrap(); + let peg_info = PoolPegs::::get(pool_id).unwrap(); + + // Sorted order: [1, 3, 5, 7, 9]. + assert_eq!( + pool.assets.to_vec(), + vec![asset_1, asset_3, asset_5, asset_7, asset_9], + ); + + // Peg values must match sorted asset order. + assert_eq!( + peg_info.source.to_vec(), + vec![ + PegSource::Value((1, 1)), // asset_1 + PegSource::Value((3, 1)), // asset_3 + PegSource::Value((5, 1)), // asset_5 + PegSource::Value((7, 1)), // asset_7 + PegSource::Value((9, 1)), // asset_9 + ], + "sources must be co-sorted; got {:?}", + peg_info.source.to_vec(), + ); + + assert_eq!( + peg_info.current.to_vec(), + vec![ + (1u128, 1u128), + (3u128, 1u128), + (5u128, 1u128), + (7u128, 1u128), + (9u128, 1u128), + ], + ); + }); +} + +/// GREEN – mixed PegSource types with unsorted assets. +/// +/// Ensures co-sorting works when the peg source slice contains a mix of +/// Value and Oracle variants (not all the same type). +#[test] +fn pool_pegs_should_be_cosorted_with_mixed_peg_source_types() { + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let asset_20: AssetId = 20; + let oracle_source = *b"testtest"; + let pool_id: PoolId = 100; + + ExtBuilder::default() + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("a20".as_bytes().to_vec(), asset_20, 12) + .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) + .build() + .execute_with(|| { + // Oracle entry for asset_10 priced via asset_5. + set_peg_oracle_value(asset_5, asset_10, (2, 1), 1); + // Oracle entry for asset_20 priced via asset_5. + set_peg_oracle_value(asset_5, asset_20, (3, 1), 1); + + // Unsorted input [10, 5, 20]; mixed peg source types. + let assets: BoundedVec = vec![asset_10, asset_5, asset_20].try_into().unwrap(); + let pegs: BoundedPegSources = BoundedVec::try_from(vec![ + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // for asset_10 + PegSource::Value((1, 1)), // for asset_5 + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // for asset_20 + ]) + .unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + assets, + 1000, + Permill::zero(), + pegs, + Perbill::from_percent(100), + )); + + let pool = Pools::::get(pool_id).unwrap(); + let peg_info = PoolPegs::::get(pool_id).unwrap(); + + // Sorted assets: [5, 10, 20]. + assert_eq!(pool.assets.to_vec(), vec![asset_5, asset_10, asset_20]); + + // Sources co-sorted: Value for asset_5, Oracle for asset_10, Oracle for asset_20. + assert_eq!( + peg_info.source.to_vec(), + vec![ + PegSource::Value((1, 1)), + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), + ], + "sources must be co-sorted; got {:?}", + peg_info.source.to_vec(), + ); + }); +} + +// ── Phase 2: Task 2.3 — PoolCreated event correctness ──────────────────────── + +/// GREEN – `PoolCreated` event must carry sorted assets and co-sorted peg info. +/// +/// After the fix the event should reflect the canonical sorted state stored +/// on-chain, not the raw (potentially unsorted) caller input. +#[test] +fn pool_created_event_should_contain_sorted_assets_and_cosorted_pegs() { + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let pool_id: PoolId = 100; + + ExtBuilder::default() + .with_registered_asset("a10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("a5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) + .build() + .execute_with(|| { + let unsorted_assets: BoundedVec = vec![asset_10, asset_5].try_into().unwrap(); + let unsorted_pegs: BoundedPegSources = + BoundedVec::try_from(vec![PegSource::Value((2, 1)), PegSource::Value((1, 1))]).unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + unsorted_assets, + 1000, + Permill::zero(), + unsorted_pegs, + Perbill::from_percent(100), + )); + + // The PoolCreated event must carry the sorted representation. + let expected_peg_info = PoolPegInfo { + source: BoundedVec::try_from(vec![ + PegSource::Value((1, 1)), // asset_5 + PegSource::Value((2, 1)), // asset_10 + ]) + .unwrap(), + updated_at: 1, + max_peg_update: Perbill::from_percent(100), + current: BoundedVec::try_from(vec![(1u128, 1u128), (2u128, 1u128)]).unwrap(), + }; + + expect_events(vec![Event::PoolCreated { + pool_id, + assets: vec![asset_5, asset_10], // sorted + amplification: 1000u16.try_into().unwrap(), + fee: Permill::zero(), + peg: Some(expected_peg_info), + } + .into()]); + }); +} + +// ── Phase 1: original (pre-fix) tests kept for reference ───────────────────── +// The tests below are the originally-submitted versions, preserved as-is with +// their known structural issues noted. They are superseded by the tests above +// but kept here so the Phase 1 "red" baseline is explicitly visible. + +/// ORIGINAL Task 1.5 — kept for historical reference. +/// Superseded by update_asset_peg_source_should_target_correct_asset_after_unsorted_creation above. +#[test] +fn update_asset_peg_source_should_target_correct_asset_after_unsorted_creation_original() { + let pool_id: PoolId = 100; + let amp = 1000; + + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let liquid = TVL / 2; + + ExtBuilder::default() + .with_endowed_accounts(vec![ + (ALICE, asset_10, liquid), + (ALICE, asset_5, liquid), + (ALICE, pool_id, TVL), + ]) + .with_registered_asset("asset10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("asset5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) + .build() + .execute_with(|| { + // Create pool with UNSORTED assets + let unsorted_assets: BoundedVec = vec![asset_10, asset_5].try_into().unwrap(); + let unsorted_pegs: BoundedPegSources = + BoundedVec::try_from(vec![PegSource::Value((2, 1)), PegSource::Value((1, 1))]).unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + unsorted_assets, + amp, + Permill::zero(), + unsorted_pegs, + Perbill::from_percent(100), + )); + + assert_ok!(Stableswap::add_assets_liquidity( + RuntimeOrigin::signed(ALICE), + pool_id, + BoundedVec::try_from(vec![ + AssetAmount::new(asset_10, liquid), + AssetAmount::new(asset_5, liquid), + ]) + .unwrap(), + Balance::zero(), + )); + + let new_peg = PegSource::Value((5, 1)); + assert_ok!(Stableswap::update_asset_peg_source( + RuntimeOrigin::root(), + pool_id, + asset_10, + new_peg.clone(), + )); + + let peg_info = PoolPegs::::get(pool_id).expect("Peg info should exist"); + + // asset_10 is at index 1 in sorted [5, 10]. + // With the fix, peg_info.source[1] == new_peg. + assert_eq!( + peg_info.source[1], new_peg, + "peg source for asset_10 (sorted index 1) must be updated; got {:?}", + peg_info.source[1], + ); + }); +} + +/// ORIGINAL Task 1.6 — kept for historical reference. +/// Superseded by remove_liquidity_should_use_correct_pegs_when_assets_unsorted above. +#[test] +fn remove_liquidity_should_use_correct_pegs_when_assets_unsorted_original() { + let pool_id: PoolId = 100; + let pool_id_2: PoolId = 101; + let amp = 1000; + + let asset_10: AssetId = 10; + let asset_5: AssetId = 5; + let liquid = TVL / 2; + let add_liq = 10_000 * ONE; + + ExtBuilder::default() + .with_endowed_accounts(vec![ + // Needs to fund both pools. + (ALICE, asset_10, liquid * 2), + (ALICE, asset_5, liquid * 2), + (ALICE, pool_id, TVL), + (ALICE, pool_id_2, TVL), + (BOB, asset_10, add_liq * 2), + ]) + .with_registered_asset("asset10".as_bytes().to_vec(), asset_10, 12) + .with_registered_asset("asset5".as_bytes().to_vec(), asset_5, 12) + .with_registered_asset("pool1".as_bytes().to_vec(), pool_id, 12) + .with_registered_asset("pool2".as_bytes().to_vec(), pool_id_2, 12) + .build() + .execute_with(|| { + // UNSORTED pool + let unsorted_assets: BoundedVec = vec![asset_10, asset_5].try_into().unwrap(); + let unsorted_pegs: BoundedPegSources = + BoundedVec::try_from(vec![PegSource::Value((2, 1)), PegSource::Value((1, 1))]).unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id, + unsorted_assets, + amp, + Permill::zero(), + unsorted_pegs, + Perbill::from_percent(100), + )); + + // Add initial liquidity + assert_ok!(Stableswap::add_assets_liquidity( + RuntimeOrigin::signed(ALICE), + pool_id, + BoundedVec::try_from(vec![ + AssetAmount::new(asset_10, liquid), + AssetAmount::new(asset_5, liquid), + ]) + .unwrap(), + Balance::zero(), + )); + + // BOB adds liquidity + assert_ok!(Stableswap::add_assets_liquidity( + RuntimeOrigin::signed(BOB), + pool_id, + BoundedVec::try_from(vec![AssetAmount::new(asset_10, add_liq)]).unwrap(), + Balance::zero(), + )); + + let shares = Tokens::free_balance(pool_id, &BOB); + let before = Tokens::free_balance(asset_5, &BOB); + assert_ok!(Stableswap::remove_liquidity_one_asset( + RuntimeOrigin::signed(BOB), + pool_id, + asset_5, + shares / 2, + 0, + )); + let received_unsorted = Tokens::free_balance(asset_5, &BOB) - before; + + // REFERENCE pool + let sorted_assets: BoundedVec = vec![asset_5, asset_10].try_into().unwrap(); + let sorted_pegs: BoundedPegSources = + BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((2, 1))]).unwrap(); + + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + pool_id_2, + sorted_assets, + amp, + Permill::zero(), + sorted_pegs, + Perbill::from_percent(100), + )); + + assert_ok!(Stableswap::add_assets_liquidity( + RuntimeOrigin::signed(ALICE), + pool_id_2, + BoundedVec::try_from(vec![ + AssetAmount::new(asset_10, liquid), + AssetAmount::new(asset_5, liquid), + ]) + .unwrap(), + Balance::zero(), + )); + + assert_ok!(Stableswap::add_assets_liquidity( + RuntimeOrigin::signed(BOB), + pool_id_2, + BoundedVec::try_from(vec![AssetAmount::new(asset_10, add_liq)]).unwrap(), + Balance::zero(), + )); + + let shares2 = Tokens::free_balance(pool_id_2, &BOB); + let before2 = Tokens::free_balance(asset_5, &BOB); + assert_ok!(Stableswap::remove_liquidity_one_asset( + RuntimeOrigin::signed(BOB), + pool_id_2, + asset_5, + shares2 / 2, + 0, + )); + let received_sorted = Tokens::free_balance(asset_5, &BOB) - before2; + + assert_eq!( + received_unsorted, received_sorted, + "Unsorted pool remove_liquidity_one_asset should match sorted pool. \ + Got unsorted={}, sorted={}.", + received_unsorted, received_sorted + ); + }); +} From 70e10efa496f907e8ed68f7a77578232b2172012 Mon Sep 17 00:00:00 2001 From: Khuzama Shahid Date: Thu, 16 Apr 2026 17:06:51 +0500 Subject: [PATCH 2/9] fix(stableswap): ensure assets and peg sources are co-sorted in pool creation --- pallets/stableswap/src/lib.rs | 33 ++- pallets/stableswap/src/tests/peg_ordering.rs | 288 ++++--------------- 2 files changed, 92 insertions(+), 229 deletions(-) diff --git a/pallets/stableswap/src/lib.rs b/pallets/stableswap/src/lib.rs index e46c25553..90b5b5b2d 100644 --- a/pallets/stableswap/src/lib.rs +++ b/pallets/stableswap/src/lib.rs @@ -1130,20 +1130,41 @@ pub mod pallet { let amplification = NonZeroU16::new(amplification).ok_or(Error::::InvalidAmplification)?; - let initial_pegs = Self::get_target_pegs(&assets, &peg_source)?; + ensure!(assets.len() == peg_source.len(), Error::::IncorrectInitialPegs); + + // Co-sort assets and peg_source by asset ID so that peg_source[i] always + // corresponds to sorted_assets[i], matching how do_create_pool stores them. + let mut pairs: Vec<_> = assets.iter().copied().zip(peg_source.into_iter()).collect(); + pairs.sort_by_key(|(asset, _)| *asset); + + let sorted_assets: BoundedVec> = pairs + .iter() + .map(|(a, _)| *a) + .collect::>() + .try_into() + .map_err(|_| Error::::MaxAssetsExceeded)?; + + let sorted_peg_source: BoundedPegSources = pairs + .into_iter() + .map(|(_, p)| p) + .collect::>() + .try_into() + .map_err(|_| Error::::MaxAssetsExceeded)?; + + let initial_pegs = Self::get_target_pegs(&sorted_assets, &sorted_peg_source)?; let peg_info = PoolPegInfo { - source: peg_source, + source: sorted_peg_source, updated_at: T::BlockNumberProvider::current_block_number(), max_peg_update, current: BoundedPegs::truncate_from(initial_pegs.into_iter().map(|(v, _)| v).collect()), }; - let pool_id = Self::do_create_pool(share_asset, &assets, amplification, fee, Some(&peg_info))?; + let pool_id = Self::do_create_pool(share_asset, &sorted_assets, amplification, fee, Some(&peg_info))?; Self::deposit_event(Event::PoolCreated { pool_id, - assets: assets.to_vec(), + assets: sorted_assets.to_vec(), amplification, fee, peg: Some(peg_info), @@ -2192,6 +2213,10 @@ impl Pallet { peg_sources.len(), "Pool assets and peg sources must have the same length" ); + debug_assert!( + pool_assets.windows(2).all(|w| w[0] <= w[1]), + "pool_assets must be sorted ascending" + ); if pool_assets.is_empty() { // Should never happen diff --git a/pallets/stableswap/src/tests/peg_ordering.rs b/pallets/stableswap/src/tests/peg_ordering.rs index 16bbbbfb1..e36f88a21 100644 --- a/pallets/stableswap/src/tests/peg_ordering.rs +++ b/pallets/stableswap/src/tests/peg_ordering.rs @@ -76,8 +76,7 @@ fn pool_pegs_should_be_cosorted_with_assets() { .execute_with(|| { // Caller intent: asset_10 → (2,1), asset_5 → (1,1), asset_20 → (3,1). // Input order: [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. - let unsorted_assets: BoundedVec = - vec![asset_10, asset_5, asset_20].try_into().unwrap(); + let unsorted_assets: BoundedVec = vec![asset_10, asset_5, asset_20].try_into().unwrap(); let unsorted_pegs: BoundedPegSources = BoundedVec::try_from(vec![ PegSource::Value((2, 1)), PegSource::Value((1, 1)), @@ -229,10 +228,21 @@ fn sell_should_use_correct_pegs_when_assets_are_unsorted() { // Unsorted pool: input [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. // Intent: asset_10 → (2,1), asset_5 → (1,1), asset_20 → (3,1). make_peg_pool(pool_id, vec![asset_10, asset_5, asset_20], vec![(2, 1), (1, 1), (3, 1)]); - add_liquidity(pool_id, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity( + pool_id, + ALICE, + vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)], + ); let bal_before = Tokens::free_balance(asset_5, &BOB); - assert_ok!(Stableswap::sell(RuntimeOrigin::signed(BOB), pool_id, asset_10, asset_5, trade, 0)); + assert_ok!(Stableswap::sell( + RuntimeOrigin::signed(BOB), + pool_id, + asset_10, + asset_5, + trade, + 0 + )); let received_unsorted = Tokens::free_balance(asset_5, &BOB) - bal_before; // Reference pool: sorted [5, 10, 20] with co-sorted pegs [(1,1), (2,1), (3,1)]. @@ -241,7 +251,11 @@ fn sell_should_use_correct_pegs_when_assets_are_unsorted() { vec![asset_5, asset_10, asset_20], vec![(1, 1), (2, 1), (3, 1)], ); - add_liquidity(pool_id_ref, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity( + pool_id_ref, + ALICE, + vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)], + ); let bal_before2 = Tokens::free_balance(asset_5, &BOB); assert_ok!(Stableswap::sell( @@ -293,7 +307,11 @@ fn buy_should_use_correct_pegs_when_assets_are_unsorted() { .execute_with(|| { // Unsorted pool: input [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. make_peg_pool(pool_id, vec![asset_10, asset_5, asset_20], vec![(2, 1), (1, 1), (3, 1)]); - add_liquidity(pool_id, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity( + pool_id, + ALICE, + vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)], + ); let bal_before = Tokens::free_balance(asset_10, &BOB); assert_ok!(Stableswap::buy( @@ -312,7 +330,11 @@ fn buy_should_use_correct_pegs_when_assets_are_unsorted() { vec![asset_5, asset_10, asset_20], vec![(1, 1), (2, 1), (3, 1)], ); - add_liquidity(pool_id_ref, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity( + pool_id_ref, + ALICE, + vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)], + ); let bal_before2 = Tokens::free_balance(asset_10, &BOB); assert_ok!(Stableswap::buy( @@ -384,11 +406,10 @@ fn sell_should_use_correct_oracle_pegs_when_assets_are_unsorted() { // Intent: asset_10 → Oracle(2,1), asset_5 → Value(1,1), asset_20 → Oracle(3,1). // Input order: [10, 5, 20]. Bug stores source as-is, so sorted pool // maps: asset_5 → Oracle (wrong), asset_10 → Value (wrong), asset_20 → Oracle (correct by coincidence). - let unsorted_assets: BoundedVec = - vec![asset_10, asset_5, asset_20].try_into().unwrap(); + let unsorted_assets: BoundedVec = vec![asset_10, asset_5, asset_20].try_into().unwrap(); let unsorted_pegs: BoundedPegSources = BoundedVec::try_from(vec![ PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // intended for asset_10 - PegSource::Value((1, 1)), // intended for asset_5 + PegSource::Value((1, 1)), // intended for asset_5 PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // intended for asset_20 ]) .unwrap(); @@ -402,16 +423,26 @@ fn sell_should_use_correct_oracle_pegs_when_assets_are_unsorted() { unsorted_pegs, Perbill::from_percent(100), )); - add_liquidity(pool_id, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity( + pool_id, + ALICE, + vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)], + ); let bal_before = Tokens::free_balance(asset_5, &BOB); - assert_ok!(Stableswap::sell(RuntimeOrigin::signed(BOB), pool_id, asset_10, asset_5, trade, 0)); + assert_ok!(Stableswap::sell( + RuntimeOrigin::signed(BOB), + pool_id, + asset_10, + asset_5, + trade, + 0 + )); let received_unsorted = Tokens::free_balance(asset_5, &BOB) - bal_before; // ── Reference pool (sorted) ────────────────────────────────────────── // Sorted [5, 10, 20], pegs: [Value(1,1), Oracle(..), Oracle(..)]. - let sorted_assets: BoundedVec = - vec![asset_5, asset_10, asset_20].try_into().unwrap(); + let sorted_assets: BoundedVec = vec![asset_5, asset_10, asset_20].try_into().unwrap(); let sorted_pegs: BoundedPegSources = BoundedVec::try_from(vec![ PegSource::Value((1, 1)), PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), @@ -428,7 +459,11 @@ fn sell_should_use_correct_oracle_pegs_when_assets_are_unsorted() { sorted_pegs, Perbill::from_percent(100), )); - add_liquidity(pool_id_ref, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity( + pool_id_ref, + ALICE, + vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)], + ); let bal_before2 = Tokens::free_balance(asset_5, &BOB); assert_ok!(Stableswap::sell( @@ -551,7 +586,11 @@ fn remove_liquidity_should_use_correct_pegs_when_assets_unsorted() { .execute_with(|| { // Unsorted pool: input [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. make_peg_pool(pool_id, vec![asset_10, asset_5, asset_20], vec![(2, 1), (1, 1), (3, 1)]); - add_liquidity(pool_id, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity( + pool_id, + ALICE, + vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)], + ); add_liquidity(pool_id, BOB, vec![(asset_10, add_liq), (asset_5, 0), (asset_20, 0)]); let shares = Tokens::free_balance(pool_id, &BOB); @@ -571,7 +610,11 @@ fn remove_liquidity_should_use_correct_pegs_when_assets_unsorted() { vec![asset_5, asset_10, asset_20], vec![(1, 1), (2, 1), (3, 1)], ); - add_liquidity(pool_id_ref, ALICE, vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)]); + add_liquidity( + pool_id_ref, + ALICE, + vec![(asset_10, liquid), (asset_5, liquid), (asset_20, liquid)], + ); add_liquidity(pool_id_ref, BOB, vec![(asset_10, add_liq), (asset_5, 0), (asset_20, 0)]); let shares2 = Tokens::free_balance(pool_id_ref, &BOB); @@ -662,9 +705,7 @@ fn pool_pegs_should_be_cosorted_with_five_assets_random_order() { .build() .execute_with(|| { // Input order [5, 1, 9, 3, 7]; pegs paired to this order. - let assets: BoundedVec = vec![asset_5, asset_1, asset_9, asset_3, asset_7] - .try_into() - .unwrap(); + let assets: BoundedVec = vec![asset_5, asset_1, asset_9, asset_3, asset_7].try_into().unwrap(); let pegs: BoundedPegSources = BoundedVec::try_from(vec![ PegSource::Value((5, 1)), // for asset_5 PegSource::Value((1, 1)), // for asset_1 @@ -688,10 +729,7 @@ fn pool_pegs_should_be_cosorted_with_five_assets_random_order() { let peg_info = PoolPegs::::get(pool_id).unwrap(); // Sorted order: [1, 3, 5, 7, 9]. - assert_eq!( - pool.assets.to_vec(), - vec![asset_1, asset_3, asset_5, asset_7, asset_9], - ); + assert_eq!(pool.assets.to_vec(), vec![asset_1, asset_3, asset_5, asset_7, asset_9],); // Peg values must match sorted asset order. assert_eq!( @@ -748,7 +786,7 @@ fn pool_pegs_should_be_cosorted_with_mixed_peg_source_types() { let assets: BoundedVec = vec![asset_10, asset_5, asset_20].try_into().unwrap(); let pegs: BoundedPegSources = BoundedVec::try_from(vec![ PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // for asset_10 - PegSource::Value((1, 1)), // for asset_5 + PegSource::Value((1, 1)), // for asset_5 PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // for asset_20 ]) .unwrap(); @@ -837,203 +875,3 @@ fn pool_created_event_should_contain_sorted_assets_and_cosorted_pegs() { .into()]); }); } - -// ── Phase 1: original (pre-fix) tests kept for reference ───────────────────── -// The tests below are the originally-submitted versions, preserved as-is with -// their known structural issues noted. They are superseded by the tests above -// but kept here so the Phase 1 "red" baseline is explicitly visible. - -/// ORIGINAL Task 1.5 — kept for historical reference. -/// Superseded by update_asset_peg_source_should_target_correct_asset_after_unsorted_creation above. -#[test] -fn update_asset_peg_source_should_target_correct_asset_after_unsorted_creation_original() { - let pool_id: PoolId = 100; - let amp = 1000; - - let asset_10: AssetId = 10; - let asset_5: AssetId = 5; - let liquid = TVL / 2; - - ExtBuilder::default() - .with_endowed_accounts(vec![ - (ALICE, asset_10, liquid), - (ALICE, asset_5, liquid), - (ALICE, pool_id, TVL), - ]) - .with_registered_asset("asset10".as_bytes().to_vec(), asset_10, 12) - .with_registered_asset("asset5".as_bytes().to_vec(), asset_5, 12) - .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) - .build() - .execute_with(|| { - // Create pool with UNSORTED assets - let unsorted_assets: BoundedVec = vec![asset_10, asset_5].try_into().unwrap(); - let unsorted_pegs: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((2, 1)), PegSource::Value((1, 1))]).unwrap(); - - assert_ok!(Stableswap::create_pool_with_pegs( - RuntimeOrigin::root(), - pool_id, - unsorted_assets, - amp, - Permill::zero(), - unsorted_pegs, - Perbill::from_percent(100), - )); - - assert_ok!(Stableswap::add_assets_liquidity( - RuntimeOrigin::signed(ALICE), - pool_id, - BoundedVec::try_from(vec![ - AssetAmount::new(asset_10, liquid), - AssetAmount::new(asset_5, liquid), - ]) - .unwrap(), - Balance::zero(), - )); - - let new_peg = PegSource::Value((5, 1)); - assert_ok!(Stableswap::update_asset_peg_source( - RuntimeOrigin::root(), - pool_id, - asset_10, - new_peg.clone(), - )); - - let peg_info = PoolPegs::::get(pool_id).expect("Peg info should exist"); - - // asset_10 is at index 1 in sorted [5, 10]. - // With the fix, peg_info.source[1] == new_peg. - assert_eq!( - peg_info.source[1], new_peg, - "peg source for asset_10 (sorted index 1) must be updated; got {:?}", - peg_info.source[1], - ); - }); -} - -/// ORIGINAL Task 1.6 — kept for historical reference. -/// Superseded by remove_liquidity_should_use_correct_pegs_when_assets_unsorted above. -#[test] -fn remove_liquidity_should_use_correct_pegs_when_assets_unsorted_original() { - let pool_id: PoolId = 100; - let pool_id_2: PoolId = 101; - let amp = 1000; - - let asset_10: AssetId = 10; - let asset_5: AssetId = 5; - let liquid = TVL / 2; - let add_liq = 10_000 * ONE; - - ExtBuilder::default() - .with_endowed_accounts(vec![ - // Needs to fund both pools. - (ALICE, asset_10, liquid * 2), - (ALICE, asset_5, liquid * 2), - (ALICE, pool_id, TVL), - (ALICE, pool_id_2, TVL), - (BOB, asset_10, add_liq * 2), - ]) - .with_registered_asset("asset10".as_bytes().to_vec(), asset_10, 12) - .with_registered_asset("asset5".as_bytes().to_vec(), asset_5, 12) - .with_registered_asset("pool1".as_bytes().to_vec(), pool_id, 12) - .with_registered_asset("pool2".as_bytes().to_vec(), pool_id_2, 12) - .build() - .execute_with(|| { - // UNSORTED pool - let unsorted_assets: BoundedVec = vec![asset_10, asset_5].try_into().unwrap(); - let unsorted_pegs: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((2, 1)), PegSource::Value((1, 1))]).unwrap(); - - assert_ok!(Stableswap::create_pool_with_pegs( - RuntimeOrigin::root(), - pool_id, - unsorted_assets, - amp, - Permill::zero(), - unsorted_pegs, - Perbill::from_percent(100), - )); - - // Add initial liquidity - assert_ok!(Stableswap::add_assets_liquidity( - RuntimeOrigin::signed(ALICE), - pool_id, - BoundedVec::try_from(vec![ - AssetAmount::new(asset_10, liquid), - AssetAmount::new(asset_5, liquid), - ]) - .unwrap(), - Balance::zero(), - )); - - // BOB adds liquidity - assert_ok!(Stableswap::add_assets_liquidity( - RuntimeOrigin::signed(BOB), - pool_id, - BoundedVec::try_from(vec![AssetAmount::new(asset_10, add_liq)]).unwrap(), - Balance::zero(), - )); - - let shares = Tokens::free_balance(pool_id, &BOB); - let before = Tokens::free_balance(asset_5, &BOB); - assert_ok!(Stableswap::remove_liquidity_one_asset( - RuntimeOrigin::signed(BOB), - pool_id, - asset_5, - shares / 2, - 0, - )); - let received_unsorted = Tokens::free_balance(asset_5, &BOB) - before; - - // REFERENCE pool - let sorted_assets: BoundedVec = vec![asset_5, asset_10].try_into().unwrap(); - let sorted_pegs: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((2, 1))]).unwrap(); - - assert_ok!(Stableswap::create_pool_with_pegs( - RuntimeOrigin::root(), - pool_id_2, - sorted_assets, - amp, - Permill::zero(), - sorted_pegs, - Perbill::from_percent(100), - )); - - assert_ok!(Stableswap::add_assets_liquidity( - RuntimeOrigin::signed(ALICE), - pool_id_2, - BoundedVec::try_from(vec![ - AssetAmount::new(asset_10, liquid), - AssetAmount::new(asset_5, liquid), - ]) - .unwrap(), - Balance::zero(), - )); - - assert_ok!(Stableswap::add_assets_liquidity( - RuntimeOrigin::signed(BOB), - pool_id_2, - BoundedVec::try_from(vec![AssetAmount::new(asset_10, add_liq)]).unwrap(), - Balance::zero(), - )); - - let shares2 = Tokens::free_balance(pool_id_2, &BOB); - let before2 = Tokens::free_balance(asset_5, &BOB); - assert_ok!(Stableswap::remove_liquidity_one_asset( - RuntimeOrigin::signed(BOB), - pool_id_2, - asset_5, - shares2 / 2, - 0, - )); - let received_sorted = Tokens::free_balance(asset_5, &BOB) - before2; - - assert_eq!( - received_unsorted, received_sorted, - "Unsorted pool remove_liquidity_one_asset should match sorted pool. \ - Got unsorted={}, sorted={}.", - received_unsorted, received_sorted - ); - }); -} From f69aa38d0e78b30a35767e8a2ed378e4b4afdd21 Mon Sep 17 00:00:00 2001 From: Khuzama Shahid Date: Fri, 17 Apr 2026 11:16:39 +0500 Subject: [PATCH 3/9] fix(stableswap)!: fix peg ordering by accepting and co-sorting asset-peg pairs --- integration-tests/src/hsm.rs | 79 ++--- .../src/omnipool_liquidity_mining.rs | 10 +- integration-tests/src/stableswap.rs | 29 +- pallets/hsm/src/tests/mock.rs | 10 +- pallets/stableswap/src/benchmarks.rs | 6 +- pallets/stableswap/src/lib.rs | 17 +- pallets/stableswap/src/tests/mock.rs | 6 +- pallets/stableswap/src/tests/peg.rs | 203 +++++++------ pallets/stableswap/src/tests/peg_one.rs | 47 +-- pallets/stableswap/src/tests/peg_ordering.rs | 155 +++++----- .../src/tests/pegs_with_different_decimals.rs | 271 ++++++++++-------- .../stableswap/src/tests/remove_liquidity.rs | 14 +- .../src/tests/update_max_peg_update.rs | 42 +-- .../stableswap/src/tests/update_peg_source.rs | 67 +++-- 14 files changed, 489 insertions(+), 467 deletions(-) diff --git a/integration-tests/src/hsm.rs b/integration-tests/src/hsm.rs index 1935368ab..1ed29bea5 100644 --- a/integration-tests/src/hsm.rs +++ b/integration-tests/src/hsm.rs @@ -22,7 +22,6 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use orml_traits::MultiCurrency; use pallet_asset_registry::AssetType; use pallet_ema_oracle::BIFROST_SOURCE; -use pallet_stableswap::types::BoundedPegSources; use pallet_stableswap::types::PegSource; use pretty_assertions::assert_eq; use primitives::EvmAddress; @@ -638,10 +637,12 @@ fn buy_hollar_with_yield_bearing_token_should_work() { let alice_hollar_balance = balance_of(alice_evm_address); assert_eq!(alice_hollar_balance, U256::from(1_000_000_000_000_000_000_000u128)); - let assets = vec![HOLLAR, COLLATERAL]; - let pegs = vec![ - PegSource::Value((1, 1)), // aDOT peg - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), // vDOT peg + let assets = vec![ + (HOLLAR, PegSource::Value((1, 1))), + ( + COLLATERAL, + PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), + ), ]; assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), @@ -649,7 +650,6 @@ fn buy_hollar_with_yield_bearing_token_should_work() { BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); @@ -747,10 +747,12 @@ fn sell_yield_bearing_token_to_get_hollar_should_work() { let alice_hollar_balance = balance_of(alice_evm_address); assert_eq!(alice_hollar_balance, U256::from(1_000_000_000_000_000_000_000u128)); - let assets = vec![HOLLAR, COLLATERAL]; - let pegs = vec![ - PegSource::Value((1, 1)), // aDOT peg - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), // vDOT peg + let assets = vec![ + (HOLLAR, PegSource::Value((1, 1))), + ( + COLLATERAL, + PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), + ), ]; assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), @@ -758,7 +760,6 @@ fn sell_yield_bearing_token_to_get_hollar_should_work() { BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); @@ -871,10 +872,12 @@ fn sell_collateral_to_get_hollar_via_router_should_work() { let alice_hollar_balance = balance_of(alice_evm_address); assert_eq!(alice_hollar_balance, U256::from(1_000_000_000_000_000_000_000u128)); - let assets = vec![HOLLAR, COLLATERAL]; - let pegs = vec![ - PegSource::Value((1, 1)), // aDOT peg - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), // vDOT peg + let assets = vec![ + (HOLLAR, PegSource::Value((1, 1))), + ( + COLLATERAL, + PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), + ), ]; assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), @@ -882,7 +885,6 @@ fn sell_collateral_to_get_hollar_via_router_should_work() { BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); @@ -1003,10 +1005,12 @@ fn sell_collateral_to_get_hollar_via_router_should_work_when_collateral_is_acqui let alice_hollar_balance = balance_of(alice_evm_address); assert_eq!(alice_hollar_balance, U256::from(1_000_000_000_000_000_000_000u128)); - let assets = vec![HOLLAR, COLLATERAL]; - let pegs = vec![ - PegSource::Value((1, 1)), // aDOT peg - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), // vDOT peg + let assets = vec![ + (HOLLAR, PegSource::Value((1, 1))), + ( + COLLATERAL, + PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), + ), ]; assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), @@ -1014,7 +1018,6 @@ fn sell_collateral_to_get_hollar_via_router_should_work_when_collateral_is_acqui BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); @@ -1342,10 +1345,12 @@ fn sell_hollar_to_get_yield_bearing_token_should_work() { let alice_hollar_balance = balance_of(alice_evm_address); assert_eq!(alice_hollar_balance, U256::from(1_000_000_000_000_000_000_000u128)); - let assets = vec![HOLLAR, COLLATERAL]; - let pegs = vec![ - PegSource::Value((1, 1)), // aDOT peg - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), // vDOT peg + let assets = vec![ + (HOLLAR, PegSource::Value((1, 1))), + ( + COLLATERAL, + PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), + ), ]; assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), @@ -1353,7 +1358,6 @@ fn sell_hollar_to_get_yield_bearing_token_should_work() { BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); @@ -1458,10 +1462,12 @@ fn buy_yield_bearing_token_with_hollar_should_work() { let alice_hollar_balance = balance_of(alice_evm_address); assert_eq!(alice_hollar_balance, U256::from(1_000_000_000_000_000_000_000u128)); - let assets = vec![HOLLAR, COLLATERAL]; - let pegs = vec![ - PegSource::Value((1, 1)), // aDOT peg - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), // vDOT peg + let assets = vec![ + (HOLLAR, PegSource::Value((1, 1))), + ( + COLLATERAL, + PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), + ), ]; assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), @@ -1469,7 +1475,6 @@ fn buy_yield_bearing_token_with_hollar_should_work() { BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); @@ -1574,18 +1579,20 @@ fn buy_collateral_with_hollar_via_router_should_work() { let alice_hollar_balance = balance_of(alice_evm_address); assert_eq!(alice_hollar_balance, U256::from(1_000_000_000_000_000_000_000u128)); - let assets = vec![HOLLAR, COLLATERAL]; - let pegs = vec![ - PegSource::Value((1, 1)), // aDOT peg - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), // vDOT peg + let assets = vec![ + (HOLLAR, PegSource::Value((1, 1))), + ( + COLLATERAL, + PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, HOLLAR)), + ), ]; + assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), POOL_ID, BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); diff --git a/integration-tests/src/omnipool_liquidity_mining.rs b/integration-tests/src/omnipool_liquidity_mining.rs index 121cc0e26..685012030 100644 --- a/integration-tests/src/omnipool_liquidity_mining.rs +++ b/integration-tests/src/omnipool_liquidity_mining.rs @@ -35,7 +35,6 @@ use hydradx_traits::Create; use orml_traits::MultiCurrency; use pallet_asset_registry::AssetType; use pallet_ema_oracle::BIFROST_SOURCE; -use pallet_stableswap::types::BoundedPegSources; use pallet_stableswap::types::PegSource; use pallet_stableswap::MAX_ASSETS_IN_POOL; use pretty_assertions::assert_eq; @@ -3400,18 +3399,17 @@ fn price_adjustment_adapter_should_use_routed_oracle() { FixedU128::from_rational(103158291366950047, 4566210555614178), ) .execute(|| { - let assets = vec![VDOT, ADOT]; - let pegs = vec![ - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT)), // vDOT peg - PegSource::Value((1, 1)), // aDOT peg + let assets = vec![ + (VDOT, PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT))), + (ADOT, PegSource::Value((1, 1))), ]; + assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), GIGADOT, BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); diff --git a/integration-tests/src/stableswap.rs b/integration-tests/src/stableswap.rs index 9dfaa0de7..52c8bb101 100644 --- a/integration-tests/src/stableswap.rs +++ b/integration-tests/src/stableswap.rs @@ -10,7 +10,6 @@ use orml_traits::MultiCurrency; use orml_traits::MultiReservableCurrency; use pallet_ema_oracle::BIFROST_SOURCE; use pallet_stableswap::traits::PegRawOracle; -use pallet_stableswap::types::BoundedPegSources; use pallet_stableswap::types::PegSource; use pretty_assertions::assert_eq; use primitives::{constants::time::SECS_PER_BLOCK, BlockNumber}; @@ -61,10 +60,9 @@ fn gigadot_pool_should_work() { .endow_account(ALICE.into(), VDOT, 1_000_000 * 10u128.pow(VDOT_DECIMALS as u32)) .endow_account(ALICE.into(), ADOT, 1_000_000 * 10u128.pow(ADOT_DECIMALS as u32)) .execute(|| { - let assets = vec![VDOT, ADOT]; - let pegs = vec![ - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT)), // vDOT peg - PegSource::Value((1, 1)), // aDOT peg + let assets = vec![ + (VDOT, PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT))), + (ADOT, PegSource::Value((1, 1))), ]; assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), @@ -72,7 +70,6 @@ fn gigadot_pool_should_work() { BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); @@ -217,18 +214,17 @@ mod circuit_breaker { .endow_account(ALICE.into(), VDOT, 1_000_000 * 10u128.pow(VDOT_DECIMALS as u32)) .endow_account(ALICE.into(), ADOT, 1_000_000 * 10u128.pow(ADOT_DECIMALS as u32)) .execute(|| { - let assets = vec![VDOT, ADOT]; - let pegs = vec![ - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT)), // vDOT peg - PegSource::Value((1, 1)), // aDOT peg + let assets = vec![ + (VDOT, PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT))), + (ADOT, PegSource::Value((1, 1))), ]; + assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), GIGADOT, BoundedVec::truncate_from(assets), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); @@ -287,18 +283,17 @@ fn pool_with_pegs_should_update_pegs_only_once_per_block() { .endow_account(ALICE.into(), ADOT, 1_000_000 * 10u128.pow(ADOT_DECIMALS as u32)) .execute(|| { let precission = FixedU128::from_inner(1_000); - let assets = vec![VDOT, ADOT]; - let pegs = vec![ - PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT)), // vDOT peg - PegSource::Value((1, 1)), // aDOT peg + let assets = vec![ + (VDOT, PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT))), + (ADOT, PegSource::Value((1, 1))), ]; + assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), GIGADOT, BoundedVec::truncate_from(assets), 100, - Permill::from_parts(600), //0.06% - BoundedPegSources::truncate_from(pegs), + Permill::from_parts(600), //0.06% Perbill::from_parts(1_000_000), //0.1% )); diff --git a/pallets/hsm/src/tests/mock.rs b/pallets/hsm/src/tests/mock.rs index bbeb7ddd2..d142a31b9 100644 --- a/pallets/hsm/src/tests/mock.rs +++ b/pallets/hsm/src/tests/mock.rs @@ -46,7 +46,7 @@ use hydradx_traits::{AccountIdFor, Liquidity, RawEntry, Volume}; use orml_traits::parameter_type_with_key; use orml_traits::MultiCurrencyExtended; use pallet_stableswap::traits::PegRawOracle; -use pallet_stableswap::types::{BoundedPegSources, PegSource}; +use pallet_stableswap::types::PegSource; use primitives::EvmAddress; use sp_core::{ByteArray, H256}; use sp_runtime::traits::{BlakeTwo256, BlockNumberProvider, Convert, IdentityLookup}; @@ -775,13 +775,17 @@ impl ExtBuilder { // Set up collaterals for (pool_id, assets, amplification, fee, pegs) in self.pools { + let asset_with_pegs = assets + .iter() + .zip(pegs.iter()) + .map(|(asset, peg)| (*asset, peg.clone())) + .collect::>(); Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - BoundedVec::try_from(assets).unwrap(), + BoundedVec::try_from(asset_with_pegs).unwrap(), amplification, fee, - BoundedPegSources::try_from(pegs).unwrap(), Perbill::from_percent(100), ) .unwrap(); diff --git a/pallets/stableswap/src/benchmarks.rs b/pallets/stableswap/src/benchmarks.rs index 9f87ca347..c7e00d60f 100644 --- a/pallets/stableswap/src/benchmarks.rs +++ b/pallets/stableswap/src/benchmarks.rs @@ -19,7 +19,6 @@ use super::*; -use crate::types::BoundedPegSources; use frame_benchmarking::account; use frame_benchmarking::benchmarks; use frame_support::traits::EnsureOrigin; @@ -92,10 +91,9 @@ where crate::Pallet::::create_pool_with_pegs( successful_origin, pool_id, - BoundedVec::truncate_from(asset_ids), + BoundedVec::truncate_from(asset_ids.into_iter().zip(peg_source.into_iter()).collect::>()), amplification, trade_fee, - BoundedPegSources::truncate_from(peg_source), Perbill::from_percent(100), ) .expect("Failed to create pool"); @@ -170,7 +168,7 @@ benchmarks! { let trade_fee = Permill::from_percent(1); let caller: T::AccountId = account("caller", 0, 1); let successful_origin = T::AuthorityOrigin::try_successful_origin().unwrap(); - }: _(successful_origin, pool_id.into(), BoundedVec::truncate_from(asset_ids), amplification, trade_fee, BoundedPegSources::truncate_from(peg_source), Perbill::from_percent(100)) + }: _(successful_origin, pool_id.into(), BoundedVec::truncate_from(asset_ids.into_iter().zip(peg_source.into_iter()).collect::>()), amplification, trade_fee, Perbill::from_percent(100)) verify { assert!(>::get::(pool_id.into()).is_some()); assert!(>::get::(pool_id.into()).is_some()); diff --git a/pallets/stableswap/src/lib.rs b/pallets/stableswap/src/lib.rs index 90b5b5b2d..e942368a8 100644 --- a/pallets/stableswap/src/lib.rs +++ b/pallets/stableswap/src/lib.rs @@ -429,6 +429,11 @@ pub mod pallet { let amplification = NonZeroU16::new(amplification).ok_or(Error::::InvalidAmplification)?; + let mut sorted_assets = assets.into_inner(); + sorted_assets.sort(); + let assets: BoundedVec> = + sorted_assets.try_into().map_err(|_| Error::::MaxAssetsExceeded)?; + let pool_id = Self::do_create_pool(share_asset, &assets, amplification, fee, None)?; Self::deposit_event(Event::PoolCreated { @@ -1120,21 +1125,17 @@ pub mod pallet { pub fn create_pool_with_pegs( origin: OriginFor, share_asset: T::AssetId, - assets: BoundedVec>, + assets: BoundedVec<(T::AssetId, PegSource), ConstU32>, amplification: u16, fee: Permill, - peg_source: BoundedPegSources, max_peg_update: Perbill, ) -> DispatchResult { T::AuthorityOrigin::ensure_origin(origin)?; let amplification = NonZeroU16::new(amplification).ok_or(Error::::InvalidAmplification)?; - ensure!(assets.len() == peg_source.len(), Error::::IncorrectInitialPegs); - // Co-sort assets and peg_source by asset ID so that peg_source[i] always - // corresponds to sorted_assets[i], matching how do_create_pool stores them. - let mut pairs: Vec<_> = assets.iter().copied().zip(peg_source.into_iter()).collect(); + let mut pairs: Vec<(T::AssetId, PegSource)> = assets.into_inner(); pairs.sort_by_key(|(asset, _)| *asset); let sorted_assets: BoundedVec> = pairs @@ -1449,6 +1450,7 @@ impl Pallet { .ok_or_else(|| ArithmeticError::Overflow.into()) } + /// Assets must be provided in ascending sort order by asset ID. #[require_transactional] fn do_create_pool( share_asset: T::AssetId, @@ -1467,8 +1469,7 @@ impl Pallet { let block_number = T::BlockNumberProvider::current_block_number(); - let mut pool_assets = assets.to_vec(); - pool_assets.sort(); + let pool_assets = assets.to_vec(); let pool = PoolInfo { assets: pool_assets diff --git a/pallets/stableswap/src/tests/mock.rs b/pallets/stableswap/src/tests/mock.rs index 2f92858a9..f2828e0ae 100644 --- a/pallets/stableswap/src/tests/mock.rs +++ b/pallets/stableswap/src/tests/mock.rs @@ -30,7 +30,6 @@ use crate as pallet_stableswap; use crate::Config; -use crate::types::BoundedPegSources; use crate::{PegRawOracle, PegSource, PegType}; use frame_support::__private::Get; use frame_support::traits::{Contains, Everything}; @@ -350,10 +349,11 @@ impl ExtBuilder { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - pool.assets.clone(), + BoundedVec::truncate_from( + pool.assets.iter().copied().zip(pegs.into_iter()).collect::>(), + ), pool.initial_amplification.get(), pool.fee, - BoundedPegSources::truncate_from(pegs), Perbill::from_percent(100), )); } else { diff --git a/pallets/stableswap/src/tests/peg.rs b/pallets/stableswap/src/tests/peg.rs index ee363444a..1ff0b87b5 100644 --- a/pallets/stableswap/src/tests/peg.rs +++ b/pallets/stableswap/src/tests/peg.rs @@ -2,11 +2,11 @@ use crate::assert_balance; use crate::tests::mock::*; -use crate::types::{BoundedPegSources, PegSource}; +use crate::types::PegSource; use frame_support::traits::Hooks; use hydradx_traits::stableswap::AssetAmount; -use crate::tests::{get_share_price, spot_price, to_bounded_asset_vec}; +use crate::tests::{get_share_price, spot_price}; use frame_support::{assert_ok, BoundedVec}; use hydradx_traits::OraclePeriod; use num_traits::One; @@ -54,14 +54,13 @@ fn sell_with_peg_should_work_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value(peg2)), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Value(peg2), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -132,14 +131,13 @@ fn buy_with_peg_should_work_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value(peg2)), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Value(peg2), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -213,14 +211,13 @@ fn sell_with_peg_with_fee_should_work_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value(peg2)), + (asset_c, PegSource::Value(peg3)), + ]), amp, trade_fee, - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Value(peg2), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -292,14 +289,13 @@ fn buy_with_peg_with_fee_should_work_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value(peg2)), + (asset_c, PegSource::Value(peg3)), + ]), amp, trade_fee, - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Value(peg2), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -372,14 +368,13 @@ fn sell_with_drifting_peg_should_work() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -455,14 +450,13 @@ fn sell_with_drifting_peg_should_not_exceed_max_peg_update() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -540,14 +534,13 @@ fn share_pries_should_be_correct_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -616,14 +609,13 @@ fn spot_prices_should_be_correct_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -705,14 +697,13 @@ fn add_liquidity_should_work_correctly_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -787,14 +778,13 @@ fn add_liquidity_shares_should_work_correctly_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -870,14 +860,13 @@ fn remove_liquidity_for_one_asset_should_work_correctly_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -965,14 +954,13 @@ fn remove_liquidity_given_asset_amount_should_work_correctly_with_different_pegs assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -1063,14 +1051,13 @@ fn remove_liquidity_uniform_should_work_correctly_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -1168,14 +1155,16 @@ fn asset_oracle_peg_should_work() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::Oracle((*b"testtest", OraclePeriod::Short, oracle_asset)) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, oracle_asset)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -1257,14 +1246,16 @@ fn pegs_should_not_change_when_multiple_trades_happen_in_same_block() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::Oracle((*b"testtest", OraclePeriod::Short, oracle_asset)) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, oracle_asset)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -1355,14 +1346,16 @@ fn trade_fee_should_not_change_when_multiple_trades_happen_in_same_block() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::Oracle((*b"testtest", OraclePeriod::Short, oracle_asset)) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, oracle_asset)), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -1454,14 +1447,16 @@ fn trade_fee_should_be_removed_on_finalize() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::Oracle((*b"testtest", OraclePeriod::Short, oracle_asset)) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, oracle_asset)), - PegSource::Value(peg3) - ]), max_peg_update, )); diff --git a/pallets/stableswap/src/tests/peg_one.rs b/pallets/stableswap/src/tests/peg_one.rs index 2a9d898e6..b36b3b834 100644 --- a/pallets/stableswap/src/tests/peg_one.rs +++ b/pallets/stableswap/src/tests/peg_one.rs @@ -1,11 +1,10 @@ #![allow(deprecated)] use crate::tests::mock::*; -use crate::types::{BoundedPegSources, PegSource}; +use crate::types::PegSource; use crate::{assert_balance, Error, Event}; use hydradx_traits::stableswap::AssetAmount; -use crate::tests::to_bounded_asset_vec; use frame_support::{assert_noop, assert_ok, BoundedVec}; use hydradx_traits::OraclePeriod; use pallet_broadcast::types::{Asset, Destination, Fee}; @@ -29,10 +28,12 @@ fn sell_with_peg_should_work_as_before_when_all_pegs_are_one() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))) + ]), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]), max_peg_update, )); @@ -82,10 +83,12 @@ fn buy_should_work_as_before_when_all_pegs_are_one() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))) + ]), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]), max_peg_update, )); @@ -167,14 +170,13 @@ fn remove_liquidity_with_peg_should_work_as_before_when_pegs_are_one() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + (asset_c, PegSource::Value((1, 1))), + ]), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Value((1, 1)), - PegSource::Value((1, 1)) - ]), max_peg_update, )); @@ -251,10 +253,12 @@ fn should_fail_when_called_by_invalid_origin() { Stableswap::create_pool_with_pegs( RuntimeOrigin::signed(BOB), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))) + ]), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]), max_peg_update ), BadOrigin @@ -280,10 +284,12 @@ fn should_fail_when_invalid_amplification_specified() { Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))) + ]), 0, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]), max_peg_update, ), Error::::InvalidAmplification @@ -309,13 +315,12 @@ fn should_fail_when_no_target_peg_oracle() { Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + ]), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - ]), max_peg_update, ), Error::::MissingTargetPegOracle diff --git a/pallets/stableswap/src/tests/peg_ordering.rs b/pallets/stableswap/src/tests/peg_ordering.rs index e36f88a21..51ff4d582 100644 --- a/pallets/stableswap/src/tests/peg_ordering.rs +++ b/pallets/stableswap/src/tests/peg_ordering.rs @@ -1,7 +1,7 @@ #![allow(deprecated)] use crate::tests::mock::*; -use crate::types::{BoundedPegSources, PegSource, PoolPegInfo}; +use crate::types::{PegSource, PoolPegInfo}; use crate::{Event, PoolPegs, Pools}; use frame_support::{assert_ok, BoundedVec}; use hydradx_traits::stableswap::AssetAmount; @@ -18,17 +18,20 @@ type PoolId = AssetId; /// Create a peg pool with `Value`-typed pegs. `assets` and `pegs` are /// positionally paired — any ordering of assets is accepted. fn make_peg_pool(pool_id: PoolId, assets: Vec, pegs: Vec<(u128, u128)>) { - let assets_bv: BoundedVec = assets.try_into().unwrap(); - let pegs_bv: BoundedPegSources = - BoundedVec::try_from(pegs.into_iter().map(PegSource::Value).collect::>()).unwrap(); + let assets_with_pegs: BoundedVec<(AssetId, PegSource), _> = BoundedVec::try_from( + assets + .into_iter() + .zip(pegs.into_iter().map(PegSource::Value)) + .collect::>(), + ) + .unwrap(); assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - assets_bv, + assets_with_pegs, 1000, Permill::zero(), - pegs_bv, Perbill::from_percent(100), )); } @@ -76,21 +79,17 @@ fn pool_pegs_should_be_cosorted_with_assets() { .execute_with(|| { // Caller intent: asset_10 → (2,1), asset_5 → (1,1), asset_20 → (3,1). // Input order: [10, 5, 20] with pegs [(2,1), (1,1), (3,1)]. - let unsorted_assets: BoundedVec = vec![asset_10, asset_5, asset_20].try_into().unwrap(); - let unsorted_pegs: BoundedPegSources = BoundedVec::try_from(vec![ - PegSource::Value((2, 1)), - PegSource::Value((1, 1)), - PegSource::Value((3, 1)), - ]) - .unwrap(); - assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - unsorted_assets, + BoundedVec::try_from(vec![ + (asset_10, PegSource::Value((2, 1))), + (asset_5, PegSource::Value((1, 1))), + (asset_20, PegSource::Value((3, 1))), + ]) + .unwrap(), 1000, Permill::zero(), - unsorted_pegs, Perbill::from_percent(100), )); @@ -149,21 +148,17 @@ fn pool_pegs_should_be_cosorted_with_three_assets_reverse_order() { .build() .execute_with(|| { // Reverse-sorted input. - let assets: BoundedVec = vec![asset_20, asset_10, asset_5].try_into().unwrap(); - let pegs: BoundedPegSources = BoundedVec::try_from(vec![ - PegSource::Value((3, 1)), // for asset_20 - PegSource::Value((2, 1)), // for asset_10 - PegSource::Value((1, 1)), // for asset_5 - ]) - .unwrap(); - assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - assets, + BoundedVec::try_from(vec![ + (asset_20, PegSource::Value((3, 1))), // for asset_20 + (asset_10, PegSource::Value((2, 1))), // for asset_10 + (asset_5, PegSource::Value((1, 1))), // for asset_5 + ]) + .unwrap(), 1000, Permill::zero(), - pegs, Perbill::from_percent(100), )); @@ -406,21 +401,23 @@ fn sell_should_use_correct_oracle_pegs_when_assets_are_unsorted() { // Intent: asset_10 → Oracle(2,1), asset_5 → Value(1,1), asset_20 → Oracle(3,1). // Input order: [10, 5, 20]. Bug stores source as-is, so sorted pool // maps: asset_5 → Oracle (wrong), asset_10 → Value (wrong), asset_20 → Oracle (correct by coincidence). - let unsorted_assets: BoundedVec = vec![asset_10, asset_5, asset_20].try_into().unwrap(); - let unsorted_pegs: BoundedPegSources = BoundedVec::try_from(vec![ - PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // intended for asset_10 - PegSource::Value((1, 1)), // intended for asset_5 - PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // intended for asset_20 - ]) - .unwrap(); - assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - unsorted_assets, + BoundedVec::try_from(vec![ + ( + asset_10, + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)) + ), // intended for asset_10 + (asset_5, PegSource::Value((1, 1))), // intended for asset_5 + ( + asset_20, + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)) + ), // intended for asset_20 + ]) + .unwrap(), 1000, Permill::zero(), - unsorted_pegs, Perbill::from_percent(100), )); add_liquidity( @@ -442,21 +439,23 @@ fn sell_should_use_correct_oracle_pegs_when_assets_are_unsorted() { // ── Reference pool (sorted) ────────────────────────────────────────── // Sorted [5, 10, 20], pegs: [Value(1,1), Oracle(..), Oracle(..)]. - let sorted_assets: BoundedVec = vec![asset_5, asset_10, asset_20].try_into().unwrap(); - let sorted_pegs: BoundedPegSources = BoundedVec::try_from(vec![ - PegSource::Value((1, 1)), - PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), - PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), - ]) - .unwrap(); - assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id_ref, - sorted_assets, + BoundedVec::try_from(vec![ + (asset_5, PegSource::Value((1, 1))), + ( + asset_10, + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)) + ), + ( + asset_20, + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)) + ), + ]) + .unwrap(), 1000, Permill::zero(), - sorted_pegs, Perbill::from_percent(100), )); add_liquidity( @@ -655,17 +654,16 @@ fn pool_pegs_are_correct_when_assets_provided_already_sorted() { .build() .execute_with(|| { // Already sorted: [5, 10]. - let assets: BoundedVec = vec![asset_5, asset_10].try_into().unwrap(); - let pegs: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((2, 1))]).unwrap(); - assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - assets, + BoundedVec::try_from(vec![ + (asset_5, PegSource::Value((1, 1))), + (asset_10, PegSource::Value((2, 1))) + ]) + .unwrap(), 1000, Permill::zero(), - pegs, Perbill::from_percent(100), )); @@ -705,23 +703,19 @@ fn pool_pegs_should_be_cosorted_with_five_assets_random_order() { .build() .execute_with(|| { // Input order [5, 1, 9, 3, 7]; pegs paired to this order. - let assets: BoundedVec = vec![asset_5, asset_1, asset_9, asset_3, asset_7].try_into().unwrap(); - let pegs: BoundedPegSources = BoundedVec::try_from(vec![ - PegSource::Value((5, 1)), // for asset_5 - PegSource::Value((1, 1)), // for asset_1 - PegSource::Value((9, 1)), // for asset_9 - PegSource::Value((3, 1)), // for asset_3 - PegSource::Value((7, 1)), // for asset_7 - ]) - .unwrap(); - assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - assets, + BoundedVec::try_from(vec![ + (asset_5, PegSource::Value((5, 1))), // for asset_5 + (asset_1, PegSource::Value((1, 1))), // for asset_1 + (asset_9, PegSource::Value((9, 1))), // for asset_9 + (asset_3, PegSource::Value((3, 1))), // for asset_3 + (asset_7, PegSource::Value((7, 1))), // for asset_7 + ]) + .unwrap(), 1000, Permill::zero(), - pegs, Perbill::from_percent(100), )); @@ -783,21 +777,23 @@ fn pool_pegs_should_be_cosorted_with_mixed_peg_source_types() { set_peg_oracle_value(asset_5, asset_20, (3, 1), 1); // Unsorted input [10, 5, 20]; mixed peg source types. - let assets: BoundedVec = vec![asset_10, asset_5, asset_20].try_into().unwrap(); - let pegs: BoundedPegSources = BoundedVec::try_from(vec![ - PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // for asset_10 - PegSource::Value((1, 1)), // for asset_5 - PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), // for asset_20 - ]) - .unwrap(); - assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - assets, + BoundedVec::try_from(vec![ + ( + asset_10, + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)) + ), // for asset_10 + (asset_5, PegSource::Value((1, 1))), // for asset_5 + ( + asset_20, + PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)) + ), // for asset_20 + ]) + .unwrap(), 1000, Permill::zero(), - pegs, Perbill::from_percent(100), )); @@ -839,17 +835,16 @@ fn pool_created_event_should_contain_sorted_assets_and_cosorted_pegs() { .with_registered_asset("pool".as_bytes().to_vec(), pool_id, 12) .build() .execute_with(|| { - let unsorted_assets: BoundedVec = vec![asset_10, asset_5].try_into().unwrap(); - let unsorted_pegs: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((2, 1)), PegSource::Value((1, 1))]).unwrap(); - assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - unsorted_assets, + BoundedVec::try_from(vec![ + (asset_10, PegSource::Value((2, 1))), + (asset_5, PegSource::Value((1, 1))), + ]) + .unwrap(), 1000, Permill::zero(), - unsorted_pegs, Perbill::from_percent(100), )); diff --git a/pallets/stableswap/src/tests/pegs_with_different_decimals.rs b/pallets/stableswap/src/tests/pegs_with_different_decimals.rs index 1f2f69e40..258fb6539 100644 --- a/pallets/stableswap/src/tests/pegs_with_different_decimals.rs +++ b/pallets/stableswap/src/tests/pegs_with_different_decimals.rs @@ -1,8 +1,7 @@ use crate::tests::mock::*; -use crate::tests::to_bounded_asset_vec; use crate::tests::{get_share_price, spot_price}; +use crate::types::PegSource; use crate::types::PoolInfo; -use crate::types::{BoundedPegSources, PegSource}; use crate::{assert_balance, to_precision, Error}; use frame_support::{assert_noop, assert_ok, BoundedVec}; use hex_literal::hex; @@ -37,14 +36,13 @@ fn creating_pool_should_work_when_all_sources_are_value_type() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + (asset_c, PegSource::Value((1, 1))), + ]), 2000, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Value((1, 1)), - PegSource::Value((1, 1)) - ]), max_peg_update, )); }); @@ -74,20 +72,28 @@ fn creating_pool_should_work_when_all_sources_are_mmoracle_type() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + ( + asset_a, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000000").as_slice() + )) + ), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000000").as_slice() + )) + ), + ( + asset_c, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000000").as_slice() + )) + ), + ]), 2000, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000000").as_slice() - )), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000000").as_slice() - )), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000000").as_slice() - )), - ]), max_peg_update, )); }); @@ -117,16 +123,18 @@ fn creating_pool_should_work_when_all_sources_are_value_or_mmoracle_type() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + ( + asset_a, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000000").as_slice() + )) + ), + (asset_b, PegSource::Value((1, 1))), + (asset_c, PegSource::Value((1, 1))), + ]), 2000, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000000").as_slice() - )), - PegSource::Value((1, 1)), - PegSource::Value((1, 1)), - ]), max_peg_update, )); }); @@ -158,16 +166,18 @@ fn creating_pool_should_fail_when_all_source_are_not_value_or_mmoracle_type() { Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + ( + asset_a, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000000").as_slice() + )) + ), + (asset_b, PegSource::Value((1, 1))), + (asset_c, PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a))), + ]), 2000, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000000").as_slice() - )), - PegSource::Value((1, 1)), - PegSource::Oracle((*b"testtest", OraclePeriod::Short, asset_a)), - ]), max_peg_update, ), Error::::IncorrectAssetDecimals @@ -200,15 +210,17 @@ fn add_initial_liquidity_should_work_when_pegs_are_same_value() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000000").as_slice() + )) + ), + ]), amplification, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000000").as_slice() - )), - ]), max_peg_update, )); @@ -552,16 +564,18 @@ fn add_liquidity_should_work_correctly_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000001").as_slice(), + )) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000001").as_slice(), - )), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -634,16 +648,18 @@ fn remove_liquidity_for_one_asset_should_work_correctly_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000001").as_slice(), + )) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000001").as_slice(), - )), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -719,16 +735,18 @@ fn remove_liquidity_given_asset_amount_should_work_correctly_with_different_pegs assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000001").as_slice(), + )) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000001").as_slice(), - )), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -806,16 +824,18 @@ fn remove_liquidity_uniform_should_work_correctly_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000001").as_slice(), + )) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000001").as_slice(), - )), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -898,16 +918,18 @@ fn sell_with_different_peg_should_work() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000001").as_slice(), + )) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000001").as_slice(), - )), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -970,16 +992,18 @@ fn share_pries_should_be_correct_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000001").as_slice(), + )) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000001").as_slice(), - )), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -1037,16 +1061,18 @@ fn spot_prices_should_be_correct_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000001").as_slice(), + )) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000001").as_slice(), - )), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -1117,16 +1143,18 @@ fn add_liquidity_shares_should_work_correctly_with_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000001").as_slice(), + )) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000001").as_slice(), - )), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -1190,14 +1218,13 @@ fn sell_with_peg_should_work_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value(peg2)), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Value(peg2), - PegSource::Value(peg3) - ]), max_peg_update, )); @@ -1258,16 +1285,18 @@ fn buy_with_peg_should_work_different_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + ( + asset_b, + PegSource::MMOracle(EvmAddress::from_slice( + hex!("0000000000000000000000000000000000000001").as_slice(), + )) + ), + (asset_c, PegSource::Value(peg3)), + ]), amp, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::MMOracle(EvmAddress::from_slice( - hex!("0000000000000000000000000000000000000001").as_slice(), - )), - PegSource::Value(peg3) - ]), max_peg_update, )); diff --git a/pallets/stableswap/src/tests/remove_liquidity.rs b/pallets/stableswap/src/tests/remove_liquidity.rs index 29d4ba0d0..fed8bebec 100644 --- a/pallets/stableswap/src/tests/remove_liquidity.rs +++ b/pallets/stableswap/src/tests/remove_liquidity.rs @@ -1,8 +1,7 @@ #![allow(deprecated)] use crate::tests::mock::*; -use crate::tests::to_bounded_asset_vec; -use crate::types::{BoundedPegSources, PegSource, PoolInfo}; +use crate::types::{PegSource, PoolInfo}; use crate::{assert_balance, Error, Event, PoolPegs, Pools}; use frame_support::traits::Contains; use frame_support::{assert_noop, assert_ok, BoundedVec}; @@ -1618,14 +1617,13 @@ fn remove_all_liquidity_should_correctly_destroy_pool_when_pool_has_pegs() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - to_bounded_asset_vec(vec![asset_a, asset_b, asset_c]), + BoundedVec::truncate_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value(peg2)), + (asset_c, PegSource::Value(peg3)), + ]), 100, Permill::from_percent(0), - BoundedPegSources::truncate_from(vec![ - PegSource::Value((1, 1)), - PegSource::Value(peg2), - PegSource::Value(peg3) - ]), max_peg_update, )); diff --git a/pallets/stableswap/src/tests/update_max_peg_update.rs b/pallets/stableswap/src/tests/update_max_peg_update.rs index bdcd4c145..03d3eb03c 100644 --- a/pallets/stableswap/src/tests/update_max_peg_update.rs +++ b/pallets/stableswap/src/tests/update_max_peg_update.rs @@ -1,5 +1,5 @@ use crate::tests::mock::*; -use crate::types::{BoundedPegSources, PegSource}; +use crate::types::PegSource; use crate::{Error, Event, PoolPegs}; use frame_support::{assert_noop, assert_ok, BoundedVec}; use sp_runtime::{Perbill, Permill}; @@ -13,9 +13,6 @@ fn update_pool_max_peg_update_should_work() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]).unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -30,10 +27,13 @@ fn update_pool_max_peg_update_should_work() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); @@ -128,9 +128,6 @@ fn update_pool_max_peg_update_should_fail_when_invalid_origin() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]).unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -146,10 +143,13 @@ fn update_pool_max_peg_update_should_fail_when_invalid_origin() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); @@ -172,9 +172,6 @@ fn update_pool_max_peg_update_should_allow_zero_percent() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]).unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -189,10 +186,13 @@ fn update_pool_max_peg_update_should_allow_zero_percent() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); @@ -228,9 +228,6 @@ fn update_pool_max_peg_update_should_allow_hundred_percent() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]).unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -245,10 +242,13 @@ fn update_pool_max_peg_update_should_allow_hundred_percent() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); diff --git a/pallets/stableswap/src/tests/update_peg_source.rs b/pallets/stableswap/src/tests/update_peg_source.rs index 8038ff308..1d7062900 100644 --- a/pallets/stableswap/src/tests/update_peg_source.rs +++ b/pallets/stableswap/src/tests/update_peg_source.rs @@ -1,5 +1,5 @@ use crate::tests::mock::*; -use crate::types::{BoundedPegSources, PegSource}; +use crate::types::PegSource; use crate::{Error, Event, PoolPegs}; use frame_support::{assert_noop, assert_ok, BoundedVec}; use hydradx_traits::OraclePeriod; @@ -14,9 +14,6 @@ fn update_asset_peg_source_should_work() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]).unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -30,10 +27,13 @@ fn update_asset_peg_source_should_work() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); @@ -139,9 +139,6 @@ fn update_asset_peg_source_should_fail_when_asset_not_in_pool() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]).unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -157,10 +154,13 @@ fn update_asset_peg_source_should_fail_when_asset_not_in_pool() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); @@ -187,9 +187,6 @@ fn update_asset_peg_source_should_fail_when_invalid_origin() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]).unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -204,10 +201,13 @@ fn update_asset_peg_source_should_fail_when_invalid_origin() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); @@ -230,9 +230,6 @@ fn update_asset_peg_source_should_work_with_oracle_source() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((1, 1))]).unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -246,10 +243,13 @@ fn update_asset_peg_source_should_work_with_oracle_source() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((1, 1))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); @@ -278,9 +278,6 @@ fn update_asset_peg_source_should_update_second_asset_correctly() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = - BoundedVec::try_from(vec![PegSource::Value((1, 1)), PegSource::Value((2, 2))]).unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -294,10 +291,13 @@ fn update_asset_peg_source_should_update_second_asset_correctly() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((2, 2))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); @@ -345,13 +345,6 @@ fn update_asset_peg_source_should_work_with_three_assets() { let amp = 1000; let fee = Permill::from_percent(1); - let peg_sources: BoundedPegSources = BoundedVec::try_from(vec![ - PegSource::Value((1, 1)), - PegSource::Value((2, 2)), - PegSource::Value((3, 3)), - ]) - .unwrap(); - ExtBuilder::default() .with_endowed_accounts(vec![ (ALICE, asset_a, 1_000_000 * ONE), @@ -367,10 +360,14 @@ fn update_asset_peg_source_should_work_with_three_assets() { assert_ok!(Stableswap::create_pool_with_pegs( RuntimeOrigin::root(), pool_id, - vec![asset_a, asset_b, asset_c].try_into().unwrap(), + BoundedVec::try_from(vec![ + (asset_a, PegSource::Value((1, 1))), + (asset_b, PegSource::Value((2, 2))), + (asset_c, PegSource::Value((3, 3))), + ]) + .unwrap(), amp, fee, - peg_sources, Perbill::from_percent(10), )); From 01f34d594b353cec5a0550f4d95530f60385fa09 Mon Sep 17 00:00:00 2001 From: Khuzama Shahid Date: Fri, 17 Apr 2026 15:22:16 +0500 Subject: [PATCH 4/9] test(stableswap): add regression test for peg co-sorting and bump the version --- Cargo.lock | 8 +- integration-tests/Cargo.toml | 2 +- integration-tests/src/stableswap.rs | 136 ++++++++++++++++++++++++++++ pallets/hsm/Cargo.toml | 2 +- pallets/hsm/src/benchmarks.rs | 12 +-- pallets/stableswap/Cargo.toml | 2 +- pallets/stableswap/src/lib.rs | 1 + runtime/hydradx/Cargo.toml | 2 +- runtime/hydradx/src/lib.rs | 2 +- 9 files changed, 152 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74c562c17..e3f145871 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6378,7 +6378,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "410.0.0" +version = "411.0.0" dependencies = [ "alloy-primitives 0.7.7", "alloy-sol-types 0.7.7", @@ -10729,7 +10729,7 @@ dependencies = [ [[package]] name = "pallet-hsm" -version = "1.7.0" +version = "1.7.1" dependencies = [ "ethabi", "evm", @@ -11878,7 +11878,7 @@ dependencies = [ [[package]] name = "pallet-stableswap" -version = "7.3.0" +version = "8.0.0" dependencies = [ "bitflags 1.3.2", "frame-benchmarking", @@ -15646,7 +15646,7 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "runtime-integration-tests" -version = "1.78.0" +version = "1.78.1" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 3a144e9d7..81428bce1 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.78.0" +version = "1.78.1" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" diff --git a/integration-tests/src/stableswap.rs b/integration-tests/src/stableswap.rs index 52c8bb101..79d68d9d8 100644 --- a/integration-tests/src/stableswap.rs +++ b/integration-tests/src/stableswap.rs @@ -120,6 +120,142 @@ fn gigadot_pool_should_work() { }); } +// Regression test: a pool created with assets in reverse order must behave identically +// to one created in sorted order. Both pools are created in the same test and their sell +// outputs are compared directly — no shared formula, no approximation tolerance on the +// equivalence check. +// +// Asset IDs: VDOT=2222, ADOT=2223 → sorted order is [VDOT, ADOT]. +// Reversed order: [ADOT, VDOT] — peg sources are supplied in caller order and must be +// co-sorted so that VDOT always gets the Oracle peg and ADOT always gets Value((1,1)). +#[test] +fn gigadot_pool_should_cosort_pegs_when_assets_provided_in_reverse_order() { + pub const GIGADOT2: AssetId = 70; // second pool share asset + + let dot_location: polkadot_xcm::v5::Location = polkadot_xcm::v5::Location::new( + 1, + [ + polkadot_xcm::v5::Junction::Parachain(1500), + polkadot_xcm::v5::Junction::GeneralIndex(0), + ], + ); + + let vdot_location: polkadot_xcm::v5::Location = polkadot_xcm::v5::Location::new( + 1, + [ + polkadot_xcm::v5::Junction::Parachain(1500), + polkadot_xcm::v5::Junction::GeneralIndex(1), + ], + ); + + let vdot_boxed = Box::new(vdot_location.clone().into_versioned()); + let dot_boxed = Box::new(dot_location.clone().into_versioned()); + + HydrationTestDriver::default() + .register_asset(DOT, b"myDOT", DOT_DECIMALS, Some(dot_location)) + .register_asset(VDOT, b"myvDOT", VDOT_DECIMALS, Some(vdot_location)) + .register_asset(ADOT, b"myaDOT", ADOT_DECIMALS, None) + .register_asset(GIGADOT, b"myGIGADOT", GIGADOT_DECIMALS, None) + .register_asset(GIGADOT2, b"myGIGADOT2", GIGADOT_DECIMALS, None) + .update_bifrost_oracle(dot_boxed, vdot_boxed, DOT_VDOT_PRICE) + .new_block() + .endow_account(ALICE.into(), VDOT, 1_000_000 * 10u128.pow(VDOT_DECIMALS as u32)) + .endow_account(ALICE.into(), ADOT, 1_000_000 * 10u128.pow(ADOT_DECIMALS as u32)) + .endow_account(BOB.into(), VDOT, 1_000_000 * 10u128.pow(VDOT_DECIMALS as u32)) + .endow_account(BOB.into(), ADOT, 1_000_000 * 10u128.pow(ADOT_DECIMALS as u32)) + .execute(|| { + let sell_amount = 10u128.pow(VDOT_DECIMALS as u32); + let initial_liquidity = 1_000 * sell_amount; + + // ── Pool A: sorted order [VDOT, ADOT] (reference) ──────────────────────────── + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + GIGADOT, + BoundedVec::truncate_from(vec![ + (VDOT, PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT))), + (ADOT, PegSource::Value((1, 1))), + ]), + 100, + Permill::from_percent(0), + Perbill::from_percent(100), + )); + assert_ok!(Stableswap::add_assets_liquidity( + RuntimeOrigin::signed(ALICE.into()), + GIGADOT, + BoundedVec::truncate_from(vec![ + AssetAmount::new(VDOT, initial_liquidity), + AssetAmount::new(ADOT, initial_liquidity), + ]), + 0, + )); + + let adot_before_a = Tokens::free_balance(ADOT, &ALICE.into()); + assert_ok!(Stableswap::sell( + RuntimeOrigin::signed(ALICE.into()), + GIGADOT, + VDOT, + ADOT, + sell_amount, + 0, + )); + let adot_received_sorted = Tokens::free_balance(ADOT, &ALICE.into()) - adot_before_a; + + // ── Pool B: reversed order [ADOT, VDOT] — pegs must be co-sorted ───────────── + assert_ok!(Stableswap::create_pool_with_pegs( + RuntimeOrigin::root(), + GIGADOT2, + BoundedVec::truncate_from(vec![ + (ADOT, PegSource::Value((1, 1))), + (VDOT, PegSource::Oracle((BIFROST_SOURCE, OraclePeriod::LastBlock, DOT))), + ]), + 100, + Permill::from_percent(0), + Perbill::from_percent(100), + )); + + // Verify storage: both pools must have the same sorted assets and co-sorted pegs. + let pool_a = pallet_stableswap::Pools::::get(GIGADOT).expect("pool A must exist"); + let pool_b = pallet_stableswap::Pools::::get(GIGADOT2).expect("pool B must exist"); + assert_eq!(pool_a.assets, pool_b.assets, "both pools must store assets in the same sorted order"); + + let pegs_a = pallet_stableswap::PoolPegs::::get(GIGADOT).expect("pegs A must exist"); + let pegs_b = pallet_stableswap::PoolPegs::::get(GIGADOT2).expect("pegs B must exist"); + assert_eq!( + pegs_a.source.to_vec(), + pegs_b.source.to_vec(), + "both pools must store peg sources in the same co-sorted order" + ); + + assert_ok!(Stableswap::add_assets_liquidity( + RuntimeOrigin::signed(BOB.into()), + GIGADOT2, + BoundedVec::truncate_from(vec![ + AssetAmount::new(VDOT, initial_liquidity), + AssetAmount::new(ADOT, initial_liquidity), + ]), + 0, + )); + + let adot_before_b = Tokens::free_balance(ADOT, &BOB.into()); + assert_ok!(Stableswap::sell( + RuntimeOrigin::signed(BOB.into()), + GIGADOT2, + VDOT, + ADOT, + sell_amount, + 0, + )); + let adot_received_reversed = Tokens::free_balance(ADOT, &BOB.into()) - adot_before_b; + + // Direct comparison: both pools must produce identical trade output. + assert_eq!( + adot_received_sorted, + adot_received_reversed, + "reversed-order pool must produce the same sell output as sorted-order pool" + ); + }); +} + #[test] fn peg_oracle_adapter_should_work_when_getting_price_from_mm_oracle() { TestNet::reset(); diff --git a/pallets/hsm/Cargo.toml b/pallets/hsm/Cargo.toml index 40d41754a..d4337f84d 100644 --- a/pallets/hsm/Cargo.toml +++ b/pallets/hsm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-hsm" -version = "1.7.0" +version = "1.7.1" edition = "2021" description = "Hollar stability module" authors = ["GalacticCouncil"] diff --git a/pallets/hsm/src/benchmarks.rs b/pallets/hsm/src/benchmarks.rs index ce475a7cc..b8d69399c 100644 --- a/pallets/hsm/src/benchmarks.rs +++ b/pallets/hsm/src/benchmarks.rs @@ -24,7 +24,7 @@ use hydra_dx_math::stableswap::types::AssetReserve; use hydradx_traits::router::{PoolType, TradeExecution}; use hydradx_traits::stableswap::AssetAmount; use hydradx_traits::OraclePeriod; -use pallet_stableswap::types::{BoundedPegSources, PegSource}; +use pallet_stableswap::types::PegSource; use pallet_stableswap::{BenchmarkHelper as HSMBenchmarkHelper, MAX_ASSETS_IN_POOL}; use sp_runtime::traits::BlockNumberProvider; use sp_runtime::{FixedU128, Perbill, Permill}; @@ -462,11 +462,12 @@ where ::AccountId: AsRef<[u8; 32]> + IsType, { seed_asset::(pool_id, DECIMALS)?; - let mut assets = vec![hollar_id]; + let mut assets_with_pegs: Vec<(T::AssetId, PegSource)> = + vec![(hollar_id, PegSource::Value((1, 1)))]; let mut initial_liquidity = vec![INITIAL_LIQUIDITY * ONE]; - let mut pegs = vec![PegSource::Value((1, 1))]; + let mut assets = vec![hollar_id]; for idx in 0..MAX_ASSETS_IN_POOL - 1 { let asset_id: T::AssetId = (idx + offset).into(); seed_asset::(asset_id, DECIMALS)?; @@ -477,7 +478,7 @@ where *b"bifrosto", )?; let source = PegSource::Oracle((*b"bifrosto", OraclePeriod::LastBlock, hollar_id)); - pegs.push(source); + assets_with_pegs.push((asset_id, source)); initial_liquidity.push(INITIAL_LIQUIDITY * ONE - 50 * ONE); } @@ -490,10 +491,9 @@ where pallet_stableswap::Pallet::::create_pool_with_pegs( successful_origin, pool_id, - BoundedVec::try_from(assets.clone()).unwrap(), + BoundedVec::try_from(assets_with_pegs).unwrap(), amplification, fee, - BoundedPegSources::try_from(pegs).unwrap(), Perbill::from_percent(100), )?; diff --git a/pallets/stableswap/Cargo.toml b/pallets/stableswap/Cargo.toml index 8de5ff294..9e16a3848 100644 --- a/pallets/stableswap/Cargo.toml +++ b/pallets/stableswap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-stableswap" -version = "7.3.0" +version = "8.0.0" description = "AMM for correlated assets" authors = ["GalacticCouncil"] edition = "2021" diff --git a/pallets/stableswap/src/lib.rs b/pallets/stableswap/src/lib.rs index e942368a8..a8e016efd 100644 --- a/pallets/stableswap/src/lib.rs +++ b/pallets/stableswap/src/lib.rs @@ -1135,6 +1135,7 @@ pub mod pallet { let amplification = NonZeroU16::new(amplification).ok_or(Error::::InvalidAmplification)?; // Co-sort assets and peg_source by asset ID so that peg_source[i] always + // corresponds to sorted_assets[i], matching how do_create_pool stores them. let mut pairs: Vec<(T::AssetId, PegSource)> = assets.into_inner(); pairs.sort_by_key(|(asset, _)| *asset); diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 00a0ad6f9..221ce37d1 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "410.0.0" +version = "411.0.0" authors = ["GalacticCouncil"] edition = "2021" license = "Apache 2.0" diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index cfa293f7f..ff49a52b2 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -129,7 +129,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: Cow::Borrowed("hydradx"), impl_name: Cow::Borrowed("hydradx"), authoring_version: 1, - spec_version: 410, + spec_version: 411, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From d9cb0c94c202a1a6f36f5071ff66d08447e94350 Mon Sep 17 00:00:00 2001 From: Khuzama Shahid Date: Fri, 17 Apr 2026 15:34:08 +0500 Subject: [PATCH 5/9] docs(stableswap): update documentation for asset-peg pairing and sorting --- pallets/stableswap/README.md | 5 +++-- pallets/stableswap/src/lib.rs | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pallets/stableswap/README.md b/pallets/stableswap/README.md index 04d2bad4f..b51187206 100644 --- a/pallets/stableswap/README.md +++ b/pallets/stableswap/README.md @@ -8,8 +8,9 @@ Curve style AMM at is designed to provide highly efficient and low-slippage trad ### Drifting peg It is possible to create a pool with so called drifting peg. -Source of target peg for each asset must be provided. Either constant value or external oracle. -First asset in the pool is considered as a base asset and all other assets are pegged to it. Therefore peg of the first asset must be 1. +Each asset must be paired with its peg source as an `(asset_id, peg_source)` tuple. The peg source is either a constant value or an external oracle. +Assets may be supplied in any order — pairs are co-sorted by asset ID internally before the pool is stored. +First asset in the pool (after sorting) is considered as a base asset and all other assets are pegged to it. Therefore peg of the first asset must be 1. ### Stableswap Hooks diff --git a/pallets/stableswap/src/lib.rs b/pallets/stableswap/src/lib.rs index a8e016efd..cc815bae2 100644 --- a/pallets/stableswap/src/lib.rs +++ b/pallets/stableswap/src/lib.rs @@ -1094,15 +1094,17 @@ pub mod pallet { /// /// This function allows the creation of a new stable pool with specified assets, amplification, fee, and peg sources. The pool is identified by a share asset. /// - /// Peg target price is determined by retrieving the target peg from the oracle - it is the price of the asset from the peg sourcedenominated in the other pool assets. + /// Peg target price is determined by retrieving the target peg from the oracle - it is the price of the asset from the peg source denominated in the other pool assets. + /// + /// Assets may be provided in any order — each asset is paired with its peg source as a tuple + /// and the pairs are co-sorted by asset ID before the pool is created. /// /// Parameters: /// - `origin`: Must be `T::AuthorityOrigin`. /// - `share_asset`: Preregistered share asset identifier. - /// - `assets`: List of asset IDs to be included in the pool. + /// - `assets`: List of `(asset_id, peg_source)` pairs to be included in the pool. /// - `amplification`: Pool amplification parameter. /// - `fee`: Fee to be applied on trade and liquidity operations. - /// - `peg_source`: Bounded vector specifying the source of the peg for each asset. /// - `max_peg_update`: Maximum allowed peg update per block. /// /// Emits `PoolCreated` event if successful. @@ -1115,7 +1117,6 @@ pub mod pallet { /// - `ShareAssetInPoolAssets`: If the share asset is among the pool assets. /// - `AssetNotRegistered`: If one or more assets are not registered in the AssetRegistry. /// - `InvalidAmplification`: If the amplification parameter is invalid. - /// - `IncorrectInitialPegs`: If the initial pegs are incorrect. /// - `MissingTargetPegOracle`: If the target peg oracle entry is missing. /// - `IncorrectAssetDecimals`: If the assets have different decimals. /// From 033c7693d678d260d1571c50b6c47082bbae1cde Mon Sep 17 00:00:00 2001 From: Khuzama Shahid <44345364+khuzama98@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:43:00 +0500 Subject: [PATCH 6/9] Update pallets/stableswap/src/tests/peg_ordering.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pallets/stableswap/src/tests/peg_ordering.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/stableswap/src/tests/peg_ordering.rs b/pallets/stableswap/src/tests/peg_ordering.rs index 51ff4d582..d7118461d 100644 --- a/pallets/stableswap/src/tests/peg_ordering.rs +++ b/pallets/stableswap/src/tests/peg_ordering.rs @@ -106,7 +106,7 @@ fn pool_pegs_should_be_cosorted_with_assets() { // Peg sources must be co-sorted to match sorted assets [5, 10, 20]: // asset_5 → (1,1), asset_10 → (2,1), asset_20 → (3,1). - // BUG: currently stored as input order [(2,1), (1,1), (3,1)]. + // Previously, peg sources were stored in input order [(2,1), (1,1), (3,1)]. assert_eq!( peg_info.source.to_vec(), vec![ From 2657587c577820b1c20eb9fe300ecd10c2ded08f Mon Sep 17 00:00:00 2001 From: Khuzama Shahid Date: Fri, 17 Apr 2026 15:47:44 +0500 Subject: [PATCH 7/9] format(stableswap): tests and hsm benchmarks formatted --- integration-tests/src/stableswap.rs | 8 +++++--- pallets/hsm/src/benchmarks.rs | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/integration-tests/src/stableswap.rs b/integration-tests/src/stableswap.rs index 79d68d9d8..5ce2f316c 100644 --- a/integration-tests/src/stableswap.rs +++ b/integration-tests/src/stableswap.rs @@ -216,7 +216,10 @@ fn gigadot_pool_should_cosort_pegs_when_assets_provided_in_reverse_order() { // Verify storage: both pools must have the same sorted assets and co-sorted pegs. let pool_a = pallet_stableswap::Pools::::get(GIGADOT).expect("pool A must exist"); let pool_b = pallet_stableswap::Pools::::get(GIGADOT2).expect("pool B must exist"); - assert_eq!(pool_a.assets, pool_b.assets, "both pools must store assets in the same sorted order"); + assert_eq!( + pool_a.assets, pool_b.assets, + "both pools must store assets in the same sorted order" + ); let pegs_a = pallet_stableswap::PoolPegs::::get(GIGADOT).expect("pegs A must exist"); let pegs_b = pallet_stableswap::PoolPegs::::get(GIGADOT2).expect("pegs B must exist"); @@ -249,8 +252,7 @@ fn gigadot_pool_should_cosort_pegs_when_assets_provided_in_reverse_order() { // Direct comparison: both pools must produce identical trade output. assert_eq!( - adot_received_sorted, - adot_received_reversed, + adot_received_sorted, adot_received_reversed, "reversed-order pool must produce the same sell output as sorted-order pool" ); }); diff --git a/pallets/hsm/src/benchmarks.rs b/pallets/hsm/src/benchmarks.rs index b8d69399c..3762ca71f 100644 --- a/pallets/hsm/src/benchmarks.rs +++ b/pallets/hsm/src/benchmarks.rs @@ -462,8 +462,7 @@ where ::AccountId: AsRef<[u8; 32]> + IsType, { seed_asset::(pool_id, DECIMALS)?; - let mut assets_with_pegs: Vec<(T::AssetId, PegSource)> = - vec![(hollar_id, PegSource::Value((1, 1)))]; + let mut assets_with_pegs: Vec<(T::AssetId, PegSource)> = vec![(hollar_id, PegSource::Value((1, 1)))]; let mut initial_liquidity = vec![INITIAL_LIQUIDITY * ONE]; From 9697cd91a7e4e7b7b59799dcc4cdb23ebe3644cd Mon Sep 17 00:00:00 2001 From: Khuzama Shahid Date: Fri, 17 Apr 2026 16:38:19 +0500 Subject: [PATCH 8/9] refactor(stableswap): optimize test assertions and benchmark iterators --- pallets/stableswap/src/benchmarks.rs | 2 +- pallets/stableswap/src/tests/peg_ordering.rs | 48 +++++++++----------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/pallets/stableswap/src/benchmarks.rs b/pallets/stableswap/src/benchmarks.rs index c7e00d60f..513c3e086 100644 --- a/pallets/stableswap/src/benchmarks.rs +++ b/pallets/stableswap/src/benchmarks.rs @@ -91,7 +91,7 @@ where crate::Pallet::::create_pool_with_pegs( successful_origin, pool_id, - BoundedVec::truncate_from(asset_ids.into_iter().zip(peg_source.into_iter()).collect::>()), + BoundedVec::truncate_from(asset_ids.into_iter().zip(peg_source).collect::>()), amplification, trade_fee, Perbill::from_percent(100), diff --git a/pallets/stableswap/src/tests/peg_ordering.rs b/pallets/stableswap/src/tests/peg_ordering.rs index d7118461d..4def4a2bf 100644 --- a/pallets/stableswap/src/tests/peg_ordering.rs +++ b/pallets/stableswap/src/tests/peg_ordering.rs @@ -108,23 +108,23 @@ fn pool_pegs_should_be_cosorted_with_assets() { // asset_5 → (1,1), asset_10 → (2,1), asset_20 → (3,1). // Previously, peg sources were stored in input order [(2,1), (1,1), (3,1)]. assert_eq!( - peg_info.source.to_vec(), - vec![ + peg_info.source.as_slice(), + [ PegSource::Value((1, 1)), PegSource::Value((2, 1)), PegSource::Value((3, 1)), ], "peg sources must be co-sorted with pool assets; got {:?}", - peg_info.source.to_vec(), + peg_info.source, ); // PoolPegs.current holds resolved peg values; must also be co-sorted. // PegSource::Value is resolved verbatim, so current[i] == source[i] value. assert_eq!( - peg_info.current.to_vec(), - vec![(1u128, 1u128), (2u128, 1u128), (3u128, 1u128)], + peg_info.current.as_slice(), + [(1u128, 1u128), (2u128, 1u128), (3u128, 1u128)], "current pegs must be co-sorted with pool assets; got {:?}", - peg_info.current.to_vec(), + peg_info.current, ); }); } @@ -168,21 +168,21 @@ fn pool_pegs_should_be_cosorted_with_three_assets_reverse_order() { assert_eq!(pool.assets.to_vec(), vec![asset_5, asset_10, asset_20]); assert_eq!( - peg_info.source.to_vec(), - vec![ + peg_info.source.as_slice(), + [ PegSource::Value((1, 1)), // asset_5 PegSource::Value((2, 1)), // asset_10 PegSource::Value((3, 1)), // asset_20 ], "sources must be co-sorted; got {:?}", - peg_info.source.to_vec(), + peg_info.source, ); assert_eq!( - peg_info.current.to_vec(), - vec![(1u128, 1u128), (2u128, 1u128), (3u128, 1u128)], + peg_info.current.as_slice(), + [(1u128, 1u128), (2u128, 1u128), (3u128, 1u128)], "current pegs must be co-sorted; got {:?}", - peg_info.current.to_vec(), + peg_info.current, ); }); } @@ -265,8 +265,7 @@ fn sell_should_use_correct_pegs_when_assets_are_unsorted() { assert_eq!( received_unsorted, received_sorted, - "unsorted pool sell output ({}) must equal sorted pool output ({})", - received_unsorted, received_sorted, + "unsorted pool sell output ({received_unsorted}) must equal sorted pool output ({received_sorted})", ); }); } @@ -344,8 +343,7 @@ fn buy_should_use_correct_pegs_when_assets_are_unsorted() { assert_eq!( spent_unsorted, spent_sorted, - "unsorted pool buy cost ({}) must equal sorted pool buy cost ({})", - spent_unsorted, spent_sorted, + "unsorted pool buy cost ({spent_unsorted}) must equal sorted pool buy cost ({spent_sorted})", ); }); } @@ -477,8 +475,7 @@ fn sell_should_use_correct_oracle_pegs_when_assets_are_unsorted() { assert_eq!( received_unsorted, received_sorted, - "unsorted oracle-peg pool sell output ({}) must equal sorted pool output ({})", - received_unsorted, received_sorted, + "unsorted oracle-peg pool sell output ({received_unsorted}) must equal sorted pool output ({received_sorted})", ); }); } @@ -629,8 +626,7 @@ fn remove_liquidity_should_use_correct_pegs_when_assets_unsorted() { assert_eq!( received_unsorted, received_sorted, - "unsorted pool remove_liquidity_one_asset ({}) must equal sorted pool ({})", - received_unsorted, received_sorted, + "unsorted pool remove_liquidity_one_asset ({received_unsorted}) must equal sorted pool ({received_sorted})", ); }); } @@ -727,8 +723,8 @@ fn pool_pegs_should_be_cosorted_with_five_assets_random_order() { // Peg values must match sorted asset order. assert_eq!( - peg_info.source.to_vec(), - vec![ + peg_info.source.as_slice(), + [ PegSource::Value((1, 1)), // asset_1 PegSource::Value((3, 1)), // asset_3 PegSource::Value((5, 1)), // asset_5 @@ -736,7 +732,7 @@ fn pool_pegs_should_be_cosorted_with_five_assets_random_order() { PegSource::Value((9, 1)), // asset_9 ], "sources must be co-sorted; got {:?}", - peg_info.source.to_vec(), + peg_info.source, ); assert_eq!( @@ -805,14 +801,14 @@ fn pool_pegs_should_be_cosorted_with_mixed_peg_source_types() { // Sources co-sorted: Value for asset_5, Oracle for asset_10, Oracle for asset_20. assert_eq!( - peg_info.source.to_vec(), - vec![ + peg_info.source.as_slice(), + [ PegSource::Value((1, 1)), PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), PegSource::Oracle((oracle_source, OraclePeriod::Short, asset_5)), ], "sources must be co-sorted; got {:?}", - peg_info.source.to_vec(), + peg_info.source, ); }); } From c55e0c24fb45424743f5de16dfc86783882bb61c Mon Sep 17 00:00:00 2001 From: khuzama98 Date: Fri, 17 Apr 2026 13:27:21 +0000 Subject: [PATCH 9/9] Update pallets weights [ignore benchmarks] --- .../hydradx/src/weights/pallet-stableswap.rs | 626 ++++++++++++++++++ 1 file changed, 626 insertions(+) create mode 100644 runtime/hydradx/src/weights/pallet-stableswap.rs diff --git a/runtime/hydradx/src/weights/pallet-stableswap.rs b/runtime/hydradx/src/weights/pallet-stableswap.rs new file mode 100644 index 000000000..5210c5b18 --- /dev/null +++ b/runtime/hydradx/src/weights/pallet-stableswap.rs @@ -0,0 +1,626 @@ +// This file is part of HydraDX. + +// Copyright (C) 2020-2024 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +//! Autogenerated weights for `pallet_stableswap` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 49.1.0 +//! DATE: 2026-04-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `bench-bot`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` + +// Executed Command: +// ./bin/hydradx +// benchmark +// pallet +// --wasm-execution=compiled +// --pallet +// pallet-stableswap +// --extrinsic +// * +// --heap-pages +// 4096 +// --steps +// 50 +// --repeat +// 20 +// --template +// scripts/pallet-weight-template.hbs +// --output +// runtime/hydradx/src/weights/pallet-stableswap.rs +// --quiet + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; +use crate::*; + +/// Weights for `pallet_stableswap`. +pub struct WeightInfo(PhantomData); + +/// Weights for `pallet_stableswap` using the HydraDX node and recommended hardware. +pub struct HydraWeight(PhantomData); +impl pallet_stableswap::WeightInfo for HydraWeight { + /// Storage: `Stableswap::Pools` (r:1 w:1) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:0 w:1) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `693` + // Estimated: `16590` + // Minimum execution time: 54_827_000 picoseconds. + Weight::from_parts(55_633_000, 16590) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::Pools` (r:1 w:1) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:0 w:1) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:0 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + fn create_pool_with_pegs() -> Weight { + // Proof Size summary in bytes: + // Measured: `1706` + // Estimated: `16590` + // Minimum execution time: 102_554_000 picoseconds. + Weight::from_parts(103_558_000, 16590) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::AssetTradability` (r:5 w:0) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:11 w:11) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:1) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolSnapshots` (r:1 w:0) + /// Proof: `Stableswap::PoolSnapshots` (`max_values`: None, `max_size`: Some(328), added: 2803, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::BlockFee` (r:1 w:1) + /// Proof: `Stableswap::BlockFee` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::BannedAssets` (r:6 w:0) + /// Proof: `AssetRegistry::BannedAssets` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AccountCurrencyMap` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AccountCurrencyMap` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::AssetLockdownState` (r:1 w:1) + /// Proof: `CircuitBreaker::AssetLockdownState` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Reserves` (r:1 w:1) + /// Proof: `Tokens::Reserves` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::EgressAccounts` (r:2 w:0) + /// Proof: `CircuitBreaker::EgressAccounts` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::GlobalAssetOverrides` (r:6 w:0) + /// Proof: `CircuitBreaker::GlobalAssetOverrides` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `EVMAccounts::AccountExtension` (r:1 w:0) + /// Proof: `EVMAccounts::AccountExtension` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `HSM::FlashMinter` (r:1 w:0) + /// Proof: `HSM::FlashMinter` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:1 w:0) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Accumulator` (r:1 w:1) + /// Proof: `EmaOracle::Accumulator` (`max_values`: Some(1), `max_size`: Some(6601), added: 7096, mode: `MaxEncodedLen`) + fn add_assets_liquidity() -> Weight { + // Proof Size summary in bytes: + // Measured: `5068` + // Estimated: `29403` + // Minimum execution time: 2_070_108_000 picoseconds. + Weight::from_parts(2_076_386_000, 29403) + .saturating_add(T::DbWeight::get().reads(54_u64)) + .saturating_add(T::DbWeight::get().writes(18_u64)) + } + /// Storage: `Stableswap::AssetTradability` (r:1 w:0) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:1) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:7 w:3) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::BlockFee` (r:1 w:1) + /// Proof: `Stableswap::BlockFee` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolSnapshots` (r:1 w:0) + /// Proof: `Stableswap::PoolSnapshots` (`max_values`: None, `max_size`: Some(328), added: 2803, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::BannedAssets` (r:2 w:0) + /// Proof: `AssetRegistry::BannedAssets` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AccountCurrencyMap` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AccountCurrencyMap` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::AssetLockdownState` (r:1 w:1) + /// Proof: `CircuitBreaker::AssetLockdownState` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Reserves` (r:1 w:1) + /// Proof: `Tokens::Reserves` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::EgressAccounts` (r:2 w:0) + /// Proof: `CircuitBreaker::EgressAccounts` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::GlobalAssetOverrides` (r:2 w:0) + /// Proof: `CircuitBreaker::GlobalAssetOverrides` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `EVMAccounts::AccountExtension` (r:1 w:0) + /// Proof: `EVMAccounts::AccountExtension` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `HSM::FlashMinter` (r:1 w:0) + /// Proof: `HSM::FlashMinter` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:1 w:0) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Accumulator` (r:1 w:1) + /// Proof: `EmaOracle::Accumulator` (`max_values`: Some(1), `max_size`: Some(6601), added: 7096, mode: `MaxEncodedLen`) + fn add_liquidity_shares() -> Weight { + // Proof Size summary in bytes: + // Measured: `4800` + // Estimated: `19071` + // Minimum execution time: 1_383_845_000 picoseconds. + Weight::from_parts(1_386_437_000, 19071) + .saturating_add(T::DbWeight::get().reads(38_u64)) + .saturating_add(T::DbWeight::get().writes(10_u64)) + } + /// Storage: `Stableswap::AssetTradability` (r:1 w:0) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:7 w:3) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:1) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::BlockFee` (r:1 w:1) + /// Proof: `Stableswap::BlockFee` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolSnapshots` (r:1 w:0) + /// Proof: `Stableswap::PoolSnapshots` (`max_values`: None, `max_size`: Some(328), added: 2803, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::GlobalAssetOverrides` (r:2 w:0) + /// Proof: `CircuitBreaker::GlobalAssetOverrides` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `EVMAccounts::AccountExtension` (r:1 w:0) + /// Proof: `EVMAccounts::AccountExtension` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `HSM::FlashMinter` (r:1 w:0) + /// Proof: `HSM::FlashMinter` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:1 w:0) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::BannedAssets` (r:1 w:0) + /// Proof: `AssetRegistry::BannedAssets` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::EgressAccounts` (r:2 w:0) + /// Proof: `CircuitBreaker::EgressAccounts` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Accumulator` (r:1 w:1) + /// Proof: `EmaOracle::Accumulator` (`max_values`: Some(1), `max_size`: Some(6601), added: 7096, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AccountCurrencyMap` (r:0 w:1) + /// Proof: `MultiTransactionPayment::AccountCurrencyMap` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + fn remove_liquidity_one_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `4942` + // Estimated: `19071` + // Minimum execution time: 1_351_380_000 picoseconds. + Weight::from_parts(1_356_528_000, 19071) + .saturating_add(T::DbWeight::get().reads(35_u64)) + .saturating_add(T::DbWeight::get().writes(9_u64)) + } + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:11 w:11) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:1) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolSnapshots` (r:1 w:0) + /// Proof: `Stableswap::PoolSnapshots` (`max_values`: None, `max_size`: Some(328), added: 2803, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::AssetTradability` (r:5 w:0) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `EVMAccounts::AccountExtension` (r:1 w:0) + /// Proof: `EVMAccounts::AccountExtension` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `HSM::FlashMinter` (r:1 w:0) + /// Proof: `HSM::FlashMinter` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:1 w:0) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::BannedAssets` (r:5 w:0) + /// Proof: `AssetRegistry::BannedAssets` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AccountCurrencyMap` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AccountCurrencyMap` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:5 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::EgressAccounts` (r:2 w:0) + /// Proof: `CircuitBreaker::EgressAccounts` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::GlobalAssetOverrides` (r:6 w:0) + /// Proof: `CircuitBreaker::GlobalAssetOverrides` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:0) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::BlockFee` (r:1 w:0) + /// Proof: `Stableswap::BlockFee` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Accumulator` (r:1 w:1) + /// Proof: `EmaOracle::Accumulator` (`max_values`: Some(1), `max_size`: Some(6601), added: 7096, mode: `MaxEncodedLen`) + fn remove_liquidity() -> Weight { + // Proof Size summary in bytes: + // Measured: `4942` + // Estimated: `29403` + // Minimum execution time: 973_764_000 picoseconds. + Weight::from_parts(981_963_000, 29403) + .saturating_add(T::DbWeight::get().reads(56_u64)) + .saturating_add(T::DbWeight::get().writes(14_u64)) + } + /// Storage: `Stableswap::AssetTradability` (r:1 w:0) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:7 w:3) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:1) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::BlockFee` (r:1 w:1) + /// Proof: `Stableswap::BlockFee` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolSnapshots` (r:1 w:0) + /// Proof: `Stableswap::PoolSnapshots` (`max_values`: None, `max_size`: Some(328), added: 2803, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::GlobalAssetOverrides` (r:2 w:0) + /// Proof: `CircuitBreaker::GlobalAssetOverrides` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `EVMAccounts::AccountExtension` (r:1 w:0) + /// Proof: `EVMAccounts::AccountExtension` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `HSM::FlashMinter` (r:1 w:0) + /// Proof: `HSM::FlashMinter` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:1 w:0) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::BannedAssets` (r:1 w:0) + /// Proof: `AssetRegistry::BannedAssets` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AccountCurrencyMap` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AccountCurrencyMap` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::EgressAccounts` (r:2 w:0) + /// Proof: `CircuitBreaker::EgressAccounts` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Accumulator` (r:1 w:1) + /// Proof: `EmaOracle::Accumulator` (`max_values`: Some(1), `max_size`: Some(6601), added: 7096, mode: `MaxEncodedLen`) + fn withdraw_asset_amount() -> Weight { + // Proof Size summary in bytes: + // Measured: `4942` + // Estimated: `19071` + // Minimum execution time: 1_831_719_000 picoseconds. + Weight::from_parts(1_840_349_000, 19071) + .saturating_add(T::DbWeight::get().reads(36_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } + /// Storage: `Stableswap::AssetTradability` (r:2 w:0) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:7 w:4) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolSnapshots` (r:1 w:0) + /// Proof: `Stableswap::PoolSnapshots` (`max_values`: None, `max_size`: Some(328), added: 2803, mode: `MaxEncodedLen`) + /// Storage: `EVMAccounts::AccountExtension` (r:1 w:0) + /// Proof: `EVMAccounts::AccountExtension` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `HSM::FlashMinter` (r:1 w:0) + /// Proof: `HSM::FlashMinter` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:2 w:0) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::BannedAssets` (r:2 w:0) + /// Proof: `AssetRegistry::BannedAssets` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::EgressAccounts` (r:2 w:0) + /// Proof: `CircuitBreaker::EgressAccounts` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::GlobalAssetOverrides` (r:2 w:0) + /// Proof: `CircuitBreaker::GlobalAssetOverrides` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:0) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Accumulator` (r:1 w:1) + /// Proof: `EmaOracle::Accumulator` (`max_values`: Some(1), `max_size`: Some(6601), added: 7096, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AccountCurrencyMap` (r:0 w:1) + /// Proof: `MultiTransactionPayment::AccountCurrencyMap` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::BlockFee` (r:0 w:1) + /// Proof: `Stableswap::BlockFee` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + fn sell() -> Weight { + // Proof Size summary in bytes: + // Measured: `4923` + // Estimated: `19071` + // Minimum execution time: 1_296_460_000 picoseconds. + Weight::from_parts(1_301_278_000, 19071) + .saturating_add(T::DbWeight::get().reads(37_u64)) + .saturating_add(T::DbWeight::get().writes(9_u64)) + } + /// Storage: `Stableswap::AssetTradability` (r:2 w:0) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:7 w:4) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolSnapshots` (r:1 w:0) + /// Proof: `Stableswap::PoolSnapshots` (`max_values`: None, `max_size`: Some(328), added: 2803, mode: `MaxEncodedLen`) + /// Storage: `EVMAccounts::AccountExtension` (r:1 w:0) + /// Proof: `EVMAccounts::AccountExtension` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `HSM::FlashMinter` (r:1 w:0) + /// Proof: `HSM::FlashMinter` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:2 w:0) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::BannedAssets` (r:2 w:0) + /// Proof: `AssetRegistry::BannedAssets` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::EgressAccounts` (r:2 w:0) + /// Proof: `CircuitBreaker::EgressAccounts` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::GlobalAssetOverrides` (r:2 w:0) + /// Proof: `CircuitBreaker::GlobalAssetOverrides` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AccountCurrencyMap` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AccountCurrencyMap` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:0) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Accumulator` (r:1 w:1) + /// Proof: `EmaOracle::Accumulator` (`max_values`: Some(1), `max_size`: Some(6601), added: 7096, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::BlockFee` (r:0 w:1) + /// Proof: `Stableswap::BlockFee` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + fn buy() -> Weight { + // Proof Size summary in bytes: + // Measured: `4893` + // Estimated: `19071` + // Minimum execution time: 1_285_269_000 picoseconds. + Weight::from_parts(1_291_937_000, 19071) + .saturating_add(T::DbWeight::get().reads(38_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::AssetTradability` (r:1 w:1) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + fn set_asset_tradable_state() -> Weight { + // Proof Size summary in bytes: + // Measured: `231` + // Estimated: `3522` + // Minimum execution time: 26_659_000 picoseconds. + Weight::from_parts(26_877_000, 3522) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Stableswap::Pools` (r:1 w:1) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + fn update_pool_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `191` + // Estimated: `3522` + // Minimum execution time: 22_992_000 picoseconds. + Weight::from_parts(23_405_000, 3522) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Stableswap::Pools` (r:1 w:1) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + fn update_amplification() -> Weight { + // Proof Size summary in bytes: + // Measured: `191` + // Estimated: `3522` + // Minimum execution time: 23_925_000 picoseconds. + Weight::from_parts(24_266_000, 3522) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + fn update_asset_peg_source() -> Weight { + // Proof Size summary in bytes: + // Measured: `461` + // Estimated: `3820` + // Minimum execution time: 28_056_000 picoseconds. + Weight::from_parts(28_409_000, 3820) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + fn update_pool_max_peg_update() -> Weight { + // Proof Size summary in bytes: + // Measured: `454` + // Estimated: `3820` + // Minimum execution time: 27_812_000 picoseconds. + Weight::from_parts(27_987_000, 3820) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::AssetTradability` (r:1 w:0) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:7 w:3) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:1) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolSnapshots` (r:1 w:0) + /// Proof: `Stableswap::PoolSnapshots` (`max_values`: None, `max_size`: Some(328), added: 2803, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::BannedAssets` (r:2 w:0) + /// Proof: `AssetRegistry::BannedAssets` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AccountCurrencyMap` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AccountCurrencyMap` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::AssetLockdownState` (r:1 w:1) + /// Proof: `CircuitBreaker::AssetLockdownState` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Reserves` (r:1 w:1) + /// Proof: `Tokens::Reserves` (`max_values`: None, `max_size`: Some(1261), added: 3736, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::EgressAccounts` (r:2 w:0) + /// Proof: `CircuitBreaker::EgressAccounts` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::GlobalAssetOverrides` (r:2 w:0) + /// Proof: `CircuitBreaker::GlobalAssetOverrides` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `EVMAccounts::AccountExtension` (r:1 w:0) + /// Proof: `EVMAccounts::AccountExtension` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `HSM::FlashMinter` (r:1 w:0) + /// Proof: `HSM::FlashMinter` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:1 w:0) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Accumulator` (r:1 w:1) + /// Proof: `EmaOracle::Accumulator` (`max_values`: Some(1), `max_size`: Some(6601), added: 7096, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::BlockFee` (r:0 w:1) + /// Proof: `Stableswap::BlockFee` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// The range of component `e` is `[0, 1]`. + fn router_execution_sell(e: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `3113 + e * (1667 ±0)` + // Estimated: `16590 + e * (5166 ±0)` + // Minimum execution time: 1_257_868_000 picoseconds. + Weight::from_parts(1_269_983_095, 16590) + // Standard Error: 1_812_099 + .saturating_add(Weight::from_parts(1_886_863_704, 0).saturating_mul(e.into())) + .saturating_add(T::DbWeight::get().reads(19_u64)) + .saturating_add(T::DbWeight::get().reads((18_u64).saturating_mul(e.into()))) + .saturating_add(T::DbWeight::get().writes((10_u64).saturating_mul(e.into()))) + .saturating_add(Weight::from_parts(0, 5166).saturating_mul(e.into())) + } + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:7 w:4) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:1) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::AssetTradability` (r:2 w:0) + /// Proof: `Stableswap::AssetTradability` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolSnapshots` (r:1 w:0) + /// Proof: `Stableswap::PoolSnapshots` (`max_values`: None, `max_size`: Some(328), added: 2803, mode: `MaxEncodedLen`) + /// Storage: `EVMAccounts::AccountExtension` (r:1 w:0) + /// Proof: `EVMAccounts::AccountExtension` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `HSM::FlashMinter` (r:1 w:0) + /// Proof: `HSM::FlashMinter` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) + /// Storage: `Duster::AccountWhitelist` (r:2 w:0) + /// Proof: `Duster::AccountWhitelist` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::BannedAssets` (r:2 w:0) + /// Proof: `AssetRegistry::BannedAssets` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::EgressAccounts` (r:2 w:0) + /// Proof: `CircuitBreaker::EgressAccounts` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `CircuitBreaker::GlobalAssetOverrides` (r:2 w:0) + /// Proof: `CircuitBreaker::GlobalAssetOverrides` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AccountCurrencyMap` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AccountCurrencyMap` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:1 w:0) + /// Proof: `MultiTransactionPayment::AcceptedCurrencies` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:0) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Accumulator` (r:1 w:1) + /// Proof: `EmaOracle::Accumulator` (`max_values`: Some(1), `max_size`: Some(6601), added: 7096, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::BlockFee` (r:0 w:1) + /// Proof: `Stableswap::BlockFee` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// The range of component `c` is `[1, 2]`. + /// The range of component `e` is `[0, 1]`. + fn router_execution_buy(c: u32, e: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `2869 + e * (2024 ±0)` + // Estimated: `13990 + e * (5745 ±3_721_610_289_260_408)` + // Minimum execution time: 595_855_000 picoseconds. + Weight::from_parts(598_663_000, 13990) + // Standard Error: 1_658_219 + .saturating_add(Weight::from_parts(6_825_969, 0).saturating_mul(c.into())) + // Standard Error: 3_693_321 + .saturating_add(Weight::from_parts(712_202_696, 0).saturating_mul(e.into())) + .saturating_add(T::DbWeight::get().reads(16_u64)) + .saturating_add(T::DbWeight::get().reads((22_u64).saturating_mul(e.into()))) + .saturating_add(T::DbWeight::get().writes((8_u64).saturating_mul(e.into()))) + .saturating_add(Weight::from_parts(0, 5745).saturating_mul(e.into())) + } + /// Storage: `Stableswap::Pools` (r:1 w:0) + /// Proof: `Stableswap::Pools` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `AssetRegistry::Assets` (r:6 w:0) + /// Proof: `AssetRegistry::Assets` (`max_values`: None, `max_size`: Some(125), added: 2600, mode: `MaxEncodedLen`) + /// Storage: `Tokens::Accounts` (r:5 w:0) + /// Proof: `Tokens::Accounts` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `Tokens::TotalIssuance` (r:1 w:0) + /// Proof: `Tokens::TotalIssuance` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Stableswap::PoolPegs` (r:1 w:0) + /// Proof: `Stableswap::PoolPegs` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `EmaOracle::Oracles` (r:4 w:0) + /// Proof: `EmaOracle::Oracles` (`max_values`: None, `max_size`: Some(194), added: 2669, mode: `MaxEncodedLen`) + fn calculate_spot_price_with_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `3094` + // Estimated: `16590` + // Minimum execution time: 512_811_000 picoseconds. + Weight::from_parts(516_159_000, 16590) + .saturating_add(T::DbWeight::get().reads(18_u64)) + } +} \ No newline at end of file