Skip to content

Commit

Permalink
Asset Conversion: Pool Touch Call (#3251)
Browse files Browse the repository at this point in the history
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 <>
  • Loading branch information
muharem authored and sandreim committed Apr 18, 2024
1 parent 4aa2010 commit e1ec8b9
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,26 @@ impl<T: frame_system::Config> pallet_asset_conversion::WeightInfo for WeightInfo
.saturating_add(T::DbWeight::get().writes(4))
.saturating_add(Weight::from_parts(0, 393).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: `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: 381_000_000 picoseconds.
Weight::from_parts(398_540_909, 6360)
// Standard Error: 1_330_283
.saturating_add(Weight::from_parts(209_463_636, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(7_u64))
.saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into())))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,26 @@ impl<T: frame_system::Config> pallet_asset_conversion::WeightInfo for WeightInfo
.saturating_add(T::DbWeight::get().writes(4))
.saturating_add(Weight::from_parts(0, 393).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: `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: 381_000_000 picoseconds.
Weight::from_parts(398_540_909, 6360)
// Standard Error: 1_330_283
.saturating_add(Weight::from_parts(209_463_636, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(7_u64))
.saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into())))
}
}
14 changes: 14 additions & 0 deletions prdoc/pr_3251.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: "Asset Conversion: Pool Touch Call"

doc:
- audience: Runtime Dev
description: |
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.

crates:
- name: pallet-asset-conversion
67 changes: 59 additions & 8 deletions substrate/frame/asset-conversion/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
65 changes: 61 additions & 4 deletions substrate/frame/asset-conversion/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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;
Expand All @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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> {
Expand Down
Loading

0 comments on commit e1ec8b9

Please sign in to comment.