1#![deny(missing_docs)]
55#![cfg_attr(not(feature = "std"), no_std)]
56
57#[cfg(feature = "runtime-benchmarks")]
58mod benchmarking;
59mod liquidity;
60#[cfg(test)]
61mod mock;
62mod swap;
63#[cfg(test)]
64mod tests;
65mod types;
66pub mod weights;
67#[cfg(feature = "runtime-benchmarks")]
68pub use benchmarking::{BenchmarkHelper, NativeOrWithIdFactory};
69pub use liquidity::*;
70pub use pallet::*;
71pub use swap::*;
72pub use types::*;
73pub use weights::WeightInfo;
74
75extern crate alloc;
76
77use alloc::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec};
78use codec::Codec;
79use frame_support::{
80 traits::{
81 fungibles::{Balanced, Create, Credit, Inspect, Mutate},
82 tokens::{
83 AssetId, Balance,
84 Fortitude::Polite,
85 Precision::Exact,
86 Preservation::{Expendable, Preserve},
87 },
88 AccountTouch, Incrementable, OnUnbalanced,
89 },
90 PalletId,
91};
92use sp_core::Get;
93use sp_runtime::{
94 traits::{
95 CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Ensure, IntegerSquareRoot, MaybeDisplay,
96 MaybeSerializeDeserialize, One, TrailingZeroInput, Zero,
97 },
98 DispatchError, Saturating, TokenError, TransactionOutcome,
99};
100
101#[frame_support::pallet]
102pub mod pallet {
103 use super::*;
104 use frame_support::{pallet_prelude::*, traits::fungibles::Refund};
105 use frame_system::pallet_prelude::*;
106 use sp_arithmetic::{traits::Unsigned, PerThing, Permill};
107
108 #[pallet::pallet]
109 pub struct Pallet<T>(_);
110
111 #[pallet::config]
112 pub trait Config: frame_system::Config {
113 #[allow(deprecated)]
115 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
116
117 type Balance: Balance;
119
120 type HigherPrecisionBalance: IntegerSquareRoot
122 + One
123 + Ensure
124 + Unsigned
125 + From<u32>
126 + From<Self::Balance>
127 + TryInto<Self::Balance>;
128
129 type AssetKind: Parameter + MaxEncodedLen + MaybeSerializeDeserialize;
132
133 type Assets: Inspect<Self::AccountId, AssetId = Self::AssetKind, Balance = Self::Balance>
135 + Mutate<Self::AccountId>
136 + AccountTouch<Self::AssetKind, Self::AccountId, Balance = Self::Balance>
137 + Balanced<Self::AccountId>
138 + Refund<Self::AccountId, AssetId = Self::AssetKind>;
139
140 type PoolId: Parameter + MaxEncodedLen + Ord;
142
143 type PoolLocator: PoolLocator<Self::AccountId, Self::AssetKind, Self::PoolId>;
148
149 type PoolAssetId: AssetId + PartialOrd + Incrementable + From<u32>;
151
152 type PoolAssets: Inspect<Self::AccountId, AssetId = Self::PoolAssetId, Balance = Self::Balance>
155 + Create<Self::AccountId>
156 + Mutate<Self::AccountId>
157 + AccountTouch<Self::PoolAssetId, Self::AccountId, Balance = Self::Balance>
158 + Refund<Self::AccountId, AssetId = Self::PoolAssetId>;
159
160 #[pallet::constant]
162 type LPFee: Get<Permill>;
163
164 #[pallet::constant]
166 type PoolSetupFee: Get<Self::Balance>;
167
168 #[pallet::constant]
170 type PoolSetupFeeAsset: Get<Self::AssetKind>;
171
172 type PoolSetupFeeTarget: OnUnbalanced<CreditOf<Self>>;
174
175 #[pallet::constant]
177 type LiquidityWithdrawalFee: Get<Permill>;
178
179 #[pallet::constant]
181 type MintMinLiquidity: Get<Self::Balance>;
182
183 #[pallet::constant]
185 type MaxSwapPathLength: Get<u32>;
186
187 #[pallet::constant]
189 type PalletId: Get<PalletId>;
190
191 type WeightInfo: WeightInfo;
193
194 #[cfg(feature = "runtime-benchmarks")]
196 type BenchmarkHelper: BenchmarkHelper<Self::AssetKind>;
197 }
198
199 #[pallet::storage]
202 pub type Pools<T: Config> =
203 StorageMap<_, Blake2_128Concat, T::PoolId, PoolInfo<T::PoolAssetId>, OptionQuery>;
204
205 #[pallet::storage]
208 pub type NextPoolAssetId<T: Config> = StorageValue<_, T::PoolAssetId, OptionQuery>;
209
210 #[pallet::genesis_config]
212 #[derive(frame_support::DefaultNoBound)]
213 pub struct GenesisConfig<T: Config> {
214 pub pools: Vec<(T::AssetKind, T::AssetKind, T::AccountId, T::Balance, T::Balance)>,
222 }
223
224 #[pallet::genesis_build]
225 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
226 fn build(&self) {
227 for (asset1, asset2, lp_provider, amount1, amount2) in &self.pools {
228 Pallet::<T>::setup_pool_from_genesis(
229 asset1,
230 asset2,
231 lp_provider,
232 *amount1,
233 *amount2,
234 )
235 .unwrap_or_else(|e| {
236 panic!("Genesis pool ({asset1:?}, {asset2:?}) setup failed: {e:?}")
237 });
238 }
239 }
240 }
241
242 #[pallet::event]
244 #[pallet::generate_deposit(pub(super) fn deposit_event)]
245 pub enum Event<T: Config> {
246 PoolCreated {
248 creator: T::AccountId,
250 pool_id: T::PoolId,
253 pool_account: T::AccountId,
255 lp_token: T::PoolAssetId,
258 },
259
260 LiquidityAdded {
262 who: T::AccountId,
264 mint_to: T::AccountId,
266 pool_id: T::PoolId,
268 amount1_provided: T::Balance,
270 amount2_provided: T::Balance,
272 lp_token: T::PoolAssetId,
274 lp_token_minted: T::Balance,
276 },
277
278 LiquidityRemoved {
280 who: T::AccountId,
282 withdraw_to: T::AccountId,
284 pool_id: T::PoolId,
286 amount1: T::Balance,
288 amount2: T::Balance,
290 lp_token: T::PoolAssetId,
292 lp_token_burned: T::Balance,
294 withdrawal_fee: Permill,
296 },
297 SwapExecuted {
300 who: T::AccountId,
302 send_to: T::AccountId,
304 amount_in: T::Balance,
306 amount_out: T::Balance,
308 path: BalancePath<T>,
311 },
312 SwapCreditExecuted {
314 amount_in: T::Balance,
316 amount_out: T::Balance,
318 path: BalancePath<T>,
321 },
322 Touched {
324 pool_id: T::PoolId,
326 who: T::AccountId,
328 },
329 }
330
331 #[pallet::error]
332 pub enum Error<T> {
333 InvalidAssetPair,
335 PoolExists,
337 WrongDesiredAmount,
339 AmountOneLessThanMinimal,
342 AmountTwoLessThanMinimal,
345 ReserveLeftLessThanMinimal,
348 AmountOutTooHigh,
350 PoolNotFound,
352 Overflow,
354 AssetOneDepositDidNotMeetMinimum,
356 AssetTwoDepositDidNotMeetMinimum,
358 AssetOneWithdrawalDidNotMeetMinimum,
360 AssetTwoWithdrawalDidNotMeetMinimum,
362 OptimalAmountLessThanDesired,
364 InsufficientLiquidityMinted,
366 ZeroLiquidity,
368 ZeroAmount,
370 ProvidedMinimumNotSufficientForSwap,
372 ProvidedMaximumNotSufficientForSwap,
374 InvalidPath,
376 NonUniquePath,
378 IncorrectPoolAssetId,
380 BelowMinimum,
382 PoolEmpty,
384 }
385
386 #[pallet::hooks]
387 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
388 fn integrity_test() {
389 assert!(
390 T::MaxSwapPathLength::get() > 1,
391 "the `MaxSwapPathLength` should be greater than 1",
392 );
393 }
394 }
395
396 #[pallet::call]
398 impl<T: Config> Pallet<T> {
399 #[pallet::call_index(0)]
404 #[pallet::weight(T::WeightInfo::create_pool())]
405 pub fn create_pool(
406 origin: OriginFor<T>,
407 asset1: Box<T::AssetKind>,
408 asset2: Box<T::AssetKind>,
409 ) -> DispatchResult {
410 let sender = ensure_signed(origin)?;
411 Self::do_create_pool(&sender, *asset1, *asset2)?;
412 Ok(())
413 }
414
415 #[pallet::call_index(1)]
430 #[pallet::weight(T::WeightInfo::add_liquidity())]
431 pub fn add_liquidity(
432 origin: OriginFor<T>,
433 asset1: Box<T::AssetKind>,
434 asset2: Box<T::AssetKind>,
435 amount1_desired: T::Balance,
436 amount2_desired: T::Balance,
437 amount1_min: T::Balance,
438 amount2_min: T::Balance,
439 mint_to: T::AccountId,
440 ) -> DispatchResult {
441 let sender = ensure_signed(origin)?;
442 Self::do_add_liquidity(
443 &sender,
444 *asset1,
445 *asset2,
446 amount1_desired,
447 amount2_desired,
448 amount1_min,
449 amount2_min,
450 &mint_to,
451 )?;
452 Ok(())
453 }
454
455 #[pallet::call_index(2)]
459 #[pallet::weight(T::WeightInfo::remove_liquidity())]
460 pub fn remove_liquidity(
461 origin: OriginFor<T>,
462 asset1: Box<T::AssetKind>,
463 asset2: Box<T::AssetKind>,
464 lp_token_burn: T::Balance,
465 amount1_min_receive: T::Balance,
466 amount2_min_receive: T::Balance,
467 withdraw_to: T::AccountId,
468 ) -> DispatchResult {
469 let sender = ensure_signed(origin)?;
470 Self::do_remove_liquidity(
471 &sender,
472 *asset1,
473 *asset2,
474 lp_token_burn,
475 amount1_min_receive,
476 amount2_min_receive,
477 &withdraw_to,
478 )?;
479 Ok(())
480 }
481
482 #[pallet::call_index(3)]
489 #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens(path.len() as u32))]
490 pub fn swap_exact_tokens_for_tokens(
491 origin: OriginFor<T>,
492 path: Vec<Box<T::AssetKind>>,
493 amount_in: T::Balance,
494 amount_out_min: T::Balance,
495 send_to: T::AccountId,
496 keep_alive: bool,
497 ) -> DispatchResult {
498 let sender = ensure_signed(origin)?;
499 Self::do_swap_exact_tokens_for_tokens(
500 sender,
501 path.into_iter().map(|a| *a).collect(),
502 amount_in,
503 Some(amount_out_min),
504 send_to,
505 keep_alive,
506 )?;
507 Ok(())
508 }
509
510 #[pallet::call_index(4)]
517 #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens(path.len() as u32))]
518 pub fn swap_tokens_for_exact_tokens(
519 origin: OriginFor<T>,
520 path: Vec<Box<T::AssetKind>>,
521 amount_out: T::Balance,
522 amount_in_max: T::Balance,
523 send_to: T::AccountId,
524 keep_alive: bool,
525 ) -> DispatchResult {
526 let sender = ensure_signed(origin)?;
527 Self::do_swap_tokens_for_exact_tokens(
528 sender,
529 path.into_iter().map(|a| *a).collect(),
530 amount_out,
531 Some(amount_in_max),
532 send_to,
533 keep_alive,
534 )?;
535 Ok(())
536 }
537
538 #[pallet::call_index(5)]
550 #[pallet::weight(T::WeightInfo::touch(3))]
551 pub fn touch(
552 origin: OriginFor<T>,
553 asset1: Box<T::AssetKind>,
554 asset2: Box<T::AssetKind>,
555 ) -> DispatchResultWithPostInfo {
556 let who = ensure_signed(origin)?;
557
558 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
559 .map_err(|_| Error::<T>::InvalidAssetPair)?;
560 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
561 let pool_account =
562 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
563
564 let mut refunds_number: u32 = 0;
565 if T::Assets::should_touch(*asset1.clone(), &pool_account) {
566 T::Assets::touch(*asset1, &pool_account, &who)?;
567 refunds_number += 1;
568 }
569 if T::Assets::should_touch(*asset2.clone(), &pool_account) {
570 T::Assets::touch(*asset2, &pool_account, &who)?;
571 refunds_number += 1;
572 }
573 if T::PoolAssets::should_touch(pool.lp_token.clone(), &pool_account) {
574 T::PoolAssets::touch(pool.lp_token, &pool_account, &who)?;
575 refunds_number += 1;
576 }
577 Self::deposit_event(Event::Touched { pool_id, who });
578 Ok(Some(T::WeightInfo::touch(refunds_number)).into())
579 }
580 }
581
582 impl<T: Config> Pallet<T> {
583 pub(crate) fn setup_pool_from_genesis(
589 asset1: &T::AssetKind,
590 asset2: &T::AssetKind,
591 lp_provider: &T::AccountId,
592 amount1: T::Balance,
593 amount2: T::Balance,
594 ) -> Result<T::Balance, DispatchError> {
595 ensure!(asset1 != asset2, Error::<T>::InvalidAssetPair);
596
597 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
598 .map_err(|_| Error::<T>::InvalidAssetPair)?;
599 ensure!(!Pools::<T>::contains_key(&pool_id), Error::<T>::PoolExists);
600
601 let pool_account =
602 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
603
604 let lp_token = NextPoolAssetId::<T>::get()
606 .or(T::PoolAssetId::initial_value())
607 .ok_or(Error::<T>::IncorrectPoolAssetId)?;
608 let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?;
609 NextPoolAssetId::<T>::set(Some(next_lp_token_id));
610
611 T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
613
614 if T::Assets::should_touch(asset1.clone(), &pool_account) {
616 T::Assets::touch(asset1.clone(), &pool_account, lp_provider)?;
617 }
618 if T::Assets::should_touch(asset2.clone(), &pool_account) {
619 T::Assets::touch(asset2.clone(), &pool_account, lp_provider)?;
620 }
621 if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) {
622 T::PoolAssets::touch(lp_token.clone(), &pool_account, lp_provider)?;
623 }
624
625 Pools::<T>::insert(pool_id, PoolInfo { lp_token: lp_token.clone() });
627
628 if !amount1.is_zero() && !amount2.is_zero() {
630 T::Assets::transfer(asset1.clone(), lp_provider, &pool_account, amount1, Preserve)?;
631 T::Assets::transfer(asset2.clone(), lp_provider, &pool_account, amount2, Preserve)?;
632
633 let lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?;
634 T::PoolAssets::mint_into(
635 lp_token.clone(),
636 &pool_account,
637 T::MintMinLiquidity::get(),
638 )?;
639 T::PoolAssets::mint_into(lp_token, lp_provider, lp_token_amount)?;
640
641 Ok(lp_token_amount)
642 } else {
643 Ok(Zero::zero())
644 }
645 }
646
647 pub(crate) fn do_create_pool(
651 creator: &T::AccountId,
652 asset1: T::AssetKind,
653 asset2: T::AssetKind,
654 ) -> Result<T::PoolId, DispatchError> {
655 ensure!(asset1 != asset2, Error::<T>::InvalidAssetPair);
656
657 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
659 .map_err(|_| Error::<T>::InvalidAssetPair)?;
660 ensure!(!Pools::<T>::contains_key(&pool_id), Error::<T>::PoolExists);
661
662 let pool_account =
663 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
664
665 let fee =
667 Self::withdraw(T::PoolSetupFeeAsset::get(), creator, T::PoolSetupFee::get(), true)?;
668 T::PoolSetupFeeTarget::on_unbalanced(fee);
669
670 if T::Assets::should_touch(asset1.clone(), &pool_account) {
671 T::Assets::touch(asset1.clone(), &pool_account, creator)?
672 };
673
674 if T::Assets::should_touch(asset2.clone(), &pool_account) {
675 T::Assets::touch(asset2.clone(), &pool_account, creator)?
676 };
677
678 let lp_token = NextPoolAssetId::<T>::get()
679 .or(T::PoolAssetId::initial_value())
680 .ok_or(Error::<T>::IncorrectPoolAssetId)?;
681 let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?;
682 NextPoolAssetId::<T>::set(Some(next_lp_token_id));
683
684 T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
685 if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) {
686 T::PoolAssets::touch(lp_token.clone(), &pool_account, creator)?
687 };
688
689 let pool_info = PoolInfo { lp_token: lp_token.clone() };
690 Pools::<T>::insert(pool_id.clone(), pool_info);
691
692 Self::deposit_event(Event::PoolCreated {
693 creator: creator.clone(),
694 pool_id: pool_id.clone(),
695 pool_account,
696 lp_token,
697 });
698
699 Ok(pool_id)
700 }
701
702 pub(crate) fn do_add_liquidity(
704 who: &T::AccountId,
705 asset1: T::AssetKind,
706 asset2: T::AssetKind,
707 amount1_desired: T::Balance,
708 amount2_desired: T::Balance,
709 amount1_min: T::Balance,
710 amount2_min: T::Balance,
711 mint_to: &T::AccountId,
712 ) -> Result<T::Balance, DispatchError> {
713 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
714 .map_err(|_| Error::<T>::InvalidAssetPair)?;
715
716 ensure!(
717 amount1_desired > Zero::zero() && amount2_desired > Zero::zero(),
718 Error::<T>::WrongDesiredAmount
719 );
720
721 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
722 let pool_account =
723 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
724
725 let reserve1 = Self::get_balance(&pool_account, asset1.clone());
726 let reserve2 = Self::get_balance(&pool_account, asset2.clone());
727
728 let amount1: T::Balance;
729 let amount2: T::Balance;
730 if reserve1.is_zero() || reserve2.is_zero() {
731 amount1 = amount1_desired;
732 amount2 = amount2_desired;
733 } else {
734 let amount2_optimal = Self::quote(&amount1_desired, &reserve1, &reserve2)?;
735
736 if amount2_optimal <= amount2_desired {
737 ensure!(
738 amount2_optimal >= amount2_min,
739 Error::<T>::AssetTwoDepositDidNotMeetMinimum
740 );
741 amount1 = amount1_desired;
742 amount2 = amount2_optimal;
743 } else {
744 let amount1_optimal = Self::quote(&amount2_desired, &reserve2, &reserve1)?;
745 ensure!(
746 amount1_optimal <= amount1_desired,
747 Error::<T>::OptimalAmountLessThanDesired
748 );
749 ensure!(
750 amount1_optimal >= amount1_min,
751 Error::<T>::AssetOneDepositDidNotMeetMinimum
752 );
753 amount1 = amount1_optimal;
754 amount2 = amount2_desired;
755 }
756 }
757
758 ensure!(
759 amount1.saturating_add(reserve1) >= T::Assets::minimum_balance(asset1.clone()),
760 Error::<T>::AmountOneLessThanMinimal
761 );
762 ensure!(
763 amount2.saturating_add(reserve2) >= T::Assets::minimum_balance(asset2.clone()),
764 Error::<T>::AmountTwoLessThanMinimal
765 );
766
767 T::Assets::transfer(asset1, who, &pool_account, amount1, Preserve)?;
768 T::Assets::transfer(asset2, who, &pool_account, amount2, Preserve)?;
769
770 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
771
772 let lp_token_amount: T::Balance;
773 if total_supply.is_zero() {
774 lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?;
775 T::PoolAssets::mint_into(
776 pool.lp_token.clone(),
777 &pool_account,
778 T::MintMinLiquidity::get(),
779 )?;
780 } else {
781 let side1 = Self::mul_div(&amount1, &total_supply, &reserve1)?;
782 let side2 = Self::mul_div(&amount2, &total_supply, &reserve2)?;
783 lp_token_amount = side1.min(side2);
784 }
785
786 ensure!(
787 lp_token_amount > T::MintMinLiquidity::get(),
788 Error::<T>::InsufficientLiquidityMinted
789 );
790
791 T::PoolAssets::mint_into(pool.lp_token.clone(), mint_to, lp_token_amount)?;
792
793 Self::deposit_event(Event::LiquidityAdded {
794 who: who.clone(),
795 mint_to: mint_to.clone(),
796 pool_id,
797 amount1_provided: amount1,
798 amount2_provided: amount2,
799 lp_token: pool.lp_token,
800 lp_token_minted: lp_token_amount,
801 });
802
803 Ok(lp_token_amount)
804 }
805
806 pub(crate) fn do_remove_liquidity(
808 who: &T::AccountId,
809 asset1: T::AssetKind,
810 asset2: T::AssetKind,
811 lp_token_burn: T::Balance,
812 amount1_min_receive: T::Balance,
813 amount2_min_receive: T::Balance,
814 withdraw_to: &T::AccountId,
815 ) -> Result<(T::Balance, T::Balance), DispatchError> {
816 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
817 .map_err(|_| Error::<T>::InvalidAssetPair)?;
818
819 ensure!(lp_token_burn > Zero::zero(), Error::<T>::ZeroLiquidity);
820
821 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
822
823 let pool_account =
824 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
825 let reserve1 = Self::get_balance(&pool_account, asset1.clone());
826 let reserve2 = Self::get_balance(&pool_account, asset2.clone());
827
828 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
829 let withdrawal_fee_amount = T::LiquidityWithdrawalFee::get() * lp_token_burn;
830 let lp_redeem_amount = lp_token_burn.saturating_sub(withdrawal_fee_amount);
831
832 let amount1 = Self::mul_div(&lp_redeem_amount, &reserve1, &total_supply)?;
833 let amount2 = Self::mul_div(&lp_redeem_amount, &reserve2, &total_supply)?;
834
835 ensure!(
836 !amount1.is_zero() && amount1 >= amount1_min_receive,
837 Error::<T>::AssetOneWithdrawalDidNotMeetMinimum
838 );
839 ensure!(
840 !amount2.is_zero() && amount2 >= amount2_min_receive,
841 Error::<T>::AssetTwoWithdrawalDidNotMeetMinimum
842 );
843 let reserve1_left = reserve1.saturating_sub(amount1);
844 let reserve2_left = reserve2.saturating_sub(amount2);
845 ensure!(
846 reserve1_left >= T::Assets::minimum_balance(asset1.clone()),
847 Error::<T>::ReserveLeftLessThanMinimal
848 );
849 ensure!(
850 reserve2_left >= T::Assets::minimum_balance(asset2.clone()),
851 Error::<T>::ReserveLeftLessThanMinimal
852 );
853
854 T::PoolAssets::burn_from(
856 pool.lp_token.clone(),
857 who,
858 lp_token_burn,
859 Expendable,
860 Exact,
861 Polite,
862 )?;
863
864 T::Assets::transfer(asset1, &pool_account, withdraw_to, amount1, Expendable)?;
865 T::Assets::transfer(asset2, &pool_account, withdraw_to, amount2, Expendable)?;
866
867 Self::deposit_event(Event::LiquidityRemoved {
868 who: who.clone(),
869 withdraw_to: withdraw_to.clone(),
870 pool_id,
871 amount1,
872 amount2,
873 lp_token: pool.lp_token,
874 lp_token_burned: lp_token_burn,
875 withdrawal_fee: T::LiquidityWithdrawalFee::get(),
876 });
877
878 Ok((amount1, amount2))
879 }
880
881 pub(crate) fn do_swap_exact_tokens_for_tokens(
894 sender: T::AccountId,
895 path: Vec<T::AssetKind>,
896 amount_in: T::Balance,
897 amount_out_min: Option<T::Balance>,
898 send_to: T::AccountId,
899 keep_alive: bool,
900 ) -> Result<T::Balance, DispatchError> {
901 ensure!(amount_in > Zero::zero(), Error::<T>::ZeroAmount);
902 if let Some(amount_out_min) = amount_out_min {
903 ensure!(amount_out_min > Zero::zero(), Error::<T>::ZeroAmount);
904 }
905
906 Self::validate_swap_path(&path)?;
907 let path = Self::balance_path_from_amount_in(amount_in, path)?;
908
909 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
910 if let Some(amount_out_min) = amount_out_min {
911 ensure!(
912 amount_out >= amount_out_min,
913 Error::<T>::ProvidedMinimumNotSufficientForSwap
914 );
915 }
916
917 Self::swap(&sender, &path, &send_to, keep_alive)?;
918
919 Self::deposit_event(Event::SwapExecuted {
920 who: sender,
921 send_to,
922 amount_in,
923 amount_out,
924 path,
925 });
926 Ok(amount_out)
927 }
928
929 pub(crate) fn do_swap_tokens_for_exact_tokens(
942 sender: T::AccountId,
943 path: Vec<T::AssetKind>,
944 amount_out: T::Balance,
945 amount_in_max: Option<T::Balance>,
946 send_to: T::AccountId,
947 keep_alive: bool,
948 ) -> Result<T::Balance, DispatchError> {
949 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
950 if let Some(amount_in_max) = amount_in_max {
951 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
952 }
953
954 Self::validate_swap_path(&path)?;
955 let path = Self::balance_path_from_amount_out(amount_out, path)?;
956
957 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
958 if let Some(amount_in_max) = amount_in_max {
959 ensure!(
960 amount_in <= amount_in_max,
961 Error::<T>::ProvidedMaximumNotSufficientForSwap
962 );
963 }
964
965 Self::swap(&sender, &path, &send_to, keep_alive)?;
966
967 Self::deposit_event(Event::SwapExecuted {
968 who: sender,
969 send_to,
970 amount_in,
971 amount_out,
972 path,
973 });
974
975 Ok(amount_in)
976 }
977
978 pub(crate) fn do_swap_exact_credit_tokens_for_tokens(
989 path: Vec<T::AssetKind>,
990 credit_in: CreditOf<T>,
991 amount_out_min: Option<T::Balance>,
992 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
993 let amount_in = credit_in.peek();
994 let inspect_path = |credit_asset| {
995 ensure!(
996 path.first().map_or(false, |a| *a == credit_asset),
997 Error::<T>::InvalidPath
998 );
999 ensure!(!amount_in.is_zero(), Error::<T>::ZeroAmount);
1000 ensure!(amount_out_min.map_or(true, |a| !a.is_zero()), Error::<T>::ZeroAmount);
1001
1002 Self::validate_swap_path(&path)?;
1003 let path = Self::balance_path_from_amount_in(amount_in, path)?;
1004
1005 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
1006 ensure!(
1007 amount_out_min.map_or(true, |a| amount_out >= a),
1008 Error::<T>::ProvidedMinimumNotSufficientForSwap
1009 );
1010 Ok((path, amount_out))
1011 };
1012 let (path, amount_out) = match inspect_path(credit_in.asset()) {
1013 Ok((p, a)) => (p, a),
1014 Err(e) => return Err((credit_in, e)),
1015 };
1016
1017 let credit_out = Self::credit_swap(credit_in, &path)?;
1018
1019 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
1020
1021 Ok(credit_out)
1022 }
1023
1024 pub(crate) fn do_swap_credit_tokens_for_exact_tokens(
1037 path: Vec<T::AssetKind>,
1038 credit_in: CreditOf<T>,
1039 amount_out: T::Balance,
1040 ) -> Result<(CreditOf<T>, CreditOf<T>), (CreditOf<T>, DispatchError)> {
1041 let amount_in_max = credit_in.peek();
1042 let inspect_path = |credit_asset| {
1043 ensure!(
1044 path.first().map_or(false, |a| a == &credit_asset),
1045 Error::<T>::InvalidPath
1046 );
1047 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
1048 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
1049
1050 Self::validate_swap_path(&path)?;
1051 let path = Self::balance_path_from_amount_out(amount_out, path)?;
1052
1053 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
1054 ensure!(
1055 amount_in <= amount_in_max,
1056 Error::<T>::ProvidedMaximumNotSufficientForSwap
1057 );
1058
1059 Ok((path, amount_in))
1060 };
1061 let (path, amount_in) = match inspect_path(credit_in.asset()) {
1062 Ok((p, a)) => (p, a),
1063 Err(e) => return Err((credit_in, e)),
1064 };
1065
1066 let (credit_in, credit_change) = credit_in.split(amount_in);
1067 let credit_out = Self::credit_swap(credit_in, &path)?;
1068
1069 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
1070
1071 Ok((credit_out, credit_change))
1072 }
1073
1074 fn swap(
1082 sender: &T::AccountId,
1083 path: &BalancePath<T>,
1084 send_to: &T::AccountId,
1085 keep_alive: bool,
1086 ) -> Result<(), DispatchError> {
1087 let (asset_in, amount_in) = path.first().ok_or(Error::<T>::InvalidPath)?;
1088 let credit_in = Self::withdraw(asset_in.clone(), sender, *amount_in, keep_alive)?;
1089
1090 let credit_out = Self::credit_swap(credit_in, path).map_err(|(_, e)| e)?;
1091 T::Assets::resolve(send_to, credit_out).map_err(|_| Error::<T>::BelowMinimum)?;
1092
1093 Ok(())
1094 }
1095
1096 fn credit_swap(
1108 credit_in: CreditOf<T>,
1109 path: &BalancePath<T>,
1110 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
1111 let resolve_path = || -> Result<CreditOf<T>, DispatchError> {
1112 for pos in 0..=path.len() {
1113 if let Some([(asset1, _), (asset2, amount_out)]) = path.get(pos..=pos + 1) {
1114 let pool_from = T::PoolLocator::pool_address(asset1, asset2)
1115 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1116
1117 if let Some((asset3, _)) = path.get(pos + 2) {
1118 let pool_to = T::PoolLocator::pool_address(asset2, asset3)
1119 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1120
1121 T::Assets::transfer(
1122 asset2.clone(),
1123 &pool_from,
1124 &pool_to,
1125 *amount_out,
1126 Preserve,
1127 )?;
1128 } else {
1129 let credit_out =
1130 Self::withdraw(asset2.clone(), &pool_from, *amount_out, true)?;
1131 return Ok(credit_out);
1132 }
1133 }
1134 }
1135 Err(Error::<T>::InvalidPath.into())
1136 };
1137
1138 let credit_out = match resolve_path() {
1139 Ok(c) => c,
1140 Err(e) => return Err((credit_in, e)),
1141 };
1142
1143 let pool_to = if let Some([(asset1, _), (asset2, _)]) = path.get(0..2) {
1144 match T::PoolLocator::pool_address(asset1, asset2) {
1145 Ok(address) => address,
1146 Err(_) => return Err((credit_in, Error::<T>::InvalidAssetPair.into())),
1147 }
1148 } else {
1149 return Err((credit_in, Error::<T>::InvalidPath.into()));
1150 };
1151
1152 T::Assets::resolve(&pool_to, credit_in)
1153 .map_err(|c| (c, Error::<T>::BelowMinimum.into()))?;
1154
1155 Ok(credit_out)
1156 }
1157
1158 fn withdraw(
1160 asset: T::AssetKind,
1161 who: &T::AccountId,
1162 value: T::Balance,
1163 keep_alive: bool,
1164 ) -> Result<CreditOf<T>, DispatchError> {
1165 let preservation = match keep_alive {
1166 true => Preserve,
1167 false => Expendable,
1168 };
1169 if preservation == Preserve {
1170 let free = T::Assets::reducible_balance(asset.clone(), who, preservation, Polite);
1173 ensure!(free >= value, TokenError::NotExpendable);
1174 }
1175 T::Assets::withdraw(asset, who, value, Exact, preservation, Polite)
1176 }
1177
1178 pub(crate) fn get_balance(owner: &T::AccountId, asset: T::AssetKind) -> T::Balance {
1181 T::Assets::reducible_balance(asset, owner, Expendable, Polite)
1182 }
1183
1184 pub(crate) fn balance_path_from_amount_out(
1186 amount_out: T::Balance,
1187 path: Vec<T::AssetKind>,
1188 ) -> Result<BalancePath<T>, DispatchError> {
1189 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1190 let mut amount_in: T::Balance = amount_out;
1191
1192 let mut iter = path.into_iter().rev().peekable();
1193 while let Some(asset2) = iter.next() {
1194 let asset1 = match iter.peek() {
1195 Some(a) => a,
1196 None => {
1197 balance_path.push((asset2, amount_in));
1198 break;
1199 },
1200 };
1201 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1202 balance_path.push((asset2, amount_in));
1203 amount_in = Self::get_amount_in(&amount_in, &reserve_in, &reserve_out)?;
1204 }
1205 balance_path.reverse();
1206
1207 Ok(balance_path)
1208 }
1209
1210 pub(crate) fn balance_path_from_amount_in(
1212 amount_in: T::Balance,
1213 path: Vec<T::AssetKind>,
1214 ) -> Result<BalancePath<T>, DispatchError> {
1215 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1216 let mut amount_out: T::Balance = amount_in;
1217
1218 let mut iter = path.into_iter().peekable();
1219 while let Some(asset1) = iter.next() {
1220 let asset2 = match iter.peek() {
1221 Some(a) => a,
1222 None => {
1223 balance_path.push((asset1, amount_out));
1224 break;
1225 },
1226 };
1227 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1228 balance_path.push((asset1, amount_out));
1229 amount_out = Self::get_amount_out(&amount_out, &reserve_in, &reserve_out)?;
1230 }
1231 Ok(balance_path)
1232 }
1233
1234 pub fn quote(
1236 amount: &T::Balance,
1237 reserve1: &T::Balance,
1238 reserve2: &T::Balance,
1239 ) -> Result<T::Balance, Error<T>> {
1240 Self::mul_div(amount, reserve2, reserve1)
1242 }
1243
1244 pub(super) fn calc_lp_amount_for_zero_supply(
1245 amount1: &T::Balance,
1246 amount2: &T::Balance,
1247 ) -> Result<T::Balance, Error<T>> {
1248 let amount1 = T::HigherPrecisionBalance::from(*amount1);
1249 let amount2 = T::HigherPrecisionBalance::from(*amount2);
1250
1251 let result = amount1
1252 .checked_mul(&amount2)
1253 .ok_or(Error::<T>::Overflow)?
1254 .integer_sqrt()
1255 .checked_sub(&T::MintMinLiquidity::get().into())
1256 .ok_or(Error::<T>::InsufficientLiquidityMinted)?;
1257
1258 result.try_into().map_err(|_| Error::<T>::Overflow)
1259 }
1260
1261 fn mul_div(a: &T::Balance, b: &T::Balance, c: &T::Balance) -> Result<T::Balance, Error<T>> {
1262 let a = T::HigherPrecisionBalance::from(*a);
1263 let b = T::HigherPrecisionBalance::from(*b);
1264 let c = T::HigherPrecisionBalance::from(*c);
1265
1266 let result = a
1267 .checked_mul(&b)
1268 .ok_or(Error::<T>::Overflow)?
1269 .checked_div(&c)
1270 .ok_or(Error::<T>::Overflow)?;
1271
1272 result.try_into().map_err(|_| Error::<T>::Overflow)
1273 }
1274
1275 pub fn get_amount_out(
1280 amount_in: &T::Balance,
1281 reserve_in: &T::Balance,
1282 reserve_out: &T::Balance,
1283 ) -> Result<T::Balance, Error<T>> {
1284 let amount_in = T::HigherPrecisionBalance::from(*amount_in);
1285 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1286 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1287
1288 if reserve_in.is_zero() || reserve_out.is_zero() {
1289 return Err(Error::<T>::ZeroLiquidity);
1290 }
1291
1292 let fee_complement = T::LPFee::get().left_from_one().deconstruct();
1293 let amount_in_with_fee = amount_in
1294 .checked_mul(&T::HigherPrecisionBalance::from(fee_complement))
1295 .ok_or(Error::<T>::Overflow)?;
1296
1297 let numerator =
1298 amount_in_with_fee.checked_mul(&reserve_out).ok_or(Error::<T>::Overflow)?;
1299
1300 let denominator = reserve_in
1301 .checked_mul(&T::HigherPrecisionBalance::from(Permill::ACCURACY))
1302 .ok_or(Error::<T>::Overflow)?
1303 .checked_add(&amount_in_with_fee)
1304 .ok_or(Error::<T>::Overflow)?;
1305
1306 let result = numerator.checked_div(&denominator).ok_or(Error::<T>::Overflow)?;
1307
1308 result.try_into().map_err(|_| Error::<T>::Overflow)
1309 }
1310
1311 pub fn get_amount_in(
1316 amount_out: &T::Balance,
1317 reserve_in: &T::Balance,
1318 reserve_out: &T::Balance,
1319 ) -> Result<T::Balance, Error<T>> {
1320 let amount_out = T::HigherPrecisionBalance::from(*amount_out);
1321 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1322 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1323
1324 if reserve_in.is_zero() || reserve_out.is_zero() {
1325 Err(Error::<T>::ZeroLiquidity)?
1326 }
1327
1328 if amount_out >= reserve_out {
1329 Err(Error::<T>::AmountOutTooHigh)?
1330 }
1331
1332 let fee_complement = T::LPFee::get().left_from_one().deconstruct();
1333 let numerator = reserve_in
1334 .checked_mul(&amount_out)
1335 .ok_or(Error::<T>::Overflow)?
1336 .checked_mul(&T::HigherPrecisionBalance::from(Permill::ACCURACY))
1337 .ok_or(Error::<T>::Overflow)?;
1338
1339 let denominator = reserve_out
1340 .checked_sub(&amount_out)
1341 .ok_or(Error::<T>::Overflow)?
1342 .checked_mul(&T::HigherPrecisionBalance::from(fee_complement))
1343 .ok_or(Error::<T>::Overflow)?;
1344
1345 let result = numerator
1346 .checked_div(&denominator)
1347 .ok_or(Error::<T>::Overflow)?
1348 .checked_add(&One::one())
1349 .ok_or(Error::<T>::Overflow)?;
1350
1351 result.try_into().map_err(|_| Error::<T>::Overflow)
1352 }
1353
1354 fn validate_swap_path(path: &Vec<T::AssetKind>) -> Result<(), DispatchError> {
1356 ensure!(path.len() >= 2, Error::<T>::InvalidPath);
1357 ensure!(path.len() as u32 <= T::MaxSwapPathLength::get(), Error::<T>::InvalidPath);
1358
1359 let mut pools = BTreeSet::<T::PoolId>::new();
1361 for assets_pair in path.windows(2) {
1362 if let [asset1, asset2] = assets_pair {
1363 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
1364 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1365
1366 let new_element = pools.insert(pool_id);
1367 if !new_element {
1368 return Err(Error::<T>::NonUniquePath.into());
1369 }
1370 }
1371 }
1372 Ok(())
1373 }
1374
1375 #[cfg(any(test, feature = "runtime-benchmarks"))]
1377 pub fn get_next_pool_asset_id() -> T::PoolAssetId {
1378 NextPoolAssetId::<T>::get()
1379 .or(T::PoolAssetId::initial_value())
1380 .expect("Next pool asset ID can not be None")
1381 }
1382 }
1383
1384 #[pallet::view_functions]
1385 impl<T: Config> Pallet<T> {
1386 pub fn get_reserves(
1389 asset1: T::AssetKind,
1390 asset2: T::AssetKind,
1391 ) -> Result<(T::Balance, T::Balance), Error<T>> {
1392 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2)
1393 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1394
1395 let balance1 = Self::get_balance(&pool_account, asset1);
1396 let balance2 = Self::get_balance(&pool_account, asset2);
1397
1398 if balance1.is_zero() || balance2.is_zero() {
1399 Err(Error::<T>::PoolEmpty)?;
1400 }
1401
1402 Ok((balance1, balance2))
1403 }
1404
1405 pub fn quote_price_exact_tokens_for_tokens(
1413 asset1: T::AssetKind,
1414 asset2: T::AssetKind,
1415 amount: T::Balance,
1416 include_fee: bool,
1417 ) -> Option<T::Balance> {
1418 if amount.is_zero() {
1420 return None;
1421 }
1422
1423 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1424
1425 let balance1 = Self::get_balance(&pool_account, asset1);
1426 let balance2 = Self::get_balance(&pool_account, asset2.clone());
1427
1428 if balance1.is_zero() {
1429 return None;
1430 }
1431
1432 let amount_out = if include_fee {
1433 Self::get_amount_out(&amount, &balance1, &balance2).ok()?
1434 } else {
1435 Self::quote(&amount, &balance1, &balance2).ok()?
1436 };
1437
1438 if amount_out.is_zero() {
1440 return None;
1441 }
1442
1443 let max_output = T::Assets::reducible_balance(asset2, &pool_account, Preserve, Polite);
1446 if amount_out > max_output {
1447 return None;
1448 }
1449
1450 Some(amount_out)
1451 }
1452
1453 pub fn quote_price_tokens_for_exact_tokens(
1461 asset1: T::AssetKind,
1462 asset2: T::AssetKind,
1463 amount: T::Balance,
1464 include_fee: bool,
1465 ) -> Option<T::Balance> {
1466 if amount.is_zero() {
1468 return None;
1469 }
1470 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1471
1472 let balance1 = Self::get_balance(&pool_account, asset1);
1473 let balance2 = Self::get_balance(&pool_account, asset2.clone());
1474
1475 if balance1.is_zero() {
1476 return None;
1477 }
1478
1479 let max_output = T::Assets::reducible_balance(asset2, &pool_account, Preserve, Polite);
1482 if amount > max_output {
1483 return None;
1484 }
1485
1486 if include_fee {
1487 Self::get_amount_in(&amount, &balance1, &balance2).ok()
1488 } else {
1489 Self::quote(&amount, &balance2, &balance1).ok()
1490 }
1491 }
1492 }
1493}
1494
1495sp_api::decl_runtime_apis! {
1496 pub trait AssetConversionApi<Balance, AssetId>
1499 where
1500 Balance: frame_support::traits::tokens::Balance + MaybeDisplay,
1501 AssetId: Codec,
1502 {
1503 fn quote_price_tokens_for_exact_tokens(
1508 asset1: AssetId,
1509 asset2: AssetId,
1510 amount: Balance,
1511 include_fee: bool,
1512 ) -> Option<Balance>;
1513
1514 fn quote_price_exact_tokens_for_tokens(
1519 asset1: AssetId,
1520 asset2: AssetId,
1521 amount: Balance,
1522 include_fee: bool,
1523 ) -> Option<Balance>;
1524
1525 fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>;
1527 }
1528}
1529
1530sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);