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 EnsureOrigin,
31 },
32};
33use frame_system::RawOrigin as SystemOrigin;
34use sp_core::Get;
35use sp_runtime::Permill;
36
37pub trait BenchmarkHelper<AssetKind> {
39 fn create_pair(seed1: u32, seed2: u32) -> (AssetKind, AssetKind);
44}
45
46impl<AssetKind> BenchmarkHelper<AssetKind> for ()
47where
48 AssetKind: From<u32>,
49{
50 fn create_pair(seed1: u32, seed2: u32) -> (AssetKind, AssetKind) {
51 (seed1.into(), seed2.into())
52 }
53}
54
55pub struct NativeOrWithIdFactory<AssetId>(PhantomData<AssetId>);
58impl<AssetId: From<u32> + Ord> BenchmarkHelper<NativeOrWithId<AssetId>>
59 for NativeOrWithIdFactory<AssetId>
60{
61 fn create_pair(seed1: u32, seed2: u32) -> (NativeOrWithId<AssetId>, NativeOrWithId<AssetId>) {
62 if seed1.is_multiple_of(2) {
63 (NativeOrWithId::WithId(seed2.into()), NativeOrWithId::Native)
64 } else {
65 (NativeOrWithId::Native, NativeOrWithId::WithId(seed2.into()))
66 }
67 }
68}
69
70fn valid_liquidity_amount<T: Config>(ed1: T::Balance, ed2: T::Balance) -> (T::Balance, T::Balance)
72where
73 T::Assets: Inspect<T::AccountId>,
74{
75 let l =
76 ed1.max(ed2) + T::MintMinLiquidity::get() + T::MintMinLiquidity::get() + T::Balance::one();
77 (l, l)
78}
79
80fn create_asset<T: Config>(
82 caller: &T::AccountId,
83 asset: &T::AssetKind,
84 amount: T::Balance,
85 is_sufficient: bool,
86) where
87 T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
88{
89 if !T::Assets::asset_exists(asset.clone()) {
90 assert_ok!(T::Assets::create(
91 asset.clone(),
92 caller.clone(),
93 is_sufficient,
94 T::Balance::one()
95 ));
96 }
97 assert_ok!(T::Assets::mint_into(
98 asset.clone(),
99 &caller,
100 amount + T::Assets::minimum_balance(asset.clone())
101 ));
102}
103
104fn create_fee_asset<T: Config>(caller: &T::AccountId)
106where
107 T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
108{
109 let fee_asset = T::PoolSetupFeeAsset::get();
110 if !T::Assets::asset_exists(fee_asset.clone()) {
111 assert_ok!(T::Assets::create(fee_asset.clone(), caller.clone(), true, T::Balance::one()));
112 }
113 assert_ok!(T::Assets::mint_into(
114 fee_asset.clone(),
115 &caller,
116 T::Assets::minimum_balance(fee_asset)
117 ));
118}
119
120fn mint_setup_fee_asset<T: Config>(
122 caller: &T::AccountId,
123 asset1: &T::AssetKind,
124 asset2: &T::AssetKind,
125 lp_token: &T::PoolAssetId,
126) where
127 T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
128{
129 assert_ok!(T::Assets::mint_into(
130 T::PoolSetupFeeAsset::get(),
131 &caller,
132 T::PoolSetupFee::get() +
133 T::Assets::deposit_required(asset1.clone()) +
134 T::Assets::deposit_required(asset2.clone()) +
135 T::PoolAssets::deposit_required(lp_token.clone())
136 ));
137}
138
139fn create_asset_and_pool<T: Config>(
145 caller: &T::AccountId,
146 asset1: &T::AssetKind,
147 asset2: &T::AssetKind,
148) -> (T::PoolAssetId, T::Balance, T::Balance)
149where
150 T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
151{
152 let (liquidity1, liquidity2) = valid_liquidity_amount::<T>(
153 T::Assets::minimum_balance(asset1.clone()),
154 T::Assets::minimum_balance(asset2.clone()),
155 );
156 create_asset::<T>(caller, asset1, liquidity1, true);
157 create_asset::<T>(caller, asset2, liquidity2, true);
158 let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
159
160 mint_setup_fee_asset::<T>(caller, asset1, asset2, &lp_token);
161
162 assert_ok!(AssetConversion::<T>::create_pool(
163 SystemOrigin::Signed(caller.clone()).into(),
164 Box::new(asset1.clone()),
165 Box::new(asset2.clone())
166 ));
167
168 (lp_token, liquidity1, liquidity2)
169}
170
171fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
172 let events = frame_system::Pallet::<T>::events();
173 let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
174 let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
176 assert_eq!(event, &system_event);
177}
178
179#[benchmarks(where T::Assets: Create<T::AccountId> + Mutate<T::AccountId>, T::PoolAssetId: Into<u32>,)]
180mod benchmarks {
181 use super::*;
182
183 #[benchmark]
184 fn create_pool() {
185 let caller: T::AccountId = whitelisted_caller();
186 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
187 create_asset::<T>(&caller, &asset1, T::Assets::minimum_balance(asset1.clone()), true);
188 create_asset::<T>(&caller, &asset2, T::Assets::minimum_balance(asset2.clone()), true);
189
190 let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
191 create_fee_asset::<T>(&caller);
192 mint_setup_fee_asset::<T>(&caller, &asset1, &asset2, &lp_token);
193
194 #[extrinsic_call]
195 _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()));
196
197 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
198 let pool_account = T::PoolLocator::address(&pool_id).unwrap();
199 assert_last_event::<T>(
200 Event::PoolCreated { creator: caller, pool_account, pool_id, lp_token }.into(),
201 );
202 }
203
204 #[benchmark]
205 fn create_pool_with_fee() -> Result<(), BenchmarkError> {
206 let caller: T::AccountId = whitelisted_caller();
207 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
208 create_asset::<T>(&caller, &asset1, T::Assets::minimum_balance(asset1.clone()), true);
209 create_asset::<T>(&caller, &asset2, T::Assets::minimum_balance(asset2.clone()), true);
210
211 let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
212 create_fee_asset::<T>(&caller);
213 mint_setup_fee_asset::<T>(&caller, &asset1, &asset2, &lp_token);
214
215 let fee = Permill::from_percent(1);
216 let origin =
217 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
218
219 #[extrinsic_call]
220 _(
221 origin as T::RuntimeOrigin,
222 caller.clone(),
223 Box::new(asset1.clone()),
224 Box::new(asset2.clone()),
225 fee,
226 );
227
228 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
229 assert_eq!(PoolFees::<T>::get(&pool_id), Some(fee));
230 assert_last_event::<T>(Event::PoolFeeSet { pool_id, fee }.into());
231 Ok(())
232 }
233
234 #[benchmark]
235 fn set_pool_fee() -> Result<(), BenchmarkError> {
236 let caller: T::AccountId = whitelisted_caller();
237 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
238
239 create_fee_asset::<T>(&caller);
240 let _ = create_asset_and_pool::<T>(&caller, &asset1, &asset2);
241
242 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
243 let fee = Permill::from_percent(1);
244 let origin =
245 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
246
247 #[extrinsic_call]
248 _(origin as T::RuntimeOrigin, pool_id.clone(), fee);
249
250 assert_eq!(PoolFees::<T>::get(&pool_id), Some(fee));
251 assert_last_event::<T>(Event::PoolFeeSet { pool_id, fee }.into());
252 Ok(())
253 }
254
255 #[benchmark]
256 fn add_liquidity() {
257 let caller: T::AccountId = whitelisted_caller();
258 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
259
260 create_fee_asset::<T>(&caller);
261 let (lp_token, liquidity1, liquidity2) =
262 create_asset_and_pool::<T>(&caller, &asset1, &asset2);
263
264 #[extrinsic_call]
265 _(
266 SystemOrigin::Signed(caller.clone()),
267 Box::new(asset1.clone()),
268 Box::new(asset2.clone()),
269 liquidity1,
270 liquidity2,
271 T::Balance::one(),
272 T::Balance::zero(),
273 caller.clone(),
274 );
275
276 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).unwrap();
277 let lp_minted =
278 AssetConversion::<T>::calc_lp_amount_for_zero_supply(&liquidity1, &liquidity2).unwrap();
279 assert_eq!(T::PoolAssets::balance(lp_token, &caller), lp_minted);
280 assert_eq!(T::Assets::balance(asset1, &pool_account), liquidity1);
281 assert_eq!(T::Assets::balance(asset2, &pool_account), liquidity2);
282 }
283
284 #[benchmark]
285 fn remove_liquidity() {
286 let caller: T::AccountId = whitelisted_caller();
287 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
288
289 create_fee_asset::<T>(&caller);
290 let (lp_token, liquidity1, liquidity2) =
291 create_asset_and_pool::<T>(&caller, &asset1, &asset2);
292
293 let remove_lp_amount = T::Balance::one();
294
295 assert_ok!(AssetConversion::<T>::add_liquidity(
296 SystemOrigin::Signed(caller.clone()).into(),
297 Box::new(asset1.clone()),
298 Box::new(asset2.clone()),
299 liquidity1,
300 liquidity2,
301 T::Balance::one(),
302 T::Balance::zero(),
303 caller.clone(),
304 ));
305 let total_supply =
306 <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token.clone());
307
308 #[extrinsic_call]
309 _(
310 SystemOrigin::Signed(caller.clone()),
311 Box::new(asset1),
312 Box::new(asset2),
313 remove_lp_amount,
314 T::Balance::zero(),
315 T::Balance::zero(),
316 caller.clone(),
317 );
318
319 let new_total_supply = <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token);
320 assert_eq!(new_total_supply, total_supply - remove_lp_amount);
321 }
322
323 #[benchmark]
324 fn swap_exact_tokens_for_tokens(n: Linear<2, { T::MaxSwapPathLength::get() }>) {
325 let mut swap_amount = T::Balance::one();
326 let mut path = vec![];
327
328 let caller: T::AccountId = whitelisted_caller();
329 create_fee_asset::<T>(&caller);
330 for n in 1..n {
331 let (asset1, asset2) = T::BenchmarkHelper::create_pair(n - 1, n);
332 swap_amount = swap_amount + T::Balance::one();
333 if path.len() == 0 {
334 path = vec![Box::new(asset1.clone()), Box::new(asset2.clone())];
335 } else {
336 path.push(Box::new(asset2.clone()));
337 }
338
339 let (_, liquidity1, liquidity2) = create_asset_and_pool::<T>(&caller, &asset1, &asset2);
340
341 assert_ok!(AssetConversion::<T>::add_liquidity(
342 SystemOrigin::Signed(caller.clone()).into(),
343 Box::new(asset1.clone()),
344 Box::new(asset2.clone()),
345 liquidity1,
346 liquidity2,
347 T::Balance::one(),
348 T::Balance::zero(),
349 caller.clone(),
350 ));
351 }
352
353 let asset_in = *path.first().unwrap().clone();
354 assert_ok!(T::Assets::mint_into(
355 asset_in.clone(),
356 &caller,
357 swap_amount + T::Balance::one()
358 ));
359 let init_caller_balance = T::Assets::balance(asset_in.clone(), &caller);
360
361 #[extrinsic_call]
362 _(
363 SystemOrigin::Signed(caller.clone()),
364 path,
365 swap_amount,
366 T::Balance::one(),
367 caller.clone(),
368 true,
369 );
370
371 let actual_balance = T::Assets::balance(asset_in, &caller);
372 assert_eq!(actual_balance, init_caller_balance - swap_amount);
373 }
374
375 #[benchmark]
376 fn swap_tokens_for_exact_tokens(n: Linear<2, { T::MaxSwapPathLength::get() }>) {
377 let mut max_swap_amount = T::Balance::one();
378 let mut path = vec![];
379
380 let caller: T::AccountId = whitelisted_caller();
381 create_fee_asset::<T>(&caller);
382 for n in 1..n {
383 let (asset1, asset2) = T::BenchmarkHelper::create_pair(n - 1, n);
384 max_swap_amount = max_swap_amount + T::Balance::one() + T::Balance::one();
385 if path.len() == 0 {
386 path = vec![Box::new(asset1.clone()), Box::new(asset2.clone())];
387 } else {
388 path.push(Box::new(asset2.clone()));
389 }
390
391 let (_, liquidity1, liquidity2) = create_asset_and_pool::<T>(&caller, &asset1, &asset2);
392
393 assert_ok!(AssetConversion::<T>::add_liquidity(
394 SystemOrigin::Signed(caller.clone()).into(),
395 Box::new(asset1.clone()),
396 Box::new(asset2.clone()),
397 liquidity1,
398 liquidity2,
399 T::Balance::one(),
400 T::Balance::zero(),
401 caller.clone(),
402 ));
403 }
404
405 let asset_in = *path.first().unwrap().clone();
406 let asset_out = *path.last().unwrap().clone();
407 assert_ok!(T::Assets::mint_into(asset_in, &caller, max_swap_amount));
408 let init_caller_balance = T::Assets::balance(asset_out.clone(), &caller);
409
410 #[extrinsic_call]
411 _(
412 SystemOrigin::Signed(caller.clone()),
413 path,
414 T::Balance::one(),
415 max_swap_amount,
416 caller.clone(),
417 true,
418 );
419
420 let actual_balance = T::Assets::balance(asset_out, &caller);
421 assert_eq!(actual_balance, init_caller_balance + T::Balance::one());
422 }
423
424 #[benchmark]
425 fn touch(n: Linear<0, 3>) {
426 let caller: T::AccountId = whitelisted_caller();
427 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
428 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
429 let pool_account = T::PoolLocator::address(&pool_id).unwrap();
430
431 create_fee_asset::<T>(&caller);
432 create_asset::<T>(&caller, &asset1, <T as Config>::Balance::one(), false);
433 create_asset::<T>(&caller, &asset2, <T as Config>::Balance::one(), false);
434 let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
435 mint_setup_fee_asset::<T>(&caller, &asset1, &asset2, &lp_token);
436
437 assert_ok!(AssetConversion::<T>::create_pool(
438 SystemOrigin::Signed(caller.clone()).into(),
439 Box::new(asset1.clone()),
440 Box::new(asset2.clone())
441 ));
442
443 if n > 0 &&
444 <T as Config>::Assets::deposit_held(asset1.clone(), pool_account.clone()).is_some()
445 {
446 let _ = <T as Config>::Assets::refund(asset1.clone(), pool_account.clone());
447 }
448 if n > 1 &&
449 <T as Config>::Assets::deposit_held(asset2.clone(), pool_account.clone()).is_some()
450 {
451 let _ = <T as Config>::Assets::refund(asset2.clone(), pool_account.clone());
452 }
453 if n > 2 &&
454 <T as Config>::PoolAssets::deposit_held(lp_token.clone(), pool_account.clone())
455 .is_some()
456 {
457 let _ = <T as Config>::PoolAssets::refund(lp_token, pool_account);
458 }
459
460 #[extrinsic_call]
461 _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()));
462
463 assert_last_event::<T>(Event::Touched { pool_id, who: caller }.into());
464 }
465
466 #[benchmark]
467 fn get_reserves() {
468 let caller: T::AccountId = whitelisted_caller();
469 let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
470
471 create_fee_asset::<T>(&caller);
472 let (_, liquidity1, liquidity2) = create_asset_and_pool::<T>(&caller, &asset1, &asset2);
473
474 assert_ok!(AssetConversion::<T>::add_liquidity(
475 SystemOrigin::Signed(caller.clone()).into(),
476 Box::new(asset1.clone()),
477 Box::new(asset2.clone()),
478 liquidity1,
479 liquidity2,
480 T::Balance::one(),
481 T::Balance::zero(),
482 caller.clone(),
483 ));
484
485 #[block]
486 {
487 AssetConversion::<T>::get_reserves(asset1, asset2).expect("pool has liquidity; qed");
488 }
489 }
490
491 impl_benchmark_test_suite!(AssetConversion, crate::mock::new_test_ext(), crate::mock::Test);
492}