1use super::*;
21use crate::Pallet as AssetConversion;
22use alloc::vec;
23use core::marker::PhantomData;
24use frame_benchmarking::{v2::*, whitelisted_caller};
25use frame_support::{
26 assert_ok,
27 traits::{
28 fungible::NativeOrWithId,
29 fungibles::{Create, Inspect, Mutate, Refund},
30 },
31};
32use frame_system::RawOrigin as SystemOrigin;
33use sp_core::Get;
34
35pub trait BenchmarkHelper<AssetKind> {
37 fn create_pair(seed1: u32, seed2: u32) -> (AssetKind, AssetKind);
42}
43
44impl<AssetKind> BenchmarkHelper<AssetKind> for ()
45where
46 AssetKind: From<u32>,
47{
48 fn create_pair(seed1: u32, seed2: u32) -> (AssetKind, AssetKind) {
49 (seed1.into(), seed2.into())
50 }
51}
52
53pub struct NativeOrWithIdFactory<AssetId>(PhantomData<AssetId>);
56impl<AssetId: From<u32> + Ord> BenchmarkHelper<NativeOrWithId<AssetId>>
57 for NativeOrWithIdFactory<AssetId>
58{
59 fn create_pair(seed1: u32, seed2: u32) -> (NativeOrWithId<AssetId>, NativeOrWithId<AssetId>) {
60 if seed1 % 2 == 0 {
61 (NativeOrWithId::WithId(seed2.into()), NativeOrWithId::Native)
62 } else {
63 (NativeOrWithId::Native, NativeOrWithId::WithId(seed2.into()))
64 }
65 }
66}
67
68fn valid_liquidity_amount<T: Config>(ed1: T::Balance, ed2: T::Balance) -> (T::Balance, T::Balance)
70where
71 T::Assets: Inspect<T::AccountId>,
72{
73 let l =
74 ed1.max(ed2) + T::MintMinLiquidity::get() + T::MintMinLiquidity::get() + T::Balance::one();
75 (l, l)
76}
77
78fn create_asset<T: Config>(
80 caller: &T::AccountId,
81 asset: &T::AssetKind,
82 amount: T::Balance,
83 is_sufficient: bool,
84) where
85 T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
86{
87 if !T::Assets::asset_exists(asset.clone()) {
88 assert_ok!(T::Assets::create(
89 asset.clone(),
90 caller.clone(),
91 is_sufficient,
92 T::Balance::one()
93 ));
94 }
95 assert_ok!(T::Assets::mint_into(
96 asset.clone(),
97 &caller,
98 amount + T::Assets::minimum_balance(asset.clone())
99 ));
100}
101
102fn create_fee_asset<T: Config>(caller: &T::AccountId)
104where
105 T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
106{
107 let fee_asset = T::PoolSetupFeeAsset::get();
108 if !T::Assets::asset_exists(fee_asset.clone()) {
109 assert_ok!(T::Assets::create(fee_asset.clone(), caller.clone(), true, T::Balance::one()));
110 }
111 assert_ok!(T::Assets::mint_into(
112 fee_asset.clone(),
113 &caller,
114 T::Assets::minimum_balance(fee_asset)
115 ));
116}
117
118fn mint_setup_fee_asset<T: Config>(
120 caller: &T::AccountId,
121 asset1: &T::AssetKind,
122 asset2: &T::AssetKind,
123 lp_token: &T::PoolAssetId,
124) where
125 T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
126{
127 assert_ok!(T::Assets::mint_into(
128 T::PoolSetupFeeAsset::get(),
129 &caller,
130 T::PoolSetupFee::get() +
131 T::Assets::deposit_required(asset1.clone()) +
132 T::Assets::deposit_required(asset2.clone()) +
133 T::PoolAssets::deposit_required(lp_token.clone())
134 ));
135}
136
137fn create_asset_and_pool<T: Config>(
143 caller: &T::AccountId,
144 asset1: &T::AssetKind,
145 asset2: &T::AssetKind,
146) -> (T::PoolAssetId, T::Balance, T::Balance)
147where
148 T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
149{
150 let (liquidity1, liquidity2) = valid_liquidity_amount::<T>(
151 T::Assets::minimum_balance(asset1.clone()),
152 T::Assets::minimum_balance(asset2.clone()),
153 );
154 create_asset::<T>(caller, asset1, liquidity1, true);
155 create_asset::<T>(caller, asset2, liquidity2, true);
156 let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
157
158 mint_setup_fee_asset::<T>(caller, asset1, asset2, &lp_token);
159
160 assert_ok!(AssetConversion::<T>::create_pool(
161 SystemOrigin::Signed(caller.clone()).into(),
162 Box::new(asset1.clone()),
163 Box::new(asset2.clone())
164 ));
165
166 (lp_token, liquidity1, liquidity2)
167}
168
169fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
170 let events = frame_system::Pallet::<T>::events();
171 let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
172 let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
174 assert_eq!(event, &system_event);
175}
176
177#[benchmarks(where T::Assets: Create<T::AccountId> + Mutate<T::AccountId>, T::PoolAssetId: Into<u32>,)]
178mod benchmarks {
179 use super::*;
180
181 #[benchmark]
182 fn create_pool() {
183 let caller: T::AccountId = whitelisted_caller();
184 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
185 create_asset::<T>(&caller, &asset1, T::Assets::minimum_balance(asset1.clone()), true);
186 create_asset::<T>(&caller, &asset2, T::Assets::minimum_balance(asset2.clone()), true);
187
188 let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
189 create_fee_asset::<T>(&caller);
190 mint_setup_fee_asset::<T>(&caller, &asset1, &asset2, &lp_token);
191
192 #[extrinsic_call]
193 _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()));
194
195 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
196 let pool_account = T::PoolLocator::address(&pool_id).unwrap();
197 assert_last_event::<T>(
198 Event::PoolCreated { creator: caller, pool_account, pool_id, lp_token }.into(),
199 );
200 }
201
202 #[benchmark]
203 fn add_liquidity() {
204 let caller: T::AccountId = whitelisted_caller();
205 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
206
207 create_fee_asset::<T>(&caller);
208 let (lp_token, liquidity1, liquidity2) =
209 create_asset_and_pool::<T>(&caller, &asset1, &asset2);
210
211 #[extrinsic_call]
212 _(
213 SystemOrigin::Signed(caller.clone()),
214 Box::new(asset1.clone()),
215 Box::new(asset2.clone()),
216 liquidity1,
217 liquidity2,
218 T::Balance::one(),
219 T::Balance::zero(),
220 caller.clone(),
221 );
222
223 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).unwrap();
224 let lp_minted =
225 AssetConversion::<T>::calc_lp_amount_for_zero_supply(&liquidity1, &liquidity2).unwrap();
226 assert_eq!(T::PoolAssets::balance(lp_token, &caller), lp_minted);
227 assert_eq!(T::Assets::balance(asset1, &pool_account), liquidity1);
228 assert_eq!(T::Assets::balance(asset2, &pool_account), liquidity2);
229 }
230
231 #[benchmark]
232 fn remove_liquidity() {
233 let caller: T::AccountId = whitelisted_caller();
234 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
235
236 create_fee_asset::<T>(&caller);
237 let (lp_token, liquidity1, liquidity2) =
238 create_asset_and_pool::<T>(&caller, &asset1, &asset2);
239
240 let remove_lp_amount = T::Balance::one();
241
242 assert_ok!(AssetConversion::<T>::add_liquidity(
243 SystemOrigin::Signed(caller.clone()).into(),
244 Box::new(asset1.clone()),
245 Box::new(asset2.clone()),
246 liquidity1,
247 liquidity2,
248 T::Balance::one(),
249 T::Balance::zero(),
250 caller.clone(),
251 ));
252 let total_supply =
253 <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token.clone());
254
255 #[extrinsic_call]
256 _(
257 SystemOrigin::Signed(caller.clone()),
258 Box::new(asset1),
259 Box::new(asset2),
260 remove_lp_amount,
261 T::Balance::zero(),
262 T::Balance::zero(),
263 caller.clone(),
264 );
265
266 let new_total_supply = <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token);
267 assert_eq!(new_total_supply, total_supply - remove_lp_amount);
268 }
269
270 #[benchmark]
271 fn swap_exact_tokens_for_tokens(n: Linear<2, { T::MaxSwapPathLength::get() }>) {
272 let mut swap_amount = T::Balance::one();
273 let mut path = vec![];
274
275 let caller: T::AccountId = whitelisted_caller();
276 create_fee_asset::<T>(&caller);
277 for n in 1..n {
278 let (asset1, asset2) = T::BenchmarkHelper::create_pair(n - 1, n);
279 swap_amount = swap_amount + T::Balance::one();
280 if path.len() == 0 {
281 path = vec![Box::new(asset1.clone()), Box::new(asset2.clone())];
282 } else {
283 path.push(Box::new(asset2.clone()));
284 }
285
286 let (_, liquidity1, liquidity2) = create_asset_and_pool::<T>(&caller, &asset1, &asset2);
287
288 assert_ok!(AssetConversion::<T>::add_liquidity(
289 SystemOrigin::Signed(caller.clone()).into(),
290 Box::new(asset1.clone()),
291 Box::new(asset2.clone()),
292 liquidity1,
293 liquidity2,
294 T::Balance::one(),
295 T::Balance::zero(),
296 caller.clone(),
297 ));
298 }
299
300 let asset_in = *path.first().unwrap().clone();
301 assert_ok!(T::Assets::mint_into(
302 asset_in.clone(),
303 &caller,
304 swap_amount + T::Balance::one()
305 ));
306 let init_caller_balance = T::Assets::balance(asset_in.clone(), &caller);
307
308 #[extrinsic_call]
309 _(
310 SystemOrigin::Signed(caller.clone()),
311 path,
312 swap_amount,
313 T::Balance::one(),
314 caller.clone(),
315 true,
316 );
317
318 let actual_balance = T::Assets::balance(asset_in, &caller);
319 assert_eq!(actual_balance, init_caller_balance - swap_amount);
320 }
321
322 #[benchmark]
323 fn swap_tokens_for_exact_tokens(n: Linear<2, { T::MaxSwapPathLength::get() }>) {
324 let mut max_swap_amount = T::Balance::one();
325 let mut path = vec![];
326
327 let caller: T::AccountId = whitelisted_caller();
328 create_fee_asset::<T>(&caller);
329 for n in 1..n {
330 let (asset1, asset2) = T::BenchmarkHelper::create_pair(n - 1, n);
331 max_swap_amount = max_swap_amount + T::Balance::one() + T::Balance::one();
332 if path.len() == 0 {
333 path = vec![Box::new(asset1.clone()), Box::new(asset2.clone())];
334 } else {
335 path.push(Box::new(asset2.clone()));
336 }
337
338 let (_, liquidity1, liquidity2) = create_asset_and_pool::<T>(&caller, &asset1, &asset2);
339
340 assert_ok!(AssetConversion::<T>::add_liquidity(
341 SystemOrigin::Signed(caller.clone()).into(),
342 Box::new(asset1.clone()),
343 Box::new(asset2.clone()),
344 liquidity1,
345 liquidity2,
346 T::Balance::one(),
347 T::Balance::zero(),
348 caller.clone(),
349 ));
350 }
351
352 let asset_in = *path.first().unwrap().clone();
353 let asset_out = *path.last().unwrap().clone();
354 assert_ok!(T::Assets::mint_into(asset_in, &caller, max_swap_amount));
355 let init_caller_balance = T::Assets::balance(asset_out.clone(), &caller);
356
357 #[extrinsic_call]
358 _(
359 SystemOrigin::Signed(caller.clone()),
360 path,
361 T::Balance::one(),
362 max_swap_amount,
363 caller.clone(),
364 true,
365 );
366
367 let actual_balance = T::Assets::balance(asset_out, &caller);
368 assert_eq!(actual_balance, init_caller_balance + T::Balance::one());
369 }
370
371 #[benchmark]
372 fn touch(n: Linear<0, 3>) {
373 let caller: T::AccountId = whitelisted_caller();
374 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
375 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
376 let pool_account = T::PoolLocator::address(&pool_id).unwrap();
377
378 create_fee_asset::<T>(&caller);
379 create_asset::<T>(&caller, &asset1, <T as Config>::Balance::one(), false);
380 create_asset::<T>(&caller, &asset2, <T as Config>::Balance::one(), false);
381 let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
382 mint_setup_fee_asset::<T>(&caller, &asset1, &asset2, &lp_token);
383
384 assert_ok!(AssetConversion::<T>::create_pool(
385 SystemOrigin::Signed(caller.clone()).into(),
386 Box::new(asset1.clone()),
387 Box::new(asset2.clone())
388 ));
389
390 if n > 0 &&
391 <T as Config>::Assets::deposit_held(asset1.clone(), pool_account.clone()).is_some()
392 {
393 let _ = <T as Config>::Assets::refund(asset1.clone(), pool_account.clone());
394 }
395 if n > 1 &&
396 <T as Config>::Assets::deposit_held(asset2.clone(), pool_account.clone()).is_some()
397 {
398 let _ = <T as Config>::Assets::refund(asset2.clone(), pool_account.clone());
399 }
400 if n > 2 &&
401 <T as Config>::PoolAssets::deposit_held(lp_token.clone(), pool_account.clone())
402 .is_some()
403 {
404 let _ = <T as Config>::PoolAssets::refund(lp_token, pool_account);
405 }
406
407 #[extrinsic_call]
408 _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()));
409
410 assert_last_event::<T>(Event::Touched { pool_id, who: caller }.into());
411 }
412
413 impl_benchmark_test_suite!(AssetConversion, crate::mock::new_test_ext(), crate::mock::Test);
414}