1#![deny(missing_docs)]
55#![cfg_attr(not(feature = "std"), no_std)]
56
57#[cfg(feature = "runtime-benchmarks")]
58mod benchmarking;
59#[cfg(test)]
60mod mock;
61mod swap;
62#[cfg(test)]
63mod tests;
64mod types;
65pub mod weights;
66#[cfg(feature = "runtime-benchmarks")]
67pub use benchmarking::{BenchmarkHelper, NativeOrWithIdFactory};
68pub use pallet::*;
69pub use swap::*;
70pub use types::*;
71pub use weights::WeightInfo;
72
73extern crate alloc;
74
75use alloc::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec};
76use codec::Codec;
77use frame_support::{
78 storage::{with_storage_layer, with_transaction},
79 traits::{
80 fungibles::{Balanced, Create, Credit, Inspect, Mutate},
81 tokens::{
82 AssetId, Balance,
83 Fortitude::Polite,
84 Precision::Exact,
85 Preservation::{Expendable, Preserve},
86 },
87 AccountTouch, Incrementable, OnUnbalanced,
88 },
89 PalletId,
90};
91use sp_core::Get;
92use sp_runtime::{
93 traits::{
94 CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Ensure, IntegerSquareRoot, MaybeDisplay,
95 One, TrailingZeroInput, Zero,
96 },
97 DispatchError, Saturating, TokenError, TransactionOutcome,
98};
99
100#[frame_support::pallet]
101pub mod pallet {
102 use super::*;
103 use frame_support::{
104 pallet_prelude::{DispatchResult, *},
105 traits::fungibles::Refund,
106 };
107 use frame_system::pallet_prelude::*;
108 use sp_arithmetic::{traits::Unsigned, Permill};
109
110 #[pallet::pallet]
111 pub struct Pallet<T>(_);
112
113 #[pallet::config]
114 pub trait Config: frame_system::Config {
115 #[allow(deprecated)]
117 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
118
119 type Balance: Balance;
121
122 type HigherPrecisionBalance: IntegerSquareRoot
124 + One
125 + Ensure
126 + Unsigned
127 + From<u32>
128 + From<Self::Balance>
129 + TryInto<Self::Balance>;
130
131 type AssetKind: Parameter + MaxEncodedLen;
134
135 type Assets: Inspect<Self::AccountId, AssetId = Self::AssetKind, Balance = Self::Balance>
137 + Mutate<Self::AccountId>
138 + AccountTouch<Self::AssetKind, Self::AccountId, Balance = Self::Balance>
139 + Balanced<Self::AccountId>
140 + Refund<Self::AccountId, AssetId = Self::AssetKind>;
141
142 type PoolId: Parameter + MaxEncodedLen + Ord;
144
145 type PoolLocator: PoolLocator<Self::AccountId, Self::AssetKind, Self::PoolId>;
150
151 type PoolAssetId: AssetId + PartialOrd + Incrementable + From<u32>;
153
154 type PoolAssets: Inspect<Self::AccountId, AssetId = Self::PoolAssetId, Balance = Self::Balance>
157 + Create<Self::AccountId>
158 + Mutate<Self::AccountId>
159 + AccountTouch<Self::PoolAssetId, Self::AccountId, Balance = Self::Balance>
160 + Refund<Self::AccountId, AssetId = Self::PoolAssetId>;
161
162 #[pallet::constant]
164 type LPFee: Get<u32>;
165
166 #[pallet::constant]
168 type PoolSetupFee: Get<Self::Balance>;
169
170 #[pallet::constant]
172 type PoolSetupFeeAsset: Get<Self::AssetKind>;
173
174 type PoolSetupFeeTarget: OnUnbalanced<CreditOf<Self>>;
176
177 #[pallet::constant]
179 type LiquidityWithdrawalFee: Get<Permill>;
180
181 #[pallet::constant]
183 type MintMinLiquidity: Get<Self::Balance>;
184
185 #[pallet::constant]
187 type MaxSwapPathLength: Get<u32>;
188
189 #[pallet::constant]
191 type PalletId: Get<PalletId>;
192
193 type WeightInfo: WeightInfo;
195
196 #[cfg(feature = "runtime-benchmarks")]
198 type BenchmarkHelper: BenchmarkHelper<Self::AssetKind>;
199 }
200
201 #[pallet::storage]
204 pub type Pools<T: Config> =
205 StorageMap<_, Blake2_128Concat, T::PoolId, PoolInfo<T::PoolAssetId>, OptionQuery>;
206
207 #[pallet::storage]
210 pub type NextPoolAssetId<T: Config> = StorageValue<_, T::PoolAssetId, OptionQuery>;
211
212 #[pallet::event]
214 #[pallet::generate_deposit(pub(super) fn deposit_event)]
215 pub enum Event<T: Config> {
216 PoolCreated {
218 creator: T::AccountId,
220 pool_id: T::PoolId,
223 pool_account: T::AccountId,
225 lp_token: T::PoolAssetId,
228 },
229
230 LiquidityAdded {
232 who: T::AccountId,
234 mint_to: T::AccountId,
236 pool_id: T::PoolId,
238 amount1_provided: T::Balance,
240 amount2_provided: T::Balance,
242 lp_token: T::PoolAssetId,
244 lp_token_minted: T::Balance,
246 },
247
248 LiquidityRemoved {
250 who: T::AccountId,
252 withdraw_to: T::AccountId,
254 pool_id: T::PoolId,
256 amount1: T::Balance,
258 amount2: T::Balance,
260 lp_token: T::PoolAssetId,
262 lp_token_burned: T::Balance,
264 withdrawal_fee: Permill,
266 },
267 SwapExecuted {
270 who: T::AccountId,
272 send_to: T::AccountId,
274 amount_in: T::Balance,
276 amount_out: T::Balance,
278 path: BalancePath<T>,
281 },
282 SwapCreditExecuted {
284 amount_in: T::Balance,
286 amount_out: T::Balance,
288 path: BalancePath<T>,
291 },
292 Touched {
294 pool_id: T::PoolId,
296 who: T::AccountId,
298 },
299 }
300
301 #[pallet::error]
302 pub enum Error<T> {
303 InvalidAssetPair,
305 PoolExists,
307 WrongDesiredAmount,
309 AmountOneLessThanMinimal,
312 AmountTwoLessThanMinimal,
315 ReserveLeftLessThanMinimal,
318 AmountOutTooHigh,
320 PoolNotFound,
322 Overflow,
324 AssetOneDepositDidNotMeetMinimum,
326 AssetTwoDepositDidNotMeetMinimum,
328 AssetOneWithdrawalDidNotMeetMinimum,
330 AssetTwoWithdrawalDidNotMeetMinimum,
332 OptimalAmountLessThanDesired,
334 InsufficientLiquidityMinted,
336 ZeroLiquidity,
338 ZeroAmount,
340 ProvidedMinimumNotSufficientForSwap,
342 ProvidedMaximumNotSufficientForSwap,
344 InvalidPath,
346 NonUniquePath,
348 IncorrectPoolAssetId,
350 BelowMinimum,
352 }
353
354 #[pallet::hooks]
355 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
356 fn integrity_test() {
357 assert!(
358 T::MaxSwapPathLength::get() > 1,
359 "the `MaxSwapPathLength` should be greater than 1",
360 );
361 }
362 }
363
364 #[pallet::call]
366 impl<T: Config> Pallet<T> {
367 #[pallet::call_index(0)]
372 #[pallet::weight(T::WeightInfo::create_pool())]
373 pub fn create_pool(
374 origin: OriginFor<T>,
375 asset1: Box<T::AssetKind>,
376 asset2: Box<T::AssetKind>,
377 ) -> DispatchResult {
378 let sender = ensure_signed(origin)?;
379 ensure!(asset1 != asset2, Error::<T>::InvalidAssetPair);
380
381 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
383 .map_err(|_| Error::<T>::InvalidAssetPair)?;
384 ensure!(!Pools::<T>::contains_key(&pool_id), Error::<T>::PoolExists);
385
386 let pool_account =
387 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
388
389 let fee =
391 Self::withdraw(T::PoolSetupFeeAsset::get(), &sender, T::PoolSetupFee::get(), true)?;
392 T::PoolSetupFeeTarget::on_unbalanced(fee);
393
394 if T::Assets::should_touch(*asset1.clone(), &pool_account) {
395 T::Assets::touch(*asset1, &pool_account, &sender)?
396 };
397
398 if T::Assets::should_touch(*asset2.clone(), &pool_account) {
399 T::Assets::touch(*asset2, &pool_account, &sender)?
400 };
401
402 let lp_token = NextPoolAssetId::<T>::get()
403 .or(T::PoolAssetId::initial_value())
404 .ok_or(Error::<T>::IncorrectPoolAssetId)?;
405 let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?;
406 NextPoolAssetId::<T>::set(Some(next_lp_token_id));
407
408 T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
409 if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) {
410 T::PoolAssets::touch(lp_token.clone(), &pool_account, &sender)?
411 };
412
413 let pool_info = PoolInfo { lp_token: lp_token.clone() };
414 Pools::<T>::insert(pool_id.clone(), pool_info);
415
416 Self::deposit_event(Event::PoolCreated {
417 creator: sender,
418 pool_id,
419 pool_account,
420 lp_token,
421 });
422
423 Ok(())
424 }
425
426 #[pallet::call_index(1)]
441 #[pallet::weight(T::WeightInfo::add_liquidity())]
442 pub fn add_liquidity(
443 origin: OriginFor<T>,
444 asset1: Box<T::AssetKind>,
445 asset2: Box<T::AssetKind>,
446 amount1_desired: T::Balance,
447 amount2_desired: T::Balance,
448 amount1_min: T::Balance,
449 amount2_min: T::Balance,
450 mint_to: T::AccountId,
451 ) -> DispatchResult {
452 let sender = ensure_signed(origin)?;
453
454 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
455 .map_err(|_| Error::<T>::InvalidAssetPair)?;
456
457 ensure!(
458 amount1_desired > Zero::zero() && amount2_desired > Zero::zero(),
459 Error::<T>::WrongDesiredAmount
460 );
461
462 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
463 let pool_account =
464 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
465
466 let reserve1 = Self::get_balance(&pool_account, *asset1.clone());
467 let reserve2 = Self::get_balance(&pool_account, *asset2.clone());
468
469 let amount1: T::Balance;
470 let amount2: T::Balance;
471 if reserve1.is_zero() || reserve2.is_zero() {
472 amount1 = amount1_desired;
473 amount2 = amount2_desired;
474 } else {
475 let amount2_optimal = Self::quote(&amount1_desired, &reserve1, &reserve2)?;
476
477 if amount2_optimal <= amount2_desired {
478 ensure!(
479 amount2_optimal >= amount2_min,
480 Error::<T>::AssetTwoDepositDidNotMeetMinimum
481 );
482 amount1 = amount1_desired;
483 amount2 = amount2_optimal;
484 } else {
485 let amount1_optimal = Self::quote(&amount2_desired, &reserve2, &reserve1)?;
486 ensure!(
487 amount1_optimal <= amount1_desired,
488 Error::<T>::OptimalAmountLessThanDesired
489 );
490 ensure!(
491 amount1_optimal >= amount1_min,
492 Error::<T>::AssetOneDepositDidNotMeetMinimum
493 );
494 amount1 = amount1_optimal;
495 amount2 = amount2_desired;
496 }
497 }
498
499 ensure!(
500 amount1.saturating_add(reserve1) >= T::Assets::minimum_balance(*asset1.clone()),
501 Error::<T>::AmountOneLessThanMinimal
502 );
503 ensure!(
504 amount2.saturating_add(reserve2) >= T::Assets::minimum_balance(*asset2.clone()),
505 Error::<T>::AmountTwoLessThanMinimal
506 );
507
508 T::Assets::transfer(*asset1, &sender, &pool_account, amount1, Preserve)?;
509 T::Assets::transfer(*asset2, &sender, &pool_account, amount2, Preserve)?;
510
511 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
512
513 let lp_token_amount: T::Balance;
514 if total_supply.is_zero() {
515 lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?;
516 T::PoolAssets::mint_into(
517 pool.lp_token.clone(),
518 &pool_account,
519 T::MintMinLiquidity::get(),
520 )?;
521 } else {
522 let side1 = Self::mul_div(&amount1, &total_supply, &reserve1)?;
523 let side2 = Self::mul_div(&amount2, &total_supply, &reserve2)?;
524 lp_token_amount = side1.min(side2);
525 }
526
527 ensure!(
528 lp_token_amount > T::MintMinLiquidity::get(),
529 Error::<T>::InsufficientLiquidityMinted
530 );
531
532 T::PoolAssets::mint_into(pool.lp_token.clone(), &mint_to, lp_token_amount)?;
533
534 Self::deposit_event(Event::LiquidityAdded {
535 who: sender,
536 mint_to,
537 pool_id,
538 amount1_provided: amount1,
539 amount2_provided: amount2,
540 lp_token: pool.lp_token,
541 lp_token_minted: lp_token_amount,
542 });
543
544 Ok(())
545 }
546
547 #[pallet::call_index(2)]
551 #[pallet::weight(T::WeightInfo::remove_liquidity())]
552 pub fn remove_liquidity(
553 origin: OriginFor<T>,
554 asset1: Box<T::AssetKind>,
555 asset2: Box<T::AssetKind>,
556 lp_token_burn: T::Balance,
557 amount1_min_receive: T::Balance,
558 amount2_min_receive: T::Balance,
559 withdraw_to: T::AccountId,
560 ) -> DispatchResult {
561 let sender = ensure_signed(origin)?;
562
563 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
564 .map_err(|_| Error::<T>::InvalidAssetPair)?;
565
566 ensure!(lp_token_burn > Zero::zero(), Error::<T>::ZeroLiquidity);
567
568 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
569
570 let pool_account =
571 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
572 let reserve1 = Self::get_balance(&pool_account, *asset1.clone());
573 let reserve2 = Self::get_balance(&pool_account, *asset2.clone());
574
575 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
576 let withdrawal_fee_amount = T::LiquidityWithdrawalFee::get() * lp_token_burn;
577 let lp_redeem_amount = lp_token_burn.saturating_sub(withdrawal_fee_amount);
578
579 let amount1 = Self::mul_div(&lp_redeem_amount, &reserve1, &total_supply)?;
580 let amount2 = Self::mul_div(&lp_redeem_amount, &reserve2, &total_supply)?;
581
582 ensure!(
583 !amount1.is_zero() && amount1 >= amount1_min_receive,
584 Error::<T>::AssetOneWithdrawalDidNotMeetMinimum
585 );
586 ensure!(
587 !amount2.is_zero() && amount2 >= amount2_min_receive,
588 Error::<T>::AssetTwoWithdrawalDidNotMeetMinimum
589 );
590 let reserve1_left = reserve1.saturating_sub(amount1);
591 let reserve2_left = reserve2.saturating_sub(amount2);
592 ensure!(
593 reserve1_left >= T::Assets::minimum_balance(*asset1.clone()),
594 Error::<T>::ReserveLeftLessThanMinimal
595 );
596 ensure!(
597 reserve2_left >= T::Assets::minimum_balance(*asset2.clone()),
598 Error::<T>::ReserveLeftLessThanMinimal
599 );
600
601 T::PoolAssets::burn_from(
603 pool.lp_token.clone(),
604 &sender,
605 lp_token_burn,
606 Expendable,
607 Exact,
608 Polite,
609 )?;
610
611 T::Assets::transfer(*asset1, &pool_account, &withdraw_to, amount1, Expendable)?;
612 T::Assets::transfer(*asset2, &pool_account, &withdraw_to, amount2, Expendable)?;
613
614 Self::deposit_event(Event::LiquidityRemoved {
615 who: sender,
616 withdraw_to,
617 pool_id,
618 amount1,
619 amount2,
620 lp_token: pool.lp_token,
621 lp_token_burned: lp_token_burn,
622 withdrawal_fee: T::LiquidityWithdrawalFee::get(),
623 });
624
625 Ok(())
626 }
627
628 #[pallet::call_index(3)]
635 #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens(path.len() as u32))]
636 pub fn swap_exact_tokens_for_tokens(
637 origin: OriginFor<T>,
638 path: Vec<Box<T::AssetKind>>,
639 amount_in: T::Balance,
640 amount_out_min: T::Balance,
641 send_to: T::AccountId,
642 keep_alive: bool,
643 ) -> DispatchResult {
644 let sender = ensure_signed(origin)?;
645 Self::do_swap_exact_tokens_for_tokens(
646 sender,
647 path.into_iter().map(|a| *a).collect(),
648 amount_in,
649 Some(amount_out_min),
650 send_to,
651 keep_alive,
652 )?;
653 Ok(())
654 }
655
656 #[pallet::call_index(4)]
663 #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens(path.len() as u32))]
664 pub fn swap_tokens_for_exact_tokens(
665 origin: OriginFor<T>,
666 path: Vec<Box<T::AssetKind>>,
667 amount_out: T::Balance,
668 amount_in_max: T::Balance,
669 send_to: T::AccountId,
670 keep_alive: bool,
671 ) -> DispatchResult {
672 let sender = ensure_signed(origin)?;
673 Self::do_swap_tokens_for_exact_tokens(
674 sender,
675 path.into_iter().map(|a| *a).collect(),
676 amount_out,
677 Some(amount_in_max),
678 send_to,
679 keep_alive,
680 )?;
681 Ok(())
682 }
683
684 #[pallet::call_index(5)]
696 #[pallet::weight(T::WeightInfo::touch(3))]
697 pub fn touch(
698 origin: OriginFor<T>,
699 asset1: Box<T::AssetKind>,
700 asset2: Box<T::AssetKind>,
701 ) -> DispatchResultWithPostInfo {
702 let who = ensure_signed(origin)?;
703
704 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
705 .map_err(|_| Error::<T>::InvalidAssetPair)?;
706 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
707 let pool_account =
708 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
709
710 let mut refunds_number: u32 = 0;
711 if T::Assets::should_touch(*asset1.clone(), &pool_account) {
712 T::Assets::touch(*asset1, &pool_account, &who)?;
713 refunds_number += 1;
714 }
715 if T::Assets::should_touch(*asset2.clone(), &pool_account) {
716 T::Assets::touch(*asset2, &pool_account, &who)?;
717 refunds_number += 1;
718 }
719 if T::PoolAssets::should_touch(pool.lp_token.clone(), &pool_account) {
720 T::PoolAssets::touch(pool.lp_token, &pool_account, &who)?;
721 refunds_number += 1;
722 }
723 Self::deposit_event(Event::Touched { pool_id, who });
724 Ok(Some(T::WeightInfo::touch(refunds_number)).into())
725 }
726 }
727
728 impl<T: Config> Pallet<T> {
729 pub(crate) fn do_swap_exact_tokens_for_tokens(
742 sender: T::AccountId,
743 path: Vec<T::AssetKind>,
744 amount_in: T::Balance,
745 amount_out_min: Option<T::Balance>,
746 send_to: T::AccountId,
747 keep_alive: bool,
748 ) -> Result<T::Balance, DispatchError> {
749 ensure!(amount_in > Zero::zero(), Error::<T>::ZeroAmount);
750 if let Some(amount_out_min) = amount_out_min {
751 ensure!(amount_out_min > Zero::zero(), Error::<T>::ZeroAmount);
752 }
753
754 Self::validate_swap_path(&path)?;
755 let path = Self::balance_path_from_amount_in(amount_in, path)?;
756
757 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
758 if let Some(amount_out_min) = amount_out_min {
759 ensure!(
760 amount_out >= amount_out_min,
761 Error::<T>::ProvidedMinimumNotSufficientForSwap
762 );
763 }
764
765 Self::swap(&sender, &path, &send_to, keep_alive)?;
766
767 Self::deposit_event(Event::SwapExecuted {
768 who: sender,
769 send_to,
770 amount_in,
771 amount_out,
772 path,
773 });
774 Ok(amount_out)
775 }
776
777 pub(crate) fn do_swap_tokens_for_exact_tokens(
790 sender: T::AccountId,
791 path: Vec<T::AssetKind>,
792 amount_out: T::Balance,
793 amount_in_max: Option<T::Balance>,
794 send_to: T::AccountId,
795 keep_alive: bool,
796 ) -> Result<T::Balance, DispatchError> {
797 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
798 if let Some(amount_in_max) = amount_in_max {
799 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
800 }
801
802 Self::validate_swap_path(&path)?;
803 let path = Self::balance_path_from_amount_out(amount_out, path)?;
804
805 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
806 if let Some(amount_in_max) = amount_in_max {
807 ensure!(
808 amount_in <= amount_in_max,
809 Error::<T>::ProvidedMaximumNotSufficientForSwap
810 );
811 }
812
813 Self::swap(&sender, &path, &send_to, keep_alive)?;
814
815 Self::deposit_event(Event::SwapExecuted {
816 who: sender,
817 send_to,
818 amount_in,
819 amount_out,
820 path,
821 });
822
823 Ok(amount_in)
824 }
825
826 pub(crate) fn do_swap_exact_credit_tokens_for_tokens(
837 path: Vec<T::AssetKind>,
838 credit_in: CreditOf<T>,
839 amount_out_min: Option<T::Balance>,
840 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
841 let amount_in = credit_in.peek();
842 let inspect_path = |credit_asset| {
843 ensure!(
844 path.first().map_or(false, |a| *a == credit_asset),
845 Error::<T>::InvalidPath
846 );
847 ensure!(!amount_in.is_zero(), Error::<T>::ZeroAmount);
848 ensure!(amount_out_min.map_or(true, |a| !a.is_zero()), Error::<T>::ZeroAmount);
849
850 Self::validate_swap_path(&path)?;
851 let path = Self::balance_path_from_amount_in(amount_in, path)?;
852
853 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
854 ensure!(
855 amount_out_min.map_or(true, |a| amount_out >= a),
856 Error::<T>::ProvidedMinimumNotSufficientForSwap
857 );
858 Ok((path, amount_out))
859 };
860 let (path, amount_out) = match inspect_path(credit_in.asset()) {
861 Ok((p, a)) => (p, a),
862 Err(e) => return Err((credit_in, e)),
863 };
864
865 let credit_out = Self::credit_swap(credit_in, &path)?;
866
867 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
868
869 Ok(credit_out)
870 }
871
872 pub(crate) fn do_swap_credit_tokens_for_exact_tokens(
885 path: Vec<T::AssetKind>,
886 credit_in: CreditOf<T>,
887 amount_out: T::Balance,
888 ) -> Result<(CreditOf<T>, CreditOf<T>), (CreditOf<T>, DispatchError)> {
889 let amount_in_max = credit_in.peek();
890 let inspect_path = |credit_asset| {
891 ensure!(
892 path.first().map_or(false, |a| a == &credit_asset),
893 Error::<T>::InvalidPath
894 );
895 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
896 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
897
898 Self::validate_swap_path(&path)?;
899 let path = Self::balance_path_from_amount_out(amount_out, path)?;
900
901 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
902 ensure!(
903 amount_in <= amount_in_max,
904 Error::<T>::ProvidedMaximumNotSufficientForSwap
905 );
906
907 Ok((path, amount_in))
908 };
909 let (path, amount_in) = match inspect_path(credit_in.asset()) {
910 Ok((p, a)) => (p, a),
911 Err(e) => return Err((credit_in, e)),
912 };
913
914 let (credit_in, credit_change) = credit_in.split(amount_in);
915 let credit_out = Self::credit_swap(credit_in, &path)?;
916
917 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
918
919 Ok((credit_out, credit_change))
920 }
921
922 fn swap(
930 sender: &T::AccountId,
931 path: &BalancePath<T>,
932 send_to: &T::AccountId,
933 keep_alive: bool,
934 ) -> Result<(), DispatchError> {
935 let (asset_in, amount_in) = path.first().ok_or(Error::<T>::InvalidPath)?;
936 let credit_in = Self::withdraw(asset_in.clone(), sender, *amount_in, keep_alive)?;
937
938 let credit_out = Self::credit_swap(credit_in, path).map_err(|(_, e)| e)?;
939 T::Assets::resolve(send_to, credit_out).map_err(|_| Error::<T>::BelowMinimum)?;
940
941 Ok(())
942 }
943
944 fn credit_swap(
956 credit_in: CreditOf<T>,
957 path: &BalancePath<T>,
958 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
959 let resolve_path = || -> Result<CreditOf<T>, DispatchError> {
960 for pos in 0..=path.len() {
961 if let Some([(asset1, _), (asset2, amount_out)]) = path.get(pos..=pos + 1) {
962 let pool_from = T::PoolLocator::pool_address(asset1, asset2)
963 .map_err(|_| Error::<T>::InvalidAssetPair)?;
964
965 if let Some((asset3, _)) = path.get(pos + 2) {
966 let pool_to = T::PoolLocator::pool_address(asset2, asset3)
967 .map_err(|_| Error::<T>::InvalidAssetPair)?;
968
969 T::Assets::transfer(
970 asset2.clone(),
971 &pool_from,
972 &pool_to,
973 *amount_out,
974 Preserve,
975 )?;
976 } else {
977 let credit_out =
978 Self::withdraw(asset2.clone(), &pool_from, *amount_out, true)?;
979 return Ok(credit_out)
980 }
981 }
982 }
983 Err(Error::<T>::InvalidPath.into())
984 };
985
986 let credit_out = match resolve_path() {
987 Ok(c) => c,
988 Err(e) => return Err((credit_in, e)),
989 };
990
991 let pool_to = if let Some([(asset1, _), (asset2, _)]) = path.get(0..2) {
992 match T::PoolLocator::pool_address(asset1, asset2) {
993 Ok(address) => address,
994 Err(_) => return Err((credit_in, Error::<T>::InvalidAssetPair.into())),
995 }
996 } else {
997 return Err((credit_in, Error::<T>::InvalidPath.into()))
998 };
999
1000 T::Assets::resolve(&pool_to, credit_in)
1001 .map_err(|c| (c, Error::<T>::BelowMinimum.into()))?;
1002
1003 Ok(credit_out)
1004 }
1005
1006 fn withdraw(
1008 asset: T::AssetKind,
1009 who: &T::AccountId,
1010 value: T::Balance,
1011 keep_alive: bool,
1012 ) -> Result<CreditOf<T>, DispatchError> {
1013 let preservation = match keep_alive {
1014 true => Preserve,
1015 false => Expendable,
1016 };
1017 if preservation == Preserve {
1018 let free = T::Assets::reducible_balance(asset.clone(), who, preservation, Polite);
1021 ensure!(free >= value, TokenError::NotExpendable);
1022 }
1023 T::Assets::withdraw(asset, who, value, Exact, preservation, Polite)
1024 }
1025
1026 fn get_balance(owner: &T::AccountId, asset: T::AssetKind) -> T::Balance {
1029 T::Assets::reducible_balance(asset, owner, Expendable, Polite)
1030 }
1031
1032 pub fn get_reserves(
1035 asset1: T::AssetKind,
1036 asset2: T::AssetKind,
1037 ) -> Result<(T::Balance, T::Balance), Error<T>> {
1038 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2)
1039 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1040
1041 let balance1 = Self::get_balance(&pool_account, asset1);
1042 let balance2 = Self::get_balance(&pool_account, asset2);
1043
1044 if balance1.is_zero() || balance2.is_zero() {
1045 Err(Error::<T>::PoolNotFound)?;
1046 }
1047
1048 Ok((balance1, balance2))
1049 }
1050
1051 pub(crate) fn balance_path_from_amount_out(
1053 amount_out: T::Balance,
1054 path: Vec<T::AssetKind>,
1055 ) -> Result<BalancePath<T>, DispatchError> {
1056 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1057 let mut amount_in: T::Balance = amount_out;
1058
1059 let mut iter = path.into_iter().rev().peekable();
1060 while let Some(asset2) = iter.next() {
1061 let asset1 = match iter.peek() {
1062 Some(a) => a,
1063 None => {
1064 balance_path.push((asset2, amount_in));
1065 break
1066 },
1067 };
1068 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1069 balance_path.push((asset2, amount_in));
1070 amount_in = Self::get_amount_in(&amount_in, &reserve_in, &reserve_out)?;
1071 }
1072 balance_path.reverse();
1073
1074 Ok(balance_path)
1075 }
1076
1077 pub(crate) fn balance_path_from_amount_in(
1079 amount_in: T::Balance,
1080 path: Vec<T::AssetKind>,
1081 ) -> Result<BalancePath<T>, DispatchError> {
1082 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1083 let mut amount_out: T::Balance = amount_in;
1084
1085 let mut iter = path.into_iter().peekable();
1086 while let Some(asset1) = iter.next() {
1087 let asset2 = match iter.peek() {
1088 Some(a) => a,
1089 None => {
1090 balance_path.push((asset1, amount_out));
1091 break
1092 },
1093 };
1094 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1095 balance_path.push((asset1, amount_out));
1096 amount_out = Self::get_amount_out(&amount_out, &reserve_in, &reserve_out)?;
1097 }
1098 Ok(balance_path)
1099 }
1100
1101 pub fn quote_price_exact_tokens_for_tokens(
1103 asset1: T::AssetKind,
1104 asset2: T::AssetKind,
1105 amount: T::Balance,
1106 include_fee: bool,
1107 ) -> Option<T::Balance> {
1108 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1109
1110 let balance1 = Self::get_balance(&pool_account, asset1);
1111 let balance2 = Self::get_balance(&pool_account, asset2);
1112 if !balance1.is_zero() {
1113 if include_fee {
1114 Self::get_amount_out(&amount, &balance1, &balance2).ok()
1115 } else {
1116 Self::quote(&amount, &balance1, &balance2).ok()
1117 }
1118 } else {
1119 None
1120 }
1121 }
1122
1123 pub fn quote_price_tokens_for_exact_tokens(
1125 asset1: T::AssetKind,
1126 asset2: T::AssetKind,
1127 amount: T::Balance,
1128 include_fee: bool,
1129 ) -> Option<T::Balance> {
1130 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1131
1132 let balance1 = Self::get_balance(&pool_account, asset1);
1133 let balance2 = Self::get_balance(&pool_account, asset2);
1134 if !balance1.is_zero() {
1135 if include_fee {
1136 Self::get_amount_in(&amount, &balance1, &balance2).ok()
1137 } else {
1138 Self::quote(&amount, &balance2, &balance1).ok()
1139 }
1140 } else {
1141 None
1142 }
1143 }
1144
1145 pub fn quote(
1147 amount: &T::Balance,
1148 reserve1: &T::Balance,
1149 reserve2: &T::Balance,
1150 ) -> Result<T::Balance, Error<T>> {
1151 Self::mul_div(amount, reserve2, reserve1)
1153 }
1154
1155 pub(super) fn calc_lp_amount_for_zero_supply(
1156 amount1: &T::Balance,
1157 amount2: &T::Balance,
1158 ) -> Result<T::Balance, Error<T>> {
1159 let amount1 = T::HigherPrecisionBalance::from(*amount1);
1160 let amount2 = T::HigherPrecisionBalance::from(*amount2);
1161
1162 let result = amount1
1163 .checked_mul(&amount2)
1164 .ok_or(Error::<T>::Overflow)?
1165 .integer_sqrt()
1166 .checked_sub(&T::MintMinLiquidity::get().into())
1167 .ok_or(Error::<T>::InsufficientLiquidityMinted)?;
1168
1169 result.try_into().map_err(|_| Error::<T>::Overflow)
1170 }
1171
1172 fn mul_div(a: &T::Balance, b: &T::Balance, c: &T::Balance) -> Result<T::Balance, Error<T>> {
1173 let a = T::HigherPrecisionBalance::from(*a);
1174 let b = T::HigherPrecisionBalance::from(*b);
1175 let c = T::HigherPrecisionBalance::from(*c);
1176
1177 let result = a
1178 .checked_mul(&b)
1179 .ok_or(Error::<T>::Overflow)?
1180 .checked_div(&c)
1181 .ok_or(Error::<T>::Overflow)?;
1182
1183 result.try_into().map_err(|_| Error::<T>::Overflow)
1184 }
1185
1186 pub fn get_amount_out(
1191 amount_in: &T::Balance,
1192 reserve_in: &T::Balance,
1193 reserve_out: &T::Balance,
1194 ) -> Result<T::Balance, Error<T>> {
1195 let amount_in = T::HigherPrecisionBalance::from(*amount_in);
1196 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1197 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1198
1199 if reserve_in.is_zero() || reserve_out.is_zero() {
1200 return Err(Error::<T>::ZeroLiquidity)
1201 }
1202
1203 let amount_in_with_fee = amount_in
1204 .checked_mul(&(T::HigherPrecisionBalance::from(1000u32) - (T::LPFee::get().into())))
1205 .ok_or(Error::<T>::Overflow)?;
1206
1207 let numerator =
1208 amount_in_with_fee.checked_mul(&reserve_out).ok_or(Error::<T>::Overflow)?;
1209
1210 let denominator = reserve_in
1211 .checked_mul(&1000u32.into())
1212 .ok_or(Error::<T>::Overflow)?
1213 .checked_add(&amount_in_with_fee)
1214 .ok_or(Error::<T>::Overflow)?;
1215
1216 let result = numerator.checked_div(&denominator).ok_or(Error::<T>::Overflow)?;
1217
1218 result.try_into().map_err(|_| Error::<T>::Overflow)
1219 }
1220
1221 pub fn get_amount_in(
1226 amount_out: &T::Balance,
1227 reserve_in: &T::Balance,
1228 reserve_out: &T::Balance,
1229 ) -> Result<T::Balance, Error<T>> {
1230 let amount_out = T::HigherPrecisionBalance::from(*amount_out);
1231 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1232 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1233
1234 if reserve_in.is_zero() || reserve_out.is_zero() {
1235 Err(Error::<T>::ZeroLiquidity)?
1236 }
1237
1238 if amount_out >= reserve_out {
1239 Err(Error::<T>::AmountOutTooHigh)?
1240 }
1241
1242 let numerator = reserve_in
1243 .checked_mul(&amount_out)
1244 .ok_or(Error::<T>::Overflow)?
1245 .checked_mul(&1000u32.into())
1246 .ok_or(Error::<T>::Overflow)?;
1247
1248 let denominator = reserve_out
1249 .checked_sub(&amount_out)
1250 .ok_or(Error::<T>::Overflow)?
1251 .checked_mul(&(T::HigherPrecisionBalance::from(1000u32) - T::LPFee::get().into()))
1252 .ok_or(Error::<T>::Overflow)?;
1253
1254 let result = numerator
1255 .checked_div(&denominator)
1256 .ok_or(Error::<T>::Overflow)?
1257 .checked_add(&One::one())
1258 .ok_or(Error::<T>::Overflow)?;
1259
1260 result.try_into().map_err(|_| Error::<T>::Overflow)
1261 }
1262
1263 fn validate_swap_path(path: &Vec<T::AssetKind>) -> Result<(), DispatchError> {
1265 ensure!(path.len() >= 2, Error::<T>::InvalidPath);
1266 ensure!(path.len() as u32 <= T::MaxSwapPathLength::get(), Error::<T>::InvalidPath);
1267
1268 let mut pools = BTreeSet::<T::PoolId>::new();
1270 for assets_pair in path.windows(2) {
1271 if let [asset1, asset2] = assets_pair {
1272 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
1273 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1274
1275 let new_element = pools.insert(pool_id);
1276 if !new_element {
1277 return Err(Error::<T>::NonUniquePath.into())
1278 }
1279 }
1280 }
1281 Ok(())
1282 }
1283
1284 #[cfg(any(test, feature = "runtime-benchmarks"))]
1286 pub fn get_next_pool_asset_id() -> T::PoolAssetId {
1287 NextPoolAssetId::<T>::get()
1288 .or(T::PoolAssetId::initial_value())
1289 .expect("Next pool asset ID can not be None")
1290 }
1291 }
1292}
1293
1294sp_api::decl_runtime_apis! {
1295 pub trait AssetConversionApi<Balance, AssetId>
1298 where
1299 Balance: frame_support::traits::tokens::Balance + MaybeDisplay,
1300 AssetId: Codec,
1301 {
1302 fn quote_price_tokens_for_exact_tokens(
1307 asset1: AssetId,
1308 asset2: AssetId,
1309 amount: Balance,
1310 include_fee: bool,
1311 ) -> Option<Balance>;
1312
1313 fn quote_price_exact_tokens_for_tokens(
1318 asset1: AssetId,
1319 asset2: AssetId,
1320 amount: Balance,
1321 include_fee: bool,
1322 ) -> Option<Balance>;
1323
1324 fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>;
1326 }
1327}
1328
1329sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);