Asset Conversion: Pool Touch Call (#3251)

Introduce `touch` call designed to address operational prerequisites
before providing liquidity to a pool.

This function ensures that essential requirements, such as the presence
of the pool's accounts, are fulfilled. It is particularly beneficial in
scenarios where a pool creator removes the pool's accounts without
providing liquidity.

---------

Co-authored-by: command-bot <>
This commit is contained in:
Muharem
2024-04-17 18:45:01 +02:00
committed by GitHub
parent aa78fe2180
commit 305d311d5c
7 changed files with 268 additions and 53 deletions
@@ -24,7 +24,7 @@ use frame_support::{
assert_ok,
traits::{
fungible::NativeOrWithId,
fungibles::{Create, Inspect, Mutate},
fungibles::{Create, Inspect, Mutate, Refund},
},
};
use frame_system::RawOrigin as SystemOrigin;
@@ -75,12 +75,21 @@ where
}
/// Create the `asset` and mint the `amount` for the `caller`.
fn create_asset<T: Config>(caller: &T::AccountId, asset: &T::AssetKind, amount: T::Balance)
where
fn create_asset<T: Config>(
caller: &T::AccountId,
asset: &T::AssetKind,
amount: T::Balance,
is_sufficient: bool,
) where
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
{
if !T::Assets::asset_exists(asset.clone()) {
assert_ok!(T::Assets::create(asset.clone(), caller.clone(), true, T::Balance::one()));
assert_ok!(T::Assets::create(
asset.clone(),
caller.clone(),
is_sufficient,
T::Balance::one()
));
}
assert_ok!(T::Assets::mint_into(
asset.clone(),
@@ -141,8 +150,8 @@ where
T::Assets::minimum_balance(asset1.clone()),
T::Assets::minimum_balance(asset2.clone()),
);
create_asset::<T>(caller, asset1, liquidity1);
create_asset::<T>(caller, asset2, liquidity2);
create_asset::<T>(caller, asset1, liquidity1, true);
create_asset::<T>(caller, asset2, liquidity2, true);
let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
mint_setup_fee_asset::<T>(caller, asset1, asset2, &lp_token);
@@ -172,8 +181,8 @@ mod benchmarks {
fn create_pool() {
let caller: T::AccountId = whitelisted_caller();
let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
create_asset::<T>(&caller, &asset1, T::Assets::minimum_balance(asset1.clone()));
create_asset::<T>(&caller, &asset2, T::Assets::minimum_balance(asset2.clone()));
create_asset::<T>(&caller, &asset1, T::Assets::minimum_balance(asset1.clone()), true);
create_asset::<T>(&caller, &asset2, T::Assets::minimum_balance(asset2.clone()), true);
let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
create_fee_asset::<T>(&caller);
@@ -358,5 +367,47 @@ mod benchmarks {
assert_eq!(actual_balance, init_caller_balance + T::Balance::one());
}
#[benchmark]
fn touch(n: Linear<0, 3>) {
let caller: T::AccountId = whitelisted_caller();
let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
let pool_account = T::PoolLocator::address(&pool_id).unwrap();
create_fee_asset::<T>(&caller);
create_asset::<T>(&caller, &asset1, <T as Config>::Balance::one(), false);
create_asset::<T>(&caller, &asset2, <T as Config>::Balance::one(), false);
let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
mint_setup_fee_asset::<T>(&caller, &asset1, &asset2, &lp_token);
assert_ok!(AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset1.clone()),
Box::new(asset2.clone())
));
if n > 0 &&
<T as Config>::Assets::deposit_held(asset1.clone(), pool_account.clone()).is_some()
{
let _ = <T as Config>::Assets::refund(asset1.clone(), pool_account.clone());
}
if n > 1 &&
<T as Config>::Assets::deposit_held(asset2.clone(), pool_account.clone()).is_some()
{
let _ = <T as Config>::Assets::refund(asset2.clone(), pool_account.clone());
}
if n > 2 &&
<T as Config>::PoolAssets::deposit_held(lp_token.clone(), pool_account.clone())
.is_some()
{
let _ = <T as Config>::PoolAssets::refund(lp_token, pool_account);
}
#[extrinsic_call]
_(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()));
assert_last_event::<T>(Event::Touched { pool_id, who: caller }.into());
}
impl_benchmark_test_suite!(AssetConversion, crate::mock::new_test_ext(), crate::mock::Test);
}
+61 -4
View File
@@ -98,7 +98,10 @@ use sp_std::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec};
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_support::{
pallet_prelude::{DispatchResult, *},
traits::fungibles::Refund,
};
use frame_system::pallet_prelude::*;
use sp_arithmetic::{traits::Unsigned, Permill};
@@ -130,7 +133,8 @@ pub mod pallet {
type Assets: Inspect<Self::AccountId, AssetId = Self::AssetKind, Balance = Self::Balance>
+ Mutate<Self::AccountId>
+ AccountTouch<Self::AssetKind, Self::AccountId, Balance = Self::Balance>
+ Balanced<Self::AccountId>;
+ Balanced<Self::AccountId>
+ Refund<Self::AccountId, AssetId = Self::AssetKind>;
/// Liquidity pool identifier.
type PoolId: Parameter + MaxEncodedLen + Ord;
@@ -149,7 +153,8 @@ pub mod pallet {
type PoolAssets: Inspect<Self::AccountId, AssetId = Self::PoolAssetId, Balance = Self::Balance>
+ Create<Self::AccountId>
+ Mutate<Self::AccountId>
+ AccountTouch<Self::PoolAssetId, Self::AccountId, Balance = Self::Balance>;
+ AccountTouch<Self::PoolAssetId, Self::AccountId, Balance = Self::Balance>
+ Refund<Self::AccountId, AssetId = Self::PoolAssetId>;
/// A % the liquidity providers will take of every swap. Represents 10ths of a percent.
#[pallet::constant]
@@ -281,6 +286,13 @@ pub mod pallet {
/// E.g. (A, amount_in) -> (Dot, amount_out) -> (B, amount_out)
path: BalancePath<T>,
},
/// Pool has been touched in order to fulfill operational requirements.
Touched {
/// The ID of the pool.
pool_id: T::PoolId,
/// The account initiating the touch.
who: T::AccountId,
},
}
#[pallet::error]
@@ -391,7 +403,9 @@ pub mod pallet {
NextPoolAssetId::<T>::set(Some(next_lp_token_id));
T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
T::PoolAssets::touch(lp_token.clone(), &pool_account, &sender)?;
if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) {
T::PoolAssets::touch(lp_token.clone(), &pool_account, &sender)?
};
let pool_info = PoolInfo { lp_token: lp_token.clone() };
Pools::<T>::insert(pool_id.clone(), pool_info);
@@ -656,6 +670,49 @@ pub mod pallet {
)?;
Ok(())
}
/// Touch an existing pool to fulfill prerequisites before providing liquidity, such as
/// ensuring that the pool's accounts are in place. It is typically useful when a pool
/// creator removes the pool's accounts and does not provide a liquidity. This action may
/// involve holding assets from the caller as a deposit for creating the pool's accounts.
///
/// The origin must be Signed.
///
/// - `asset1`: The asset ID of an existing pool with a pair (asset1, asset2).
/// - `asset2`: The asset ID of an existing pool with a pair (asset1, asset2).
///
/// Emits `Touched` event when successful.
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::touch(3))]
pub fn touch(
origin: OriginFor<T>,
asset1: Box<T::AssetKind>,
asset2: Box<T::AssetKind>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
.map_err(|_| Error::<T>::InvalidAssetPair)?;
let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
let pool_account =
T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
let mut refunds_number: u32 = 0;
if T::Assets::should_touch(*asset1.clone(), &pool_account) {
T::Assets::touch(*asset1, &pool_account, &who)?;
refunds_number += 1;
}
if T::Assets::should_touch(*asset2.clone(), &pool_account) {
T::Assets::touch(*asset2, &pool_account, &who)?;
refunds_number += 1;
}
if T::PoolAssets::should_touch(pool.lp_token.clone(), &pool_account) {
T::PoolAssets::touch(pool.lp_token, &pool_account, &who)?;
refunds_number += 1;
}
Self::deposit_event(Event::Touched { pool_id, who });
Ok(Some(T::WeightInfo::touch(refunds_number)).into())
}
}
impl<T: Config> Pallet<T> {
+88 -41
View File
@@ -18,27 +18,25 @@
//! Autogenerated weights for `pallet_asset_conversion`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2024-04-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2024-03-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! HOSTNAME: `runner-p5qp1txx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
// Executed Command:
// ./target/production/substrate-node
// target/production/substrate-node
// benchmark
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_asset_conversion
// --no-storage-info
// --no-median-slopes
// --no-min-squares
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --output=./substrate/frame/asset-conversion/src/weights.rs
// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
// --pallet=pallet_asset_conversion
// --chain=dev
// --header=./substrate/HEADER-APACHE2
// --output=./substrate/frame/asset-conversion/src/weights.rs
// --template=./substrate/.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
@@ -56,6 +54,7 @@ pub trait WeightInfo {
fn remove_liquidity() -> Weight;
fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight;
fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight;
fn touch(n: u32, ) -> Weight;
}
/// Weights for `pallet_asset_conversion` using the Substrate node and recommended hardware.
@@ -63,7 +62,7 @@ pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `AssetConversion::Pools` (r:1 w:1)
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Storage: `System::Account` (r:2 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Assets::Asset` (r:2 w:0)
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
@@ -77,9 +76,9 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof Size summary in bytes:
// Measured: `910`
// Estimated: `6360`
// Minimum execution time: 86_709_000 picoseconds.
Weight::from_parts(88_841_000, 6360)
.saturating_add(T::DbWeight::get().reads(7_u64))
// Minimum execution time: 95_080_000 picoseconds.
Weight::from_parts(97_241_000, 6360)
.saturating_add(T::DbWeight::get().reads(8_u64))
.saturating_add(T::DbWeight::get().writes(5_u64))
}
/// Storage: `AssetConversion::Pools` (r:1 w:0)
@@ -98,8 +97,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof Size summary in bytes:
// Measured: `1507`
// Estimated: `11426`
// Minimum execution time: 148_672_000 picoseconds.
Weight::from_parts(151_824_000, 11426)
// Minimum execution time: 147_652_000 picoseconds.
Weight::from_parts(153_331_000, 11426)
.saturating_add(T::DbWeight::get().reads(11_u64))
.saturating_add(T::DbWeight::get().writes(10_u64))
}
@@ -117,8 +116,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof Size summary in bytes:
// Measured: `1650`
// Estimated: `11426`
// Minimum execution time: 130_743_000 picoseconds.
Weight::from_parts(132_793_000, 11426)
// Minimum execution time: 130_738_000 picoseconds.
Weight::from_parts(134_350_000, 11426)
.saturating_add(T::DbWeight::get().reads(9_u64))
.saturating_add(T::DbWeight::get().writes(8_u64))
}
@@ -131,10 +130,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof Size summary in bytes:
// Measured: `89 + n * (419 ±0)`
// Estimated: `990 + n * (5218 ±0)`
// Minimum execution time: 81_173_000 picoseconds.
Weight::from_parts(82_574_000, 990)
// Standard Error: 335_929
.saturating_add(Weight::from_parts(11_607_291, 0).saturating_mul(n.into()))
// Minimum execution time: 79_681_000 picoseconds.
Weight::from_parts(81_461_000, 990)
// Standard Error: 320_959
.saturating_add(Weight::from_parts(11_223_703, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into())))
.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into())))
.saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into()))
@@ -148,21 +147,45 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Proof Size summary in bytes:
// Measured: `89 + n * (419 ±0)`
// Estimated: `990 + n * (5218 ±0)`
// Minimum execution time: 80_562_000 picoseconds.
Weight::from_parts(82_501_000, 990)
// Standard Error: 329_460
.saturating_add(Weight::from_parts(11_295_339, 0).saturating_mul(n.into()))
// Minimum execution time: 78_988_000 picoseconds.
Weight::from_parts(81_025_000, 990)
// Standard Error: 320_021
.saturating_add(Weight::from_parts(11_040_712, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into())))
.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into())))
.saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into()))
}
/// Storage: `AssetConversion::Pools` (r:1 w:0)
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `Assets::Asset` (r:2 w:2)
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:0)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Assets::Account` (r:2 w:2)
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
/// Storage: `PoolAssets::Asset` (r:1 w:1)
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
/// Storage: `PoolAssets::Account` (r:1 w:1)
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
/// The range of component `n` is `[0, 3]`.
fn touch(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `1571`
// Estimated: `6360`
// Minimum execution time: 45_757_000 picoseconds.
Weight::from_parts(48_502_032, 6360)
// Standard Error: 62_850
.saturating_add(Weight::from_parts(19_450_978, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(8_u64))
.saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into())))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `AssetConversion::Pools` (r:1 w:1)
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Storage: `System::Account` (r:2 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Assets::Asset` (r:2 w:0)
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
@@ -176,9 +199,9 @@ impl WeightInfo for () {
// Proof Size summary in bytes:
// Measured: `910`
// Estimated: `6360`
// Minimum execution time: 86_709_000 picoseconds.
Weight::from_parts(88_841_000, 6360)
.saturating_add(RocksDbWeight::get().reads(7_u64))
// Minimum execution time: 95_080_000 picoseconds.
Weight::from_parts(97_241_000, 6360)
.saturating_add(RocksDbWeight::get().reads(8_u64))
.saturating_add(RocksDbWeight::get().writes(5_u64))
}
/// Storage: `AssetConversion::Pools` (r:1 w:0)
@@ -197,8 +220,8 @@ impl WeightInfo for () {
// Proof Size summary in bytes:
// Measured: `1507`
// Estimated: `11426`
// Minimum execution time: 148_672_000 picoseconds.
Weight::from_parts(151_824_000, 11426)
// Minimum execution time: 147_652_000 picoseconds.
Weight::from_parts(153_331_000, 11426)
.saturating_add(RocksDbWeight::get().reads(11_u64))
.saturating_add(RocksDbWeight::get().writes(10_u64))
}
@@ -216,8 +239,8 @@ impl WeightInfo for () {
// Proof Size summary in bytes:
// Measured: `1650`
// Estimated: `11426`
// Minimum execution time: 130_743_000 picoseconds.
Weight::from_parts(132_793_000, 11426)
// Minimum execution time: 130_738_000 picoseconds.
Weight::from_parts(134_350_000, 11426)
.saturating_add(RocksDbWeight::get().reads(9_u64))
.saturating_add(RocksDbWeight::get().writes(8_u64))
}
@@ -230,10 +253,10 @@ impl WeightInfo for () {
// Proof Size summary in bytes:
// Measured: `89 + n * (419 ±0)`
// Estimated: `990 + n * (5218 ±0)`
// Minimum execution time: 81_173_000 picoseconds.
Weight::from_parts(82_574_000, 990)
// Standard Error: 335_929
.saturating_add(Weight::from_parts(11_607_291, 0).saturating_mul(n.into()))
// Minimum execution time: 79_681_000 picoseconds.
Weight::from_parts(81_461_000, 990)
// Standard Error: 320_959
.saturating_add(Weight::from_parts(11_223_703, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into())))
.saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into())))
.saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into()))
@@ -247,12 +270,36 @@ impl WeightInfo for () {
// Proof Size summary in bytes:
// Measured: `89 + n * (419 ±0)`
// Estimated: `990 + n * (5218 ±0)`
// Minimum execution time: 80_562_000 picoseconds.
Weight::from_parts(82_501_000, 990)
// Standard Error: 329_460
.saturating_add(Weight::from_parts(11_295_339, 0).saturating_mul(n.into()))
// Minimum execution time: 78_988_000 picoseconds.
Weight::from_parts(81_025_000, 990)
// Standard Error: 320_021
.saturating_add(Weight::from_parts(11_040_712, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into())))
.saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into())))
.saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into()))
}
/// Storage: `AssetConversion::Pools` (r:1 w:0)
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
/// Storage: `Assets::Asset` (r:2 w:2)
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:0)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Assets::Account` (r:2 w:2)
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
/// Storage: `PoolAssets::Asset` (r:1 w:1)
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
/// Storage: `PoolAssets::Account` (r:1 w:1)
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
/// The range of component `n` is `[0, 3]`.
fn touch(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `1571`
// Estimated: `6360`
// Minimum execution time: 45_757_000 picoseconds.
Weight::from_parts(48_502_032, 6360)
// Standard Error: 62_850
.saturating_add(Weight::from_parts(19_450_978, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(8_u64))
.saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(n.into())))
}
}