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 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, 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;
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<u32>;
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::event]
212 #[pallet::generate_deposit(pub(super) fn deposit_event)]
213 pub enum Event<T: Config> {
214 PoolCreated {
216 creator: T::AccountId,
218 pool_id: T::PoolId,
221 pool_account: T::AccountId,
223 lp_token: T::PoolAssetId,
226 },
227
228 LiquidityAdded {
230 who: T::AccountId,
232 mint_to: T::AccountId,
234 pool_id: T::PoolId,
236 amount1_provided: T::Balance,
238 amount2_provided: T::Balance,
240 lp_token: T::PoolAssetId,
242 lp_token_minted: T::Balance,
244 },
245
246 LiquidityRemoved {
248 who: T::AccountId,
250 withdraw_to: T::AccountId,
252 pool_id: T::PoolId,
254 amount1: T::Balance,
256 amount2: T::Balance,
258 lp_token: T::PoolAssetId,
260 lp_token_burned: T::Balance,
262 withdrawal_fee: Permill,
264 },
265 SwapExecuted {
268 who: T::AccountId,
270 send_to: T::AccountId,
272 amount_in: T::Balance,
274 amount_out: T::Balance,
276 path: BalancePath<T>,
279 },
280 SwapCreditExecuted {
282 amount_in: T::Balance,
284 amount_out: T::Balance,
286 path: BalancePath<T>,
289 },
290 Touched {
292 pool_id: T::PoolId,
294 who: T::AccountId,
296 },
297 }
298
299 #[pallet::error]
300 pub enum Error<T> {
301 InvalidAssetPair,
303 PoolExists,
305 WrongDesiredAmount,
307 AmountOneLessThanMinimal,
310 AmountTwoLessThanMinimal,
313 ReserveLeftLessThanMinimal,
316 AmountOutTooHigh,
318 PoolNotFound,
320 Overflow,
322 AssetOneDepositDidNotMeetMinimum,
324 AssetTwoDepositDidNotMeetMinimum,
326 AssetOneWithdrawalDidNotMeetMinimum,
328 AssetTwoWithdrawalDidNotMeetMinimum,
330 OptimalAmountLessThanDesired,
332 InsufficientLiquidityMinted,
334 ZeroLiquidity,
336 ZeroAmount,
338 ProvidedMinimumNotSufficientForSwap,
340 ProvidedMaximumNotSufficientForSwap,
342 InvalidPath,
344 NonUniquePath,
346 IncorrectPoolAssetId,
348 BelowMinimum,
350 }
351
352 #[pallet::hooks]
353 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
354 fn integrity_test() {
355 assert!(
356 T::MaxSwapPathLength::get() > 1,
357 "the `MaxSwapPathLength` should be greater than 1",
358 );
359 }
360 }
361
362 #[pallet::call]
364 impl<T: Config> Pallet<T> {
365 #[pallet::call_index(0)]
370 #[pallet::weight(T::WeightInfo::create_pool())]
371 pub fn create_pool(
372 origin: OriginFor<T>,
373 asset1: Box<T::AssetKind>,
374 asset2: Box<T::AssetKind>,
375 ) -> DispatchResult {
376 let sender = ensure_signed(origin)?;
377 Self::do_create_pool(&sender, *asset1, *asset2)?;
378 Ok(())
379 }
380
381 #[pallet::call_index(1)]
396 #[pallet::weight(T::WeightInfo::add_liquidity())]
397 pub fn add_liquidity(
398 origin: OriginFor<T>,
399 asset1: Box<T::AssetKind>,
400 asset2: Box<T::AssetKind>,
401 amount1_desired: T::Balance,
402 amount2_desired: T::Balance,
403 amount1_min: T::Balance,
404 amount2_min: T::Balance,
405 mint_to: T::AccountId,
406 ) -> DispatchResult {
407 let sender = ensure_signed(origin)?;
408 Self::do_add_liquidity(
409 &sender,
410 *asset1,
411 *asset2,
412 amount1_desired,
413 amount2_desired,
414 amount1_min,
415 amount2_min,
416 &mint_to,
417 )?;
418 Ok(())
419 }
420
421 #[pallet::call_index(2)]
425 #[pallet::weight(T::WeightInfo::remove_liquidity())]
426 pub fn remove_liquidity(
427 origin: OriginFor<T>,
428 asset1: Box<T::AssetKind>,
429 asset2: Box<T::AssetKind>,
430 lp_token_burn: T::Balance,
431 amount1_min_receive: T::Balance,
432 amount2_min_receive: T::Balance,
433 withdraw_to: T::AccountId,
434 ) -> DispatchResult {
435 let sender = ensure_signed(origin)?;
436 Self::do_remove_liquidity(
437 &sender,
438 *asset1,
439 *asset2,
440 lp_token_burn,
441 amount1_min_receive,
442 amount2_min_receive,
443 &withdraw_to,
444 )?;
445 Ok(())
446 }
447
448 #[pallet::call_index(3)]
455 #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens(path.len() as u32))]
456 pub fn swap_exact_tokens_for_tokens(
457 origin: OriginFor<T>,
458 path: Vec<Box<T::AssetKind>>,
459 amount_in: T::Balance,
460 amount_out_min: T::Balance,
461 send_to: T::AccountId,
462 keep_alive: bool,
463 ) -> DispatchResult {
464 let sender = ensure_signed(origin)?;
465 Self::do_swap_exact_tokens_for_tokens(
466 sender,
467 path.into_iter().map(|a| *a).collect(),
468 amount_in,
469 Some(amount_out_min),
470 send_to,
471 keep_alive,
472 )?;
473 Ok(())
474 }
475
476 #[pallet::call_index(4)]
483 #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens(path.len() as u32))]
484 pub fn swap_tokens_for_exact_tokens(
485 origin: OriginFor<T>,
486 path: Vec<Box<T::AssetKind>>,
487 amount_out: T::Balance,
488 amount_in_max: T::Balance,
489 send_to: T::AccountId,
490 keep_alive: bool,
491 ) -> DispatchResult {
492 let sender = ensure_signed(origin)?;
493 Self::do_swap_tokens_for_exact_tokens(
494 sender,
495 path.into_iter().map(|a| *a).collect(),
496 amount_out,
497 Some(amount_in_max),
498 send_to,
499 keep_alive,
500 )?;
501 Ok(())
502 }
503
504 #[pallet::call_index(5)]
516 #[pallet::weight(T::WeightInfo::touch(3))]
517 pub fn touch(
518 origin: OriginFor<T>,
519 asset1: Box<T::AssetKind>,
520 asset2: Box<T::AssetKind>,
521 ) -> DispatchResultWithPostInfo {
522 let who = ensure_signed(origin)?;
523
524 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
525 .map_err(|_| Error::<T>::InvalidAssetPair)?;
526 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
527 let pool_account =
528 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
529
530 let mut refunds_number: u32 = 0;
531 if T::Assets::should_touch(*asset1.clone(), &pool_account) {
532 T::Assets::touch(*asset1, &pool_account, &who)?;
533 refunds_number += 1;
534 }
535 if T::Assets::should_touch(*asset2.clone(), &pool_account) {
536 T::Assets::touch(*asset2, &pool_account, &who)?;
537 refunds_number += 1;
538 }
539 if T::PoolAssets::should_touch(pool.lp_token.clone(), &pool_account) {
540 T::PoolAssets::touch(pool.lp_token, &pool_account, &who)?;
541 refunds_number += 1;
542 }
543 Self::deposit_event(Event::Touched { pool_id, who });
544 Ok(Some(T::WeightInfo::touch(refunds_number)).into())
545 }
546 }
547
548 impl<T: Config> Pallet<T> {
549 pub(crate) fn do_create_pool(
553 creator: &T::AccountId,
554 asset1: T::AssetKind,
555 asset2: T::AssetKind,
556 ) -> Result<T::PoolId, DispatchError> {
557 ensure!(asset1 != asset2, Error::<T>::InvalidAssetPair);
558
559 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
561 .map_err(|_| Error::<T>::InvalidAssetPair)?;
562 ensure!(!Pools::<T>::contains_key(&pool_id), Error::<T>::PoolExists);
563
564 let pool_account =
565 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
566
567 let fee =
569 Self::withdraw(T::PoolSetupFeeAsset::get(), creator, T::PoolSetupFee::get(), true)?;
570 T::PoolSetupFeeTarget::on_unbalanced(fee);
571
572 if T::Assets::should_touch(asset1.clone(), &pool_account) {
573 T::Assets::touch(asset1.clone(), &pool_account, creator)?
574 };
575
576 if T::Assets::should_touch(asset2.clone(), &pool_account) {
577 T::Assets::touch(asset2.clone(), &pool_account, creator)?
578 };
579
580 let lp_token = NextPoolAssetId::<T>::get()
581 .or(T::PoolAssetId::initial_value())
582 .ok_or(Error::<T>::IncorrectPoolAssetId)?;
583 let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?;
584 NextPoolAssetId::<T>::set(Some(next_lp_token_id));
585
586 T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
587 if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) {
588 T::PoolAssets::touch(lp_token.clone(), &pool_account, creator)?
589 };
590
591 let pool_info = PoolInfo { lp_token: lp_token.clone() };
592 Pools::<T>::insert(pool_id.clone(), pool_info);
593
594 Self::deposit_event(Event::PoolCreated {
595 creator: creator.clone(),
596 pool_id: pool_id.clone(),
597 pool_account,
598 lp_token,
599 });
600
601 Ok(pool_id)
602 }
603
604 pub(crate) fn do_add_liquidity(
606 who: &T::AccountId,
607 asset1: T::AssetKind,
608 asset2: T::AssetKind,
609 amount1_desired: T::Balance,
610 amount2_desired: T::Balance,
611 amount1_min: T::Balance,
612 amount2_min: T::Balance,
613 mint_to: &T::AccountId,
614 ) -> Result<T::Balance, DispatchError> {
615 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
616 .map_err(|_| Error::<T>::InvalidAssetPair)?;
617
618 ensure!(
619 amount1_desired > Zero::zero() && amount2_desired > Zero::zero(),
620 Error::<T>::WrongDesiredAmount
621 );
622
623 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
624 let pool_account =
625 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
626
627 let reserve1 = Self::get_balance(&pool_account, asset1.clone());
628 let reserve2 = Self::get_balance(&pool_account, asset2.clone());
629
630 let amount1: T::Balance;
631 let amount2: T::Balance;
632 if reserve1.is_zero() || reserve2.is_zero() {
633 amount1 = amount1_desired;
634 amount2 = amount2_desired;
635 } else {
636 let amount2_optimal = Self::quote(&amount1_desired, &reserve1, &reserve2)?;
637
638 if amount2_optimal <= amount2_desired {
639 ensure!(
640 amount2_optimal >= amount2_min,
641 Error::<T>::AssetTwoDepositDidNotMeetMinimum
642 );
643 amount1 = amount1_desired;
644 amount2 = amount2_optimal;
645 } else {
646 let amount1_optimal = Self::quote(&amount2_desired, &reserve2, &reserve1)?;
647 ensure!(
648 amount1_optimal <= amount1_desired,
649 Error::<T>::OptimalAmountLessThanDesired
650 );
651 ensure!(
652 amount1_optimal >= amount1_min,
653 Error::<T>::AssetOneDepositDidNotMeetMinimum
654 );
655 amount1 = amount1_optimal;
656 amount2 = amount2_desired;
657 }
658 }
659
660 ensure!(
661 amount1.saturating_add(reserve1) >= T::Assets::minimum_balance(asset1.clone()),
662 Error::<T>::AmountOneLessThanMinimal
663 );
664 ensure!(
665 amount2.saturating_add(reserve2) >= T::Assets::minimum_balance(asset2.clone()),
666 Error::<T>::AmountTwoLessThanMinimal
667 );
668
669 T::Assets::transfer(asset1, who, &pool_account, amount1, Preserve)?;
670 T::Assets::transfer(asset2, who, &pool_account, amount2, Preserve)?;
671
672 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
673
674 let lp_token_amount: T::Balance;
675 if total_supply.is_zero() {
676 lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?;
677 T::PoolAssets::mint_into(
678 pool.lp_token.clone(),
679 &pool_account,
680 T::MintMinLiquidity::get(),
681 )?;
682 } else {
683 let side1 = Self::mul_div(&amount1, &total_supply, &reserve1)?;
684 let side2 = Self::mul_div(&amount2, &total_supply, &reserve2)?;
685 lp_token_amount = side1.min(side2);
686 }
687
688 ensure!(
689 lp_token_amount > T::MintMinLiquidity::get(),
690 Error::<T>::InsufficientLiquidityMinted
691 );
692
693 T::PoolAssets::mint_into(pool.lp_token.clone(), mint_to, lp_token_amount)?;
694
695 Self::deposit_event(Event::LiquidityAdded {
696 who: who.clone(),
697 mint_to: mint_to.clone(),
698 pool_id,
699 amount1_provided: amount1,
700 amount2_provided: amount2,
701 lp_token: pool.lp_token,
702 lp_token_minted: lp_token_amount,
703 });
704
705 Ok(lp_token_amount)
706 }
707
708 pub(crate) fn do_remove_liquidity(
710 who: &T::AccountId,
711 asset1: T::AssetKind,
712 asset2: T::AssetKind,
713 lp_token_burn: T::Balance,
714 amount1_min_receive: T::Balance,
715 amount2_min_receive: T::Balance,
716 withdraw_to: &T::AccountId,
717 ) -> Result<(T::Balance, T::Balance), DispatchError> {
718 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
719 .map_err(|_| Error::<T>::InvalidAssetPair)?;
720
721 ensure!(lp_token_burn > Zero::zero(), Error::<T>::ZeroLiquidity);
722
723 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
724
725 let pool_account =
726 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
727 let reserve1 = Self::get_balance(&pool_account, asset1.clone());
728 let reserve2 = Self::get_balance(&pool_account, asset2.clone());
729
730 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
731 let withdrawal_fee_amount = T::LiquidityWithdrawalFee::get() * lp_token_burn;
732 let lp_redeem_amount = lp_token_burn.saturating_sub(withdrawal_fee_amount);
733
734 let amount1 = Self::mul_div(&lp_redeem_amount, &reserve1, &total_supply)?;
735 let amount2 = Self::mul_div(&lp_redeem_amount, &reserve2, &total_supply)?;
736
737 ensure!(
738 !amount1.is_zero() && amount1 >= amount1_min_receive,
739 Error::<T>::AssetOneWithdrawalDidNotMeetMinimum
740 );
741 ensure!(
742 !amount2.is_zero() && amount2 >= amount2_min_receive,
743 Error::<T>::AssetTwoWithdrawalDidNotMeetMinimum
744 );
745 let reserve1_left = reserve1.saturating_sub(amount1);
746 let reserve2_left = reserve2.saturating_sub(amount2);
747 ensure!(
748 reserve1_left >= T::Assets::minimum_balance(asset1.clone()),
749 Error::<T>::ReserveLeftLessThanMinimal
750 );
751 ensure!(
752 reserve2_left >= T::Assets::minimum_balance(asset2.clone()),
753 Error::<T>::ReserveLeftLessThanMinimal
754 );
755
756 T::PoolAssets::burn_from(
758 pool.lp_token.clone(),
759 who,
760 lp_token_burn,
761 Expendable,
762 Exact,
763 Polite,
764 )?;
765
766 T::Assets::transfer(asset1, &pool_account, withdraw_to, amount1, Expendable)?;
767 T::Assets::transfer(asset2, &pool_account, withdraw_to, amount2, Expendable)?;
768
769 Self::deposit_event(Event::LiquidityRemoved {
770 who: who.clone(),
771 withdraw_to: withdraw_to.clone(),
772 pool_id,
773 amount1,
774 amount2,
775 lp_token: pool.lp_token,
776 lp_token_burned: lp_token_burn,
777 withdrawal_fee: T::LiquidityWithdrawalFee::get(),
778 });
779
780 Ok((amount1, amount2))
781 }
782
783 pub(crate) fn do_swap_exact_tokens_for_tokens(
796 sender: T::AccountId,
797 path: Vec<T::AssetKind>,
798 amount_in: T::Balance,
799 amount_out_min: Option<T::Balance>,
800 send_to: T::AccountId,
801 keep_alive: bool,
802 ) -> Result<T::Balance, DispatchError> {
803 ensure!(amount_in > Zero::zero(), Error::<T>::ZeroAmount);
804 if let Some(amount_out_min) = amount_out_min {
805 ensure!(amount_out_min > Zero::zero(), Error::<T>::ZeroAmount);
806 }
807
808 Self::validate_swap_path(&path)?;
809 let path = Self::balance_path_from_amount_in(amount_in, path)?;
810
811 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
812 if let Some(amount_out_min) = amount_out_min {
813 ensure!(
814 amount_out >= amount_out_min,
815 Error::<T>::ProvidedMinimumNotSufficientForSwap
816 );
817 }
818
819 Self::swap(&sender, &path, &send_to, keep_alive)?;
820
821 Self::deposit_event(Event::SwapExecuted {
822 who: sender,
823 send_to,
824 amount_in,
825 amount_out,
826 path,
827 });
828 Ok(amount_out)
829 }
830
831 pub(crate) fn do_swap_tokens_for_exact_tokens(
844 sender: T::AccountId,
845 path: Vec<T::AssetKind>,
846 amount_out: T::Balance,
847 amount_in_max: Option<T::Balance>,
848 send_to: T::AccountId,
849 keep_alive: bool,
850 ) -> Result<T::Balance, DispatchError> {
851 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
852 if let Some(amount_in_max) = amount_in_max {
853 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
854 }
855
856 Self::validate_swap_path(&path)?;
857 let path = Self::balance_path_from_amount_out(amount_out, path)?;
858
859 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
860 if let Some(amount_in_max) = amount_in_max {
861 ensure!(
862 amount_in <= amount_in_max,
863 Error::<T>::ProvidedMaximumNotSufficientForSwap
864 );
865 }
866
867 Self::swap(&sender, &path, &send_to, keep_alive)?;
868
869 Self::deposit_event(Event::SwapExecuted {
870 who: sender,
871 send_to,
872 amount_in,
873 amount_out,
874 path,
875 });
876
877 Ok(amount_in)
878 }
879
880 pub(crate) fn do_swap_exact_credit_tokens_for_tokens(
891 path: Vec<T::AssetKind>,
892 credit_in: CreditOf<T>,
893 amount_out_min: Option<T::Balance>,
894 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
895 let amount_in = credit_in.peek();
896 let inspect_path = |credit_asset| {
897 ensure!(
898 path.first().map_or(false, |a| *a == credit_asset),
899 Error::<T>::InvalidPath
900 );
901 ensure!(!amount_in.is_zero(), Error::<T>::ZeroAmount);
902 ensure!(amount_out_min.map_or(true, |a| !a.is_zero()), Error::<T>::ZeroAmount);
903
904 Self::validate_swap_path(&path)?;
905 let path = Self::balance_path_from_amount_in(amount_in, path)?;
906
907 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
908 ensure!(
909 amount_out_min.map_or(true, |a| amount_out >= a),
910 Error::<T>::ProvidedMinimumNotSufficientForSwap
911 );
912 Ok((path, amount_out))
913 };
914 let (path, amount_out) = match inspect_path(credit_in.asset()) {
915 Ok((p, a)) => (p, a),
916 Err(e) => return Err((credit_in, e)),
917 };
918
919 let credit_out = Self::credit_swap(credit_in, &path)?;
920
921 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
922
923 Ok(credit_out)
924 }
925
926 pub(crate) fn do_swap_credit_tokens_for_exact_tokens(
939 path: Vec<T::AssetKind>,
940 credit_in: CreditOf<T>,
941 amount_out: T::Balance,
942 ) -> Result<(CreditOf<T>, CreditOf<T>), (CreditOf<T>, DispatchError)> {
943 let amount_in_max = credit_in.peek();
944 let inspect_path = |credit_asset| {
945 ensure!(
946 path.first().map_or(false, |a| a == &credit_asset),
947 Error::<T>::InvalidPath
948 );
949 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
950 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
951
952 Self::validate_swap_path(&path)?;
953 let path = Self::balance_path_from_amount_out(amount_out, path)?;
954
955 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
956 ensure!(
957 amount_in <= amount_in_max,
958 Error::<T>::ProvidedMaximumNotSufficientForSwap
959 );
960
961 Ok((path, amount_in))
962 };
963 let (path, amount_in) = match inspect_path(credit_in.asset()) {
964 Ok((p, a)) => (p, a),
965 Err(e) => return Err((credit_in, e)),
966 };
967
968 let (credit_in, credit_change) = credit_in.split(amount_in);
969 let credit_out = Self::credit_swap(credit_in, &path)?;
970
971 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
972
973 Ok((credit_out, credit_change))
974 }
975
976 fn swap(
984 sender: &T::AccountId,
985 path: &BalancePath<T>,
986 send_to: &T::AccountId,
987 keep_alive: bool,
988 ) -> Result<(), DispatchError> {
989 let (asset_in, amount_in) = path.first().ok_or(Error::<T>::InvalidPath)?;
990 let credit_in = Self::withdraw(asset_in.clone(), sender, *amount_in, keep_alive)?;
991
992 let credit_out = Self::credit_swap(credit_in, path).map_err(|(_, e)| e)?;
993 T::Assets::resolve(send_to, credit_out).map_err(|_| Error::<T>::BelowMinimum)?;
994
995 Ok(())
996 }
997
998 fn credit_swap(
1010 credit_in: CreditOf<T>,
1011 path: &BalancePath<T>,
1012 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
1013 let resolve_path = || -> Result<CreditOf<T>, DispatchError> {
1014 for pos in 0..=path.len() {
1015 if let Some([(asset1, _), (asset2, amount_out)]) = path.get(pos..=pos + 1) {
1016 let pool_from = T::PoolLocator::pool_address(asset1, asset2)
1017 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1018
1019 if let Some((asset3, _)) = path.get(pos + 2) {
1020 let pool_to = T::PoolLocator::pool_address(asset2, asset3)
1021 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1022
1023 T::Assets::transfer(
1024 asset2.clone(),
1025 &pool_from,
1026 &pool_to,
1027 *amount_out,
1028 Preserve,
1029 )?;
1030 } else {
1031 let credit_out =
1032 Self::withdraw(asset2.clone(), &pool_from, *amount_out, true)?;
1033 return Ok(credit_out)
1034 }
1035 }
1036 }
1037 Err(Error::<T>::InvalidPath.into())
1038 };
1039
1040 let credit_out = match resolve_path() {
1041 Ok(c) => c,
1042 Err(e) => return Err((credit_in, e)),
1043 };
1044
1045 let pool_to = if let Some([(asset1, _), (asset2, _)]) = path.get(0..2) {
1046 match T::PoolLocator::pool_address(asset1, asset2) {
1047 Ok(address) => address,
1048 Err(_) => return Err((credit_in, Error::<T>::InvalidAssetPair.into())),
1049 }
1050 } else {
1051 return Err((credit_in, Error::<T>::InvalidPath.into()))
1052 };
1053
1054 T::Assets::resolve(&pool_to, credit_in)
1055 .map_err(|c| (c, Error::<T>::BelowMinimum.into()))?;
1056
1057 Ok(credit_out)
1058 }
1059
1060 fn withdraw(
1062 asset: T::AssetKind,
1063 who: &T::AccountId,
1064 value: T::Balance,
1065 keep_alive: bool,
1066 ) -> Result<CreditOf<T>, DispatchError> {
1067 let preservation = match keep_alive {
1068 true => Preserve,
1069 false => Expendable,
1070 };
1071 if preservation == Preserve {
1072 let free = T::Assets::reducible_balance(asset.clone(), who, preservation, Polite);
1075 ensure!(free >= value, TokenError::NotExpendable);
1076 }
1077 T::Assets::withdraw(asset, who, value, Exact, preservation, Polite)
1078 }
1079
1080 pub(crate) fn get_balance(owner: &T::AccountId, asset: T::AssetKind) -> T::Balance {
1083 T::Assets::reducible_balance(asset, owner, Expendable, Polite)
1084 }
1085
1086 pub(crate) fn balance_path_from_amount_out(
1088 amount_out: T::Balance,
1089 path: Vec<T::AssetKind>,
1090 ) -> Result<BalancePath<T>, DispatchError> {
1091 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1092 let mut amount_in: T::Balance = amount_out;
1093
1094 let mut iter = path.into_iter().rev().peekable();
1095 while let Some(asset2) = iter.next() {
1096 let asset1 = match iter.peek() {
1097 Some(a) => a,
1098 None => {
1099 balance_path.push((asset2, amount_in));
1100 break
1101 },
1102 };
1103 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1104 balance_path.push((asset2, amount_in));
1105 amount_in = Self::get_amount_in(&amount_in, &reserve_in, &reserve_out)?;
1106 }
1107 balance_path.reverse();
1108
1109 Ok(balance_path)
1110 }
1111
1112 pub(crate) fn balance_path_from_amount_in(
1114 amount_in: T::Balance,
1115 path: Vec<T::AssetKind>,
1116 ) -> Result<BalancePath<T>, DispatchError> {
1117 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1118 let mut amount_out: T::Balance = amount_in;
1119
1120 let mut iter = path.into_iter().peekable();
1121 while let Some(asset1) = iter.next() {
1122 let asset2 = match iter.peek() {
1123 Some(a) => a,
1124 None => {
1125 balance_path.push((asset1, amount_out));
1126 break
1127 },
1128 };
1129 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1130 balance_path.push((asset1, amount_out));
1131 amount_out = Self::get_amount_out(&amount_out, &reserve_in, &reserve_out)?;
1132 }
1133 Ok(balance_path)
1134 }
1135
1136 pub fn quote(
1138 amount: &T::Balance,
1139 reserve1: &T::Balance,
1140 reserve2: &T::Balance,
1141 ) -> Result<T::Balance, Error<T>> {
1142 Self::mul_div(amount, reserve2, reserve1)
1144 }
1145
1146 pub(super) fn calc_lp_amount_for_zero_supply(
1147 amount1: &T::Balance,
1148 amount2: &T::Balance,
1149 ) -> Result<T::Balance, Error<T>> {
1150 let amount1 = T::HigherPrecisionBalance::from(*amount1);
1151 let amount2 = T::HigherPrecisionBalance::from(*amount2);
1152
1153 let result = amount1
1154 .checked_mul(&amount2)
1155 .ok_or(Error::<T>::Overflow)?
1156 .integer_sqrt()
1157 .checked_sub(&T::MintMinLiquidity::get().into())
1158 .ok_or(Error::<T>::InsufficientLiquidityMinted)?;
1159
1160 result.try_into().map_err(|_| Error::<T>::Overflow)
1161 }
1162
1163 fn mul_div(a: &T::Balance, b: &T::Balance, c: &T::Balance) -> Result<T::Balance, Error<T>> {
1164 let a = T::HigherPrecisionBalance::from(*a);
1165 let b = T::HigherPrecisionBalance::from(*b);
1166 let c = T::HigherPrecisionBalance::from(*c);
1167
1168 let result = a
1169 .checked_mul(&b)
1170 .ok_or(Error::<T>::Overflow)?
1171 .checked_div(&c)
1172 .ok_or(Error::<T>::Overflow)?;
1173
1174 result.try_into().map_err(|_| Error::<T>::Overflow)
1175 }
1176
1177 pub fn get_amount_out(
1182 amount_in: &T::Balance,
1183 reserve_in: &T::Balance,
1184 reserve_out: &T::Balance,
1185 ) -> Result<T::Balance, Error<T>> {
1186 let amount_in = T::HigherPrecisionBalance::from(*amount_in);
1187 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1188 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1189
1190 if reserve_in.is_zero() || reserve_out.is_zero() {
1191 return Err(Error::<T>::ZeroLiquidity)
1192 }
1193
1194 let amount_in_with_fee = amount_in
1195 .checked_mul(&(T::HigherPrecisionBalance::from(1000u32) - (T::LPFee::get().into())))
1196 .ok_or(Error::<T>::Overflow)?;
1197
1198 let numerator =
1199 amount_in_with_fee.checked_mul(&reserve_out).ok_or(Error::<T>::Overflow)?;
1200
1201 let denominator = reserve_in
1202 .checked_mul(&1000u32.into())
1203 .ok_or(Error::<T>::Overflow)?
1204 .checked_add(&amount_in_with_fee)
1205 .ok_or(Error::<T>::Overflow)?;
1206
1207 let result = numerator.checked_div(&denominator).ok_or(Error::<T>::Overflow)?;
1208
1209 result.try_into().map_err(|_| Error::<T>::Overflow)
1210 }
1211
1212 pub fn get_amount_in(
1217 amount_out: &T::Balance,
1218 reserve_in: &T::Balance,
1219 reserve_out: &T::Balance,
1220 ) -> Result<T::Balance, Error<T>> {
1221 let amount_out = T::HigherPrecisionBalance::from(*amount_out);
1222 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1223 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1224
1225 if reserve_in.is_zero() || reserve_out.is_zero() {
1226 Err(Error::<T>::ZeroLiquidity)?
1227 }
1228
1229 if amount_out >= reserve_out {
1230 Err(Error::<T>::AmountOutTooHigh)?
1231 }
1232
1233 let numerator = reserve_in
1234 .checked_mul(&amount_out)
1235 .ok_or(Error::<T>::Overflow)?
1236 .checked_mul(&1000u32.into())
1237 .ok_or(Error::<T>::Overflow)?;
1238
1239 let denominator = reserve_out
1240 .checked_sub(&amount_out)
1241 .ok_or(Error::<T>::Overflow)?
1242 .checked_mul(&(T::HigherPrecisionBalance::from(1000u32) - T::LPFee::get().into()))
1243 .ok_or(Error::<T>::Overflow)?;
1244
1245 let result = numerator
1246 .checked_div(&denominator)
1247 .ok_or(Error::<T>::Overflow)?
1248 .checked_add(&One::one())
1249 .ok_or(Error::<T>::Overflow)?;
1250
1251 result.try_into().map_err(|_| Error::<T>::Overflow)
1252 }
1253
1254 fn validate_swap_path(path: &Vec<T::AssetKind>) -> Result<(), DispatchError> {
1256 ensure!(path.len() >= 2, Error::<T>::InvalidPath);
1257 ensure!(path.len() as u32 <= T::MaxSwapPathLength::get(), Error::<T>::InvalidPath);
1258
1259 let mut pools = BTreeSet::<T::PoolId>::new();
1261 for assets_pair in path.windows(2) {
1262 if let [asset1, asset2] = assets_pair {
1263 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
1264 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1265
1266 let new_element = pools.insert(pool_id);
1267 if !new_element {
1268 return Err(Error::<T>::NonUniquePath.into())
1269 }
1270 }
1271 }
1272 Ok(())
1273 }
1274
1275 #[cfg(any(test, feature = "runtime-benchmarks"))]
1277 pub fn get_next_pool_asset_id() -> T::PoolAssetId {
1278 NextPoolAssetId::<T>::get()
1279 .or(T::PoolAssetId::initial_value())
1280 .expect("Next pool asset ID can not be None")
1281 }
1282 }
1283
1284 #[pallet::view_functions]
1285 impl<T: Config> Pallet<T> {
1286 pub fn get_reserves(
1289 asset1: T::AssetKind,
1290 asset2: T::AssetKind,
1291 ) -> Result<(T::Balance, T::Balance), Error<T>> {
1292 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2)
1293 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1294
1295 let balance1 = Self::get_balance(&pool_account, asset1);
1296 let balance2 = Self::get_balance(&pool_account, asset2);
1297
1298 if balance1.is_zero() || balance2.is_zero() {
1299 Err(Error::<T>::PoolNotFound)?;
1300 }
1301
1302 Ok((balance1, balance2))
1303 }
1304
1305 pub fn quote_price_exact_tokens_for_tokens(
1313 asset1: T::AssetKind,
1314 asset2: T::AssetKind,
1315 amount: T::Balance,
1316 include_fee: bool,
1317 ) -> Option<T::Balance> {
1318 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1319
1320 let balance1 = Self::get_balance(&pool_account, asset1);
1321 let balance2 = Self::get_balance(&pool_account, asset2);
1322 if !balance1.is_zero() {
1323 if include_fee {
1324 Self::get_amount_out(&amount, &balance1, &balance2).ok()
1325 } else {
1326 Self::quote(&amount, &balance1, &balance2).ok()
1327 }
1328 } else {
1329 None
1330 }
1331 }
1332
1333 pub fn quote_price_tokens_for_exact_tokens(
1341 asset1: T::AssetKind,
1342 asset2: T::AssetKind,
1343 amount: T::Balance,
1344 include_fee: bool,
1345 ) -> Option<T::Balance> {
1346 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1347
1348 let balance1 = Self::get_balance(&pool_account, asset1);
1349 let balance2 = Self::get_balance(&pool_account, asset2);
1350 if !balance1.is_zero() {
1351 if include_fee {
1352 Self::get_amount_in(&amount, &balance1, &balance2).ok()
1353 } else {
1354 Self::quote(&amount, &balance2, &balance1).ok()
1355 }
1356 } else {
1357 None
1358 }
1359 }
1360 }
1361}
1362
1363sp_api::decl_runtime_apis! {
1364 pub trait AssetConversionApi<Balance, AssetId>
1367 where
1368 Balance: frame_support::traits::tokens::Balance + MaybeDisplay,
1369 AssetId: Codec,
1370 {
1371 fn quote_price_tokens_for_exact_tokens(
1376 asset1: AssetId,
1377 asset2: AssetId,
1378 amount: Balance,
1379 include_fee: bool,
1380 ) -> Option<Balance>;
1381
1382 fn quote_price_exact_tokens_for_tokens(
1387 asset1: AssetId,
1388 asset2: AssetId,
1389 amount: Balance,
1390 include_fee: bool,
1391 ) -> Option<Balance>;
1392
1393 fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>;
1395 }
1396}
1397
1398sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);