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::{
105 pallet_prelude::*,
106 traits::{fungibles::Refund, EnsureOrigin},
107 };
108 use frame_system::pallet_prelude::*;
109 use sp_arithmetic::{traits::Unsigned, PerThing, Permill};
110
111 #[pallet::pallet]
112 pub struct Pallet<T>(_);
113
114 #[pallet::config]
115 pub trait Config: frame_system::Config {
116 #[allow(deprecated)]
118 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
119
120 type Balance: Balance;
122
123 type HigherPrecisionBalance: IntegerSquareRoot
125 + One
126 + Ensure
127 + Unsigned
128 + From<u32>
129 + From<Self::Balance>
130 + TryInto<Self::Balance>;
131
132 type AssetKind: Parameter + MaxEncodedLen + MaybeSerializeDeserialize;
135
136 type Assets: Inspect<Self::AccountId, AssetId = Self::AssetKind, Balance = Self::Balance>
138 + Mutate<Self::AccountId>
139 + AccountTouch<Self::AssetKind, Self::AccountId, Balance = Self::Balance>
140 + Balanced<Self::AccountId>
141 + Refund<Self::AccountId, AssetId = Self::AssetKind>;
142
143 type PoolId: Parameter + MaxEncodedLen + Ord;
145
146 type PoolLocator: PoolLocator<Self::AccountId, Self::AssetKind, Self::PoolId>;
151
152 type PoolAssetId: AssetId + PartialOrd + Incrementable + From<u32>;
154
155 type PoolAssets: Inspect<Self::AccountId, AssetId = Self::PoolAssetId, Balance = Self::Balance>
158 + Create<Self::AccountId>
159 + Mutate<Self::AccountId>
160 + AccountTouch<Self::PoolAssetId, Self::AccountId, Balance = Self::Balance>
161 + Refund<Self::AccountId, AssetId = Self::PoolAssetId>;
162
163 #[pallet::constant]
168 type LPFee: Get<Permill>;
169
170 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
173
174 #[pallet::constant]
176 type MaxSwapFee: Get<Permill>;
177
178 #[pallet::constant]
180 type PoolSetupFee: Get<Self::Balance>;
181
182 #[pallet::constant]
184 type PoolSetupFeeAsset: Get<Self::AssetKind>;
185
186 type PoolSetupFeeTarget: OnUnbalanced<CreditOf<Self>>;
188
189 #[pallet::constant]
191 type LiquidityWithdrawalFee: Get<Permill>;
192
193 #[pallet::constant]
195 type MintMinLiquidity: Get<Self::Balance>;
196
197 #[pallet::constant]
199 type MaxSwapPathLength: Get<u32>;
200
201 #[pallet::constant]
203 type PalletId: Get<PalletId>;
204
205 type WeightInfo: WeightInfo;
207
208 #[cfg(feature = "runtime-benchmarks")]
210 type BenchmarkHelper: BenchmarkHelper<Self::AssetKind>;
211 }
212
213 #[pallet::storage]
216 pub type Pools<T: Config> =
217 StorageMap<_, Blake2_128Concat, T::PoolId, PoolInfo<T::PoolAssetId>, OptionQuery>;
218
219 #[pallet::storage]
222 pub type NextPoolAssetId<T: Config> = StorageValue<_, T::PoolAssetId, OptionQuery>;
223
224 #[pallet::storage]
230 pub type PoolFees<T: Config> = StorageMap<_, Blake2_128Concat, T::PoolId, Permill, OptionQuery>;
231
232 #[pallet::genesis_config]
234 #[derive(frame_support::DefaultNoBound)]
235 pub struct GenesisConfig<T: Config> {
236 pub pools: Vec<(T::AssetKind, T::AssetKind, T::AccountId, T::Balance, T::Balance)>,
244 }
245
246 #[pallet::genesis_build]
247 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
248 fn build(&self) {
249 for (asset1, asset2, lp_provider, amount1, amount2) in &self.pools {
250 Pallet::<T>::setup_pool_from_genesis(
251 asset1,
252 asset2,
253 lp_provider,
254 *amount1,
255 *amount2,
256 )
257 .unwrap_or_else(|e| {
258 panic!("Genesis pool ({asset1:?}, {asset2:?}) setup failed: {e:?}")
259 });
260 }
261 }
262 }
263
264 #[pallet::event]
266 #[pallet::generate_deposit(pub(super) fn deposit_event)]
267 pub enum Event<T: Config> {
268 PoolCreated {
270 creator: T::AccountId,
272 pool_id: T::PoolId,
275 pool_account: T::AccountId,
277 lp_token: T::PoolAssetId,
280 },
281
282 PoolFeeSet {
285 pool_id: T::PoolId,
287 fee: Permill,
289 },
290
291 LiquidityAdded {
293 who: T::AccountId,
295 mint_to: T::AccountId,
297 pool_id: T::PoolId,
299 amount1_provided: T::Balance,
301 amount2_provided: T::Balance,
303 lp_token: T::PoolAssetId,
305 lp_token_minted: T::Balance,
307 },
308
309 LiquidityRemoved {
311 who: T::AccountId,
313 withdraw_to: T::AccountId,
315 pool_id: T::PoolId,
317 amount1: T::Balance,
319 amount2: T::Balance,
321 lp_token: T::PoolAssetId,
323 lp_token_burned: T::Balance,
325 withdrawal_fee: Permill,
327 },
328 SwapExecuted {
331 who: T::AccountId,
333 send_to: T::AccountId,
335 amount_in: T::Balance,
337 amount_out: T::Balance,
339 path: BalancePath<T>,
342 },
343 SwapCreditExecuted {
345 amount_in: T::Balance,
347 amount_out: T::Balance,
349 path: BalancePath<T>,
352 },
353 Touched {
355 pool_id: T::PoolId,
357 who: T::AccountId,
359 },
360 }
361
362 #[pallet::error]
363 pub enum Error<T> {
364 InvalidAssetPair,
366 PoolExists,
368 WrongDesiredAmount,
370 AmountOneLessThanMinimal,
373 AmountTwoLessThanMinimal,
376 ReserveLeftLessThanMinimal,
379 AmountOutTooHigh,
381 PoolNotFound,
383 Overflow,
385 AssetOneDepositDidNotMeetMinimum,
387 AssetTwoDepositDidNotMeetMinimum,
389 AssetOneWithdrawalDidNotMeetMinimum,
391 AssetTwoWithdrawalDidNotMeetMinimum,
393 OptimalAmountLessThanDesired,
395 InsufficientLiquidityMinted,
397 ZeroLiquidity,
399 ZeroAmount,
401 ProvidedMinimumNotSufficientForSwap,
403 ProvidedMaximumNotSufficientForSwap,
405 InvalidPath,
407 NonUniquePath,
409 IncorrectPoolAssetId,
411 BelowMinimum,
413 PoolEmpty,
415 FeeTooHigh,
417 PoolUnavailable,
420 }
421
422 #[pallet::hooks]
423 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
424 fn integrity_test() {
425 assert!(
426 T::MaxSwapPathLength::get() > 1,
427 "the `MaxSwapPathLength` should be greater than 1",
428 );
429 assert!(
430 T::MaxSwapFee::get() < Permill::one(),
431 "the `MaxSwapFee` should be less than 100%",
432 );
433 }
434 }
435
436 #[pallet::call]
438 impl<T: Config> Pallet<T> {
439 #[pallet::call_index(0)]
444 #[pallet::weight(T::WeightInfo::create_pool())]
445 pub fn create_pool(
446 origin: OriginFor<T>,
447 asset1: Box<T::AssetKind>,
448 asset2: Box<T::AssetKind>,
449 ) -> DispatchResult {
450 let sender = ensure_signed(origin)?;
451 Self::do_create_pool(&sender, *asset1, *asset2, None)?;
452 Ok(())
453 }
454
455 #[pallet::call_index(1)]
470 #[pallet::weight(T::WeightInfo::add_liquidity())]
471 pub fn add_liquidity(
472 origin: OriginFor<T>,
473 asset1: Box<T::AssetKind>,
474 asset2: Box<T::AssetKind>,
475 amount1_desired: T::Balance,
476 amount2_desired: T::Balance,
477 amount1_min: T::Balance,
478 amount2_min: T::Balance,
479 mint_to: T::AccountId,
480 ) -> DispatchResult {
481 let sender = ensure_signed(origin)?;
482 Self::do_add_liquidity(
483 &sender,
484 *asset1,
485 *asset2,
486 amount1_desired,
487 amount2_desired,
488 amount1_min,
489 amount2_min,
490 &mint_to,
491 )?;
492 Ok(())
493 }
494
495 #[pallet::call_index(2)]
499 #[pallet::weight(T::WeightInfo::remove_liquidity())]
500 pub fn remove_liquidity(
501 origin: OriginFor<T>,
502 asset1: Box<T::AssetKind>,
503 asset2: Box<T::AssetKind>,
504 lp_token_burn: T::Balance,
505 amount1_min_receive: T::Balance,
506 amount2_min_receive: T::Balance,
507 withdraw_to: T::AccountId,
508 ) -> DispatchResult {
509 let sender = ensure_signed(origin)?;
510 Self::do_remove_liquidity(
511 &sender,
512 *asset1,
513 *asset2,
514 lp_token_burn,
515 amount1_min_receive,
516 amount2_min_receive,
517 &withdraw_to,
518 )?;
519 Ok(())
520 }
521
522 #[pallet::call_index(3)]
529 #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens(path.len() as u32))]
530 pub fn swap_exact_tokens_for_tokens(
531 origin: OriginFor<T>,
532 path: Vec<Box<T::AssetKind>>,
533 amount_in: T::Balance,
534 amount_out_min: T::Balance,
535 send_to: T::AccountId,
536 keep_alive: bool,
537 ) -> DispatchResult {
538 let sender = ensure_signed(origin)?;
539 Self::do_swap_exact_tokens_for_tokens(
540 sender,
541 path.into_iter().map(|a| *a).collect(),
542 amount_in,
543 Some(amount_out_min),
544 send_to,
545 keep_alive,
546 )?;
547 Ok(())
548 }
549
550 #[pallet::call_index(4)]
557 #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens(path.len() as u32))]
558 pub fn swap_tokens_for_exact_tokens(
559 origin: OriginFor<T>,
560 path: Vec<Box<T::AssetKind>>,
561 amount_out: T::Balance,
562 amount_in_max: T::Balance,
563 send_to: T::AccountId,
564 keep_alive: bool,
565 ) -> DispatchResult {
566 let sender = ensure_signed(origin)?;
567 Self::do_swap_tokens_for_exact_tokens(
568 sender,
569 path.into_iter().map(|a| *a).collect(),
570 amount_out,
571 Some(amount_in_max),
572 send_to,
573 keep_alive,
574 )?;
575 Ok(())
576 }
577
578 #[pallet::call_index(5)]
590 #[pallet::weight(T::WeightInfo::touch(3))]
591 pub fn touch(
592 origin: OriginFor<T>,
593 asset1: Box<T::AssetKind>,
594 asset2: Box<T::AssetKind>,
595 ) -> DispatchResultWithPostInfo {
596 let who = ensure_signed(origin)?;
597
598 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
599 .map_err(|_| Error::<T>::InvalidAssetPair)?;
600 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
601 let pool_account =
602 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
603
604 let mut refunds_number: u32 = 0;
605 if T::Assets::should_touch(*asset1.clone(), &pool_account) {
606 T::Assets::touch(*asset1, &pool_account, &who)?;
607 refunds_number += 1;
608 }
609 if T::Assets::should_touch(*asset2.clone(), &pool_account) {
610 T::Assets::touch(*asset2, &pool_account, &who)?;
611 refunds_number += 1;
612 }
613 if T::PoolAssets::should_touch(pool.lp_token.clone(), &pool_account) {
614 T::PoolAssets::touch(pool.lp_token, &pool_account, &who)?;
615 refunds_number += 1;
616 }
617 Self::deposit_event(Event::Touched { pool_id, who });
618 Ok(Some(T::WeightInfo::touch(refunds_number)).into())
619 }
620
621 #[pallet::call_index(6)]
629 #[pallet::weight(T::WeightInfo::create_pool_with_fee())]
630 pub fn create_pool_with_fee(
631 origin: OriginFor<T>,
632 creator: T::AccountId,
633 asset1: Box<T::AssetKind>,
634 asset2: Box<T::AssetKind>,
635 fee: Permill,
636 ) -> DispatchResult {
637 T::AdminOrigin::ensure_origin(origin)?;
638 Self::do_create_pool(&creator, *asset1, *asset2, Some(fee))?;
639 Ok(())
640 }
641
642 #[pallet::call_index(7)]
649 #[pallet::weight(T::WeightInfo::set_pool_fee())]
650 pub fn set_pool_fee(
651 origin: OriginFor<T>,
652 pool_id: T::PoolId,
653 fee: Permill,
654 ) -> DispatchResult {
655 T::AdminOrigin::ensure_origin(origin)?;
656 ensure!(fee <= T::MaxSwapFee::get(), Error::<T>::FeeTooHigh);
657 ensure!(Pools::<T>::contains_key(&pool_id), Error::<T>::PoolNotFound);
658 PoolFees::<T>::insert(&pool_id, fee);
659 Self::deposit_event(Event::PoolFeeSet { pool_id, fee });
660 Ok(())
661 }
662 }
663
664 impl<T: Config> Pallet<T> {
665 pub(crate) fn setup_pool_from_genesis(
671 asset1: &T::AssetKind,
672 asset2: &T::AssetKind,
673 lp_provider: &T::AccountId,
674 amount1: T::Balance,
675 amount2: T::Balance,
676 ) -> Result<T::Balance, DispatchError> {
677 ensure!(asset1 != asset2, Error::<T>::InvalidAssetPair);
678
679 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
680 .map_err(|_| Error::<T>::InvalidAssetPair)?;
681 ensure!(!Pools::<T>::contains_key(&pool_id), Error::<T>::PoolExists);
682
683 let pool_account =
684 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
685
686 let lp_token = NextPoolAssetId::<T>::get()
688 .or(T::PoolAssetId::initial_value())
689 .ok_or(Error::<T>::IncorrectPoolAssetId)?;
690 let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?;
691 NextPoolAssetId::<T>::set(Some(next_lp_token_id));
692
693 T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
695
696 if T::Assets::should_touch(asset1.clone(), &pool_account) {
698 T::Assets::touch(asset1.clone(), &pool_account, lp_provider)?;
699 }
700 if T::Assets::should_touch(asset2.clone(), &pool_account) {
701 T::Assets::touch(asset2.clone(), &pool_account, lp_provider)?;
702 }
703 if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) {
704 T::PoolAssets::touch(lp_token.clone(), &pool_account, lp_provider)?;
705 }
706
707 Pools::<T>::insert(pool_id, PoolInfo { lp_token: lp_token.clone() });
709
710 if !amount1.is_zero() && !amount2.is_zero() {
712 T::Assets::transfer(asset1.clone(), lp_provider, &pool_account, amount1, Preserve)?;
713 T::Assets::transfer(asset2.clone(), lp_provider, &pool_account, amount2, Preserve)?;
714
715 let lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?;
716 T::PoolAssets::mint_into(
717 lp_token.clone(),
718 &pool_account,
719 T::MintMinLiquidity::get(),
720 )?;
721 T::PoolAssets::mint_into(lp_token, lp_provider, lp_token_amount)?;
722
723 Ok(lp_token_amount)
724 } else {
725 Ok(Zero::zero())
726 }
727 }
728
729 pub(crate) fn do_create_pool(
733 creator: &T::AccountId,
734 asset1: T::AssetKind,
735 asset2: T::AssetKind,
736 initial_fee: Option<Permill>,
737 ) -> Result<T::PoolId, DispatchError> {
738 ensure!(asset1 != asset2, Error::<T>::InvalidAssetPair);
739 if let Some(fee) = initial_fee {
740 ensure!(fee <= T::MaxSwapFee::get(), Error::<T>::FeeTooHigh);
741 }
742
743 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
745 .map_err(|_| Error::<T>::InvalidAssetPair)?;
746 ensure!(!Pools::<T>::contains_key(&pool_id), Error::<T>::PoolExists);
747
748 let pool_account =
749 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
750
751 let fee =
753 Self::withdraw(T::PoolSetupFeeAsset::get(), creator, T::PoolSetupFee::get(), true)?;
754 T::PoolSetupFeeTarget::on_unbalanced(fee);
755
756 if T::Assets::should_touch(asset1.clone(), &pool_account) {
757 T::Assets::touch(asset1.clone(), &pool_account, creator)?
758 };
759
760 if T::Assets::should_touch(asset2.clone(), &pool_account) {
761 T::Assets::touch(asset2.clone(), &pool_account, creator)?
762 };
763
764 let lp_token = NextPoolAssetId::<T>::get()
765 .or(T::PoolAssetId::initial_value())
766 .ok_or(Error::<T>::IncorrectPoolAssetId)?;
767 let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?;
768 NextPoolAssetId::<T>::set(Some(next_lp_token_id));
769
770 T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
771 if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) {
772 T::PoolAssets::touch(lp_token.clone(), &pool_account, creator)?
773 };
774
775 let pool_info = PoolInfo { lp_token: lp_token.clone() };
776 Pools::<T>::insert(pool_id.clone(), pool_info);
777
778 Self::deposit_event(Event::PoolCreated {
779 creator: creator.clone(),
780 pool_id: pool_id.clone(),
781 pool_account,
782 lp_token,
783 });
784
785 if let Some(fee) = initial_fee {
786 PoolFees::<T>::insert(&pool_id, fee);
787 Self::deposit_event(Event::PoolFeeSet { pool_id: pool_id.clone(), fee });
788 }
789
790 Ok(pool_id)
791 }
792
793 pub(crate) fn do_add_liquidity(
795 who: &T::AccountId,
796 asset1: T::AssetKind,
797 asset2: T::AssetKind,
798 amount1_desired: T::Balance,
799 amount2_desired: T::Balance,
800 amount1_min: T::Balance,
801 amount2_min: T::Balance,
802 mint_to: &T::AccountId,
803 ) -> Result<T::Balance, DispatchError> {
804 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
805 .map_err(|_| Error::<T>::InvalidAssetPair)?;
806
807 ensure!(
808 amount1_desired > Zero::zero() && amount2_desired > Zero::zero(),
809 Error::<T>::WrongDesiredAmount
810 );
811
812 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
813 let pool_account =
814 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
815
816 let reserve1 = Self::get_balance(&pool_account, asset1.clone());
817 let reserve2 = Self::get_balance(&pool_account, asset2.clone());
818
819 let amount1: T::Balance;
820 let amount2: T::Balance;
821 if reserve1.is_zero() || reserve2.is_zero() {
822 amount1 = amount1_desired;
823 amount2 = amount2_desired;
824 } else {
825 let amount2_optimal = Self::quote(&amount1_desired, &reserve1, &reserve2)?;
826
827 if amount2_optimal <= amount2_desired {
828 ensure!(
829 amount2_optimal >= amount2_min,
830 Error::<T>::AssetTwoDepositDidNotMeetMinimum
831 );
832 amount1 = amount1_desired;
833 amount2 = amount2_optimal;
834 } else {
835 let amount1_optimal = Self::quote(&amount2_desired, &reserve2, &reserve1)?;
836 ensure!(
837 amount1_optimal <= amount1_desired,
838 Error::<T>::OptimalAmountLessThanDesired
839 );
840 ensure!(
841 amount1_optimal >= amount1_min,
842 Error::<T>::AssetOneDepositDidNotMeetMinimum
843 );
844 amount1 = amount1_optimal;
845 amount2 = amount2_desired;
846 }
847 }
848
849 ensure!(
850 amount1.saturating_add(reserve1) >= T::Assets::minimum_balance(asset1.clone()),
851 Error::<T>::AmountOneLessThanMinimal
852 );
853 ensure!(
854 amount2.saturating_add(reserve2) >= T::Assets::minimum_balance(asset2.clone()),
855 Error::<T>::AmountTwoLessThanMinimal
856 );
857
858 T::Assets::transfer(asset1, who, &pool_account, amount1, Preserve)?;
859 T::Assets::transfer(asset2, who, &pool_account, amount2, Preserve)?;
860
861 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
862
863 let lp_token_amount: T::Balance;
864 if total_supply.is_zero() {
865 lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?;
866 T::PoolAssets::mint_into(
867 pool.lp_token.clone(),
868 &pool_account,
869 T::MintMinLiquidity::get(),
870 )?;
871 } else {
872 let side1 = Self::mul_div(&amount1, &total_supply, &reserve1)?;
873 let side2 = Self::mul_div(&amount2, &total_supply, &reserve2)?;
874 lp_token_amount = side1.min(side2);
875 }
876
877 ensure!(
878 lp_token_amount > T::MintMinLiquidity::get(),
879 Error::<T>::InsufficientLiquidityMinted
880 );
881
882 T::PoolAssets::mint_into(pool.lp_token.clone(), mint_to, lp_token_amount)?;
883
884 Self::deposit_event(Event::LiquidityAdded {
885 who: who.clone(),
886 mint_to: mint_to.clone(),
887 pool_id,
888 amount1_provided: amount1,
889 amount2_provided: amount2,
890 lp_token: pool.lp_token,
891 lp_token_minted: lp_token_amount,
892 });
893
894 Ok(lp_token_amount)
895 }
896
897 pub(crate) fn do_remove_liquidity(
899 who: &T::AccountId,
900 asset1: T::AssetKind,
901 asset2: T::AssetKind,
902 lp_token_burn: T::Balance,
903 amount1_min_receive: T::Balance,
904 amount2_min_receive: T::Balance,
905 withdraw_to: &T::AccountId,
906 ) -> Result<(T::Balance, T::Balance), DispatchError> {
907 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
908 .map_err(|_| Error::<T>::InvalidAssetPair)?;
909
910 ensure!(lp_token_burn > Zero::zero(), Error::<T>::ZeroLiquidity);
911
912 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
913
914 let pool_account =
915 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
916 let (reserve1, reserve2) = Self::get_reserves(asset1.clone(), asset2.clone())?;
917
918 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
919 let withdrawal_fee_amount = T::LiquidityWithdrawalFee::get() * lp_token_burn;
920 let lp_redeem_amount = lp_token_burn.saturating_sub(withdrawal_fee_amount);
921
922 let amount1 = Self::mul_div(&lp_redeem_amount, &reserve1, &total_supply)?;
923 let amount2 = Self::mul_div(&lp_redeem_amount, &reserve2, &total_supply)?;
924
925 ensure!(
926 !amount1.is_zero() && amount1 >= amount1_min_receive,
927 Error::<T>::AssetOneWithdrawalDidNotMeetMinimum
928 );
929 ensure!(
930 !amount2.is_zero() && amount2 >= amount2_min_receive,
931 Error::<T>::AssetTwoWithdrawalDidNotMeetMinimum
932 );
933 let reserve1_left = reserve1.saturating_sub(amount1);
934 let reserve2_left = reserve2.saturating_sub(amount2);
935 ensure!(
936 reserve1_left >= T::Assets::minimum_balance(asset1.clone()),
937 Error::<T>::ReserveLeftLessThanMinimal
938 );
939 ensure!(
940 reserve2_left >= T::Assets::minimum_balance(asset2.clone()),
941 Error::<T>::ReserveLeftLessThanMinimal
942 );
943
944 T::PoolAssets::burn_from(
946 pool.lp_token.clone(),
947 who,
948 lp_token_burn,
949 Expendable,
950 Exact,
951 Polite,
952 )?;
953
954 T::Assets::transfer(asset1, &pool_account, withdraw_to, amount1, Expendable)?;
955 T::Assets::transfer(asset2, &pool_account, withdraw_to, amount2, Expendable)?;
956
957 Self::deposit_event(Event::LiquidityRemoved {
958 who: who.clone(),
959 withdraw_to: withdraw_to.clone(),
960 pool_id,
961 amount1,
962 amount2,
963 lp_token: pool.lp_token,
964 lp_token_burned: lp_token_burn,
965 withdrawal_fee: T::LiquidityWithdrawalFee::get(),
966 });
967
968 Ok((amount1, amount2))
969 }
970
971 pub(crate) fn do_swap_exact_tokens_for_tokens(
984 sender: T::AccountId,
985 path: Vec<T::AssetKind>,
986 amount_in: T::Balance,
987 amount_out_min: Option<T::Balance>,
988 send_to: T::AccountId,
989 keep_alive: bool,
990 ) -> Result<T::Balance, DispatchError> {
991 ensure!(amount_in > Zero::zero(), Error::<T>::ZeroAmount);
992 if let Some(amount_out_min) = amount_out_min {
993 ensure!(amount_out_min > Zero::zero(), Error::<T>::ZeroAmount);
994 }
995
996 Self::validate_swap_path(&path)?;
997 let path = Self::balance_path_from_amount_in(amount_in, path)?;
998
999 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
1000 if let Some(amount_out_min) = amount_out_min {
1001 ensure!(
1002 amount_out >= amount_out_min,
1003 Error::<T>::ProvidedMinimumNotSufficientForSwap
1004 );
1005 }
1006
1007 Self::swap(&sender, &path, &send_to, keep_alive)?;
1008
1009 Self::deposit_event(Event::SwapExecuted {
1010 who: sender,
1011 send_to,
1012 amount_in,
1013 amount_out,
1014 path,
1015 });
1016 Ok(amount_out)
1017 }
1018
1019 pub(crate) fn do_swap_tokens_for_exact_tokens(
1032 sender: T::AccountId,
1033 path: Vec<T::AssetKind>,
1034 amount_out: T::Balance,
1035 amount_in_max: Option<T::Balance>,
1036 send_to: T::AccountId,
1037 keep_alive: bool,
1038 ) -> Result<T::Balance, DispatchError> {
1039 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
1040 if let Some(amount_in_max) = amount_in_max {
1041 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
1042 }
1043
1044 Self::validate_swap_path(&path)?;
1045 let path = Self::balance_path_from_amount_out(amount_out, path)?;
1046
1047 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
1048 if let Some(amount_in_max) = amount_in_max {
1049 ensure!(
1050 amount_in <= amount_in_max,
1051 Error::<T>::ProvidedMaximumNotSufficientForSwap
1052 );
1053 }
1054
1055 Self::swap(&sender, &path, &send_to, keep_alive)?;
1056
1057 Self::deposit_event(Event::SwapExecuted {
1058 who: sender,
1059 send_to,
1060 amount_in,
1061 amount_out,
1062 path,
1063 });
1064
1065 Ok(amount_in)
1066 }
1067
1068 pub(crate) fn do_swap_exact_credit_tokens_for_tokens(
1079 path: Vec<T::AssetKind>,
1080 credit_in: CreditOf<T>,
1081 amount_out_min: Option<T::Balance>,
1082 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
1083 let amount_in = credit_in.peek();
1084 let inspect_path = |credit_asset| {
1085 ensure!(
1086 path.first().map_or(false, |a| *a == credit_asset),
1087 Error::<T>::InvalidPath
1088 );
1089 ensure!(!amount_in.is_zero(), Error::<T>::ZeroAmount);
1090 ensure!(amount_out_min.map_or(true, |a| !a.is_zero()), Error::<T>::ZeroAmount);
1091
1092 Self::validate_swap_path(&path)?;
1093 let path = Self::balance_path_from_amount_in(amount_in, path)?;
1094
1095 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
1096 ensure!(
1097 amount_out_min.map_or(true, |a| amount_out >= a),
1098 Error::<T>::ProvidedMinimumNotSufficientForSwap
1099 );
1100 Ok((path, amount_out))
1101 };
1102 let (path, amount_out) = match inspect_path(credit_in.asset()) {
1103 Ok((p, a)) => (p, a),
1104 Err(e) => return Err((credit_in, e)),
1105 };
1106
1107 let credit_out = Self::credit_swap(credit_in, &path)?;
1108
1109 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
1110
1111 Ok(credit_out)
1112 }
1113
1114 pub(crate) fn do_swap_credit_tokens_for_exact_tokens(
1127 path: Vec<T::AssetKind>,
1128 credit_in: CreditOf<T>,
1129 amount_out: T::Balance,
1130 ) -> Result<(CreditOf<T>, CreditOf<T>), (CreditOf<T>, DispatchError)> {
1131 let amount_in_max = credit_in.peek();
1132 let inspect_path = |credit_asset| {
1133 ensure!(
1134 path.first().map_or(false, |a| a == &credit_asset),
1135 Error::<T>::InvalidPath
1136 );
1137 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
1138 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
1139
1140 Self::validate_swap_path(&path)?;
1141 let path = Self::balance_path_from_amount_out(amount_out, path)?;
1142
1143 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
1144 ensure!(
1145 amount_in <= amount_in_max,
1146 Error::<T>::ProvidedMaximumNotSufficientForSwap
1147 );
1148
1149 Ok((path, amount_in))
1150 };
1151 let (path, amount_in) = match inspect_path(credit_in.asset()) {
1152 Ok((p, a)) => (p, a),
1153 Err(e) => return Err((credit_in, e)),
1154 };
1155
1156 let (credit_in, credit_change) = credit_in.split(amount_in);
1157 let credit_out = match Self::credit_swap(credit_in, &path) {
1158 Ok(credit_out) => credit_out,
1159 Err((mut credit_in, e)) => {
1160 let _ = credit_in.subsume(credit_change);
1165 return Err((credit_in, e));
1166 },
1167 };
1168
1169 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
1170
1171 Ok((credit_out, credit_change))
1172 }
1173
1174 fn swap(
1182 sender: &T::AccountId,
1183 path: &BalancePath<T>,
1184 send_to: &T::AccountId,
1185 keep_alive: bool,
1186 ) -> Result<(), DispatchError> {
1187 let (asset_in, amount_in) = path.first().ok_or(Error::<T>::InvalidPath)?;
1188 let credit_in = Self::withdraw(asset_in.clone(), sender, *amount_in, keep_alive)?;
1189
1190 let credit_out = Self::credit_swap(credit_in, path).map_err(|(_, e)| e)?;
1191 T::Assets::resolve(send_to, credit_out).map_err(|_| Error::<T>::BelowMinimum)?;
1192
1193 Ok(())
1194 }
1195
1196 fn credit_swap(
1208 credit_in: CreditOf<T>,
1209 path: &BalancePath<T>,
1210 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
1211 let resolve_path = || -> Result<CreditOf<T>, DispatchError> {
1212 for pos in 0..=path.len() {
1213 if let Some([(asset1, _), (asset2, amount_out)]) = path.get(pos..=pos + 1) {
1214 let pool_from = T::PoolLocator::pool_address(asset1, asset2)
1215 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1216
1217 if let Some((asset3, _)) = path.get(pos + 2) {
1218 let pool_to = T::PoolLocator::pool_address(asset2, asset3)
1219 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1220
1221 T::Assets::transfer(
1222 asset2.clone(),
1223 &pool_from,
1224 &pool_to,
1225 *amount_out,
1226 Preserve,
1227 )?;
1228 } else {
1229 let credit_out =
1230 Self::withdraw(asset2.clone(), &pool_from, *amount_out, true)?;
1231 return Ok(credit_out);
1232 }
1233 }
1234 }
1235 Err(Error::<T>::InvalidPath.into())
1236 };
1237
1238 let credit_out = match resolve_path() {
1239 Ok(c) => c,
1240 Err(e) => return Err((credit_in, e)),
1241 };
1242
1243 let pool_to = if let Some([(asset1, _), (asset2, _)]) = path.get(0..2) {
1244 match T::PoolLocator::pool_address(asset1, asset2) {
1245 Ok(address) => address,
1246 Err(_) => return Err((credit_in, Error::<T>::InvalidAssetPair.into())),
1247 }
1248 } else {
1249 return Err((credit_in, Error::<T>::InvalidPath.into()));
1250 };
1251
1252 T::Assets::resolve(&pool_to, credit_in)
1253 .map_err(|c| (c, Error::<T>::BelowMinimum.into()))?;
1254
1255 Ok(credit_out)
1256 }
1257
1258 fn withdraw(
1260 asset: T::AssetKind,
1261 who: &T::AccountId,
1262 value: T::Balance,
1263 keep_alive: bool,
1264 ) -> Result<CreditOf<T>, DispatchError> {
1265 let preservation = match keep_alive {
1266 true => Preserve,
1267 false => Expendable,
1268 };
1269 if preservation == Preserve {
1270 let free = T::Assets::reducible_balance(asset.clone(), who, preservation, Polite);
1273 ensure!(free >= value, TokenError::NotExpendable);
1274 }
1275 T::Assets::withdraw(asset, who, value, Exact, preservation, Polite)
1276 }
1277
1278 pub(crate) fn get_balance(owner: &T::AccountId, asset: T::AssetKind) -> T::Balance {
1281 T::Assets::balance(asset, owner)
1282 }
1283
1284 pub(crate) fn ensure_pool_can_send(
1286 pool_account: &T::AccountId,
1287 asset: T::AssetKind,
1288 ) -> Result<(), Error<T>> {
1289 let balance = T::Assets::balance(asset.clone(), pool_account);
1290 let minimum = T::Assets::minimum_balance(asset.clone());
1291 let spendable = T::Assets::reducible_balance(asset, pool_account, Preserve, Polite);
1292 let blocked = balance > minimum && spendable.is_zero();
1295 ensure!(!blocked, Error::<T>::PoolUnavailable);
1296 Ok(())
1297 }
1298
1299 pub fn pool_fee(pool_id: &T::PoolId) -> Permill {
1304 PoolFees::<T>::get(pool_id).unwrap_or_else(T::LPFee::get)
1305 }
1306
1307 pub(crate) fn pool_fee_for(
1311 asset1: &T::AssetKind,
1312 asset2: &T::AssetKind,
1313 ) -> Result<Permill, DispatchError> {
1314 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
1315 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1316 Ok(Self::pool_fee(&pool_id))
1317 }
1318
1319 pub(crate) fn balance_path_from_amount_out(
1321 amount_out: T::Balance,
1322 path: Vec<T::AssetKind>,
1323 ) -> Result<BalancePath<T>, DispatchError> {
1324 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1325 let mut amount_in: T::Balance = amount_out;
1326
1327 let mut iter = path.into_iter().rev().peekable();
1328 while let Some(asset2) = iter.next() {
1329 let asset1 = match iter.peek() {
1330 Some(a) => a,
1331 None => {
1332 balance_path.push((asset2, amount_in));
1333 break;
1334 },
1335 };
1336 let fee = Self::pool_fee_for(asset1, &asset2)?;
1337 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1338 balance_path.push((asset2, amount_in));
1339 amount_in = Self::get_amount_in(fee, &amount_in, &reserve_in, &reserve_out)?;
1340 }
1341 balance_path.reverse();
1342
1343 Ok(balance_path)
1344 }
1345
1346 pub(crate) fn balance_path_from_amount_in(
1348 amount_in: T::Balance,
1349 path: Vec<T::AssetKind>,
1350 ) -> Result<BalancePath<T>, DispatchError> {
1351 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1352 let mut amount_out: T::Balance = amount_in;
1353
1354 let mut iter = path.into_iter().peekable();
1355 while let Some(asset1) = iter.next() {
1356 let asset2 = match iter.peek() {
1357 Some(a) => a,
1358 None => {
1359 balance_path.push((asset1, amount_out));
1360 break;
1361 },
1362 };
1363 let fee = Self::pool_fee_for(&asset1, asset2)?;
1364 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1365 balance_path.push((asset1, amount_out));
1366 amount_out = Self::get_amount_out(fee, &amount_out, &reserve_in, &reserve_out)?;
1367 }
1368 Ok(balance_path)
1369 }
1370
1371 pub fn quote(
1373 amount: &T::Balance,
1374 reserve1: &T::Balance,
1375 reserve2: &T::Balance,
1376 ) -> Result<T::Balance, Error<T>> {
1377 Self::mul_div(amount, reserve2, reserve1)
1379 }
1380
1381 pub(super) fn calc_lp_amount_for_zero_supply(
1382 amount1: &T::Balance,
1383 amount2: &T::Balance,
1384 ) -> Result<T::Balance, Error<T>> {
1385 let amount1 = T::HigherPrecisionBalance::from(*amount1);
1386 let amount2 = T::HigherPrecisionBalance::from(*amount2);
1387
1388 let result = amount1
1389 .checked_mul(&amount2)
1390 .ok_or(Error::<T>::Overflow)?
1391 .integer_sqrt()
1392 .checked_sub(&T::MintMinLiquidity::get().into())
1393 .ok_or(Error::<T>::InsufficientLiquidityMinted)?;
1394
1395 result.try_into().map_err(|_| Error::<T>::Overflow)
1396 }
1397
1398 fn mul_div(a: &T::Balance, b: &T::Balance, c: &T::Balance) -> Result<T::Balance, Error<T>> {
1399 let a = T::HigherPrecisionBalance::from(*a);
1400 let b = T::HigherPrecisionBalance::from(*b);
1401 let c = T::HigherPrecisionBalance::from(*c);
1402
1403 let result = a
1404 .checked_mul(&b)
1405 .ok_or(Error::<T>::Overflow)?
1406 .checked_div(&c)
1407 .ok_or(Error::<T>::Overflow)?;
1408
1409 result.try_into().map_err(|_| Error::<T>::Overflow)
1410 }
1411
1412 pub fn get_amount_out(
1417 fee: Permill,
1418 amount_in: &T::Balance,
1419 reserve_in: &T::Balance,
1420 reserve_out: &T::Balance,
1421 ) -> Result<T::Balance, Error<T>> {
1422 let amount_in = T::HigherPrecisionBalance::from(*amount_in);
1423 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1424 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1425
1426 if reserve_in.is_zero() || reserve_out.is_zero() {
1427 return Err(Error::<T>::ZeroLiquidity);
1428 }
1429
1430 let fee_complement = fee.left_from_one().deconstruct();
1431 let amount_in_with_fee = amount_in
1432 .checked_mul(&T::HigherPrecisionBalance::from(fee_complement))
1433 .ok_or(Error::<T>::Overflow)?;
1434
1435 let numerator =
1436 amount_in_with_fee.checked_mul(&reserve_out).ok_or(Error::<T>::Overflow)?;
1437
1438 let denominator = reserve_in
1439 .checked_mul(&T::HigherPrecisionBalance::from(Permill::ACCURACY))
1440 .ok_or(Error::<T>::Overflow)?
1441 .checked_add(&amount_in_with_fee)
1442 .ok_or(Error::<T>::Overflow)?;
1443
1444 let result = numerator.checked_div(&denominator).ok_or(Error::<T>::Overflow)?;
1445
1446 result.try_into().map_err(|_| Error::<T>::Overflow)
1447 }
1448
1449 pub fn get_amount_in(
1454 fee: Permill,
1455 amount_out: &T::Balance,
1456 reserve_in: &T::Balance,
1457 reserve_out: &T::Balance,
1458 ) -> Result<T::Balance, Error<T>> {
1459 let amount_out = T::HigherPrecisionBalance::from(*amount_out);
1460 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1461 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1462
1463 if reserve_in.is_zero() || reserve_out.is_zero() {
1464 Err(Error::<T>::ZeroLiquidity)?
1465 }
1466
1467 if amount_out >= reserve_out {
1468 Err(Error::<T>::AmountOutTooHigh)?
1469 }
1470
1471 let fee_complement = fee.left_from_one().deconstruct();
1472 let numerator = reserve_in
1473 .checked_mul(&amount_out)
1474 .ok_or(Error::<T>::Overflow)?
1475 .checked_mul(&T::HigherPrecisionBalance::from(Permill::ACCURACY))
1476 .ok_or(Error::<T>::Overflow)?;
1477
1478 let denominator = reserve_out
1479 .checked_sub(&amount_out)
1480 .ok_or(Error::<T>::Overflow)?
1481 .checked_mul(&T::HigherPrecisionBalance::from(fee_complement))
1482 .ok_or(Error::<T>::Overflow)?;
1483
1484 let result = numerator
1485 .checked_div(&denominator)
1486 .ok_or(Error::<T>::Overflow)?
1487 .checked_add(&One::one())
1488 .ok_or(Error::<T>::Overflow)?;
1489
1490 result.try_into().map_err(|_| Error::<T>::Overflow)
1491 }
1492
1493 fn validate_swap_path(path: &Vec<T::AssetKind>) -> Result<(), DispatchError> {
1495 ensure!(path.len() >= 2, Error::<T>::InvalidPath);
1496 ensure!(path.len() as u32 <= T::MaxSwapPathLength::get(), Error::<T>::InvalidPath);
1497
1498 let mut pools = BTreeSet::<T::PoolId>::new();
1500 for assets_pair in path.windows(2) {
1501 if let [asset1, asset2] = assets_pair {
1502 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
1503 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1504
1505 let new_element = pools.insert(pool_id);
1506 if !new_element {
1507 return Err(Error::<T>::NonUniquePath.into());
1508 }
1509 }
1510 }
1511 Ok(())
1512 }
1513
1514 #[cfg(any(test, feature = "runtime-benchmarks"))]
1516 pub fn get_next_pool_asset_id() -> T::PoolAssetId {
1517 NextPoolAssetId::<T>::get()
1518 .or(T::PoolAssetId::initial_value())
1519 .expect("Next pool asset ID can not be None")
1520 }
1521 }
1522
1523 #[pallet::view_functions]
1524 impl<T: Config> Pallet<T> {
1525 pub fn get_reserves(
1528 asset1: T::AssetKind,
1529 asset2: T::AssetKind,
1530 ) -> Result<(T::Balance, T::Balance), Error<T>> {
1531 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2)
1532 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1533
1534 let balance1 = Self::get_balance(&pool_account, asset1.clone());
1535 let balance2 = Self::get_balance(&pool_account, asset2.clone());
1536
1537 if balance1.is_zero() || balance2.is_zero() {
1538 Err(Error::<T>::PoolEmpty)?;
1539 }
1540
1541 Self::ensure_pool_can_send(&pool_account, asset1)?;
1542 Self::ensure_pool_can_send(&pool_account, asset2)?;
1543
1544 Ok((balance1, balance2))
1545 }
1546
1547 pub fn quote_price_exact_tokens_for_tokens(
1555 asset1: T::AssetKind,
1556 asset2: T::AssetKind,
1557 amount: T::Balance,
1558 include_fee: bool,
1559 ) -> Option<T::Balance> {
1560 if amount.is_zero() {
1562 return None;
1563 }
1564
1565 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1566
1567 let (balance1, balance2) = Self::get_reserves(asset1.clone(), asset2.clone()).ok()?;
1568
1569 if balance1.is_zero() {
1570 return None;
1571 }
1572
1573 let amount_out = if include_fee {
1574 let fee = Self::pool_fee_for(&asset1, &asset2).ok()?;
1575 Self::get_amount_out(fee, &amount, &balance1, &balance2).ok()?
1576 } else {
1577 Self::quote(&amount, &balance1, &balance2).ok()?
1578 };
1579
1580 if amount_out.is_zero() {
1582 return None;
1583 }
1584
1585 let max_output = T::Assets::reducible_balance(asset2, &pool_account, Preserve, Polite);
1588 if amount_out > max_output {
1589 return None;
1590 }
1591
1592 Some(amount_out)
1593 }
1594
1595 pub fn quote_price_tokens_for_exact_tokens(
1603 asset1: T::AssetKind,
1604 asset2: T::AssetKind,
1605 amount: T::Balance,
1606 include_fee: bool,
1607 ) -> Option<T::Balance> {
1608 if amount.is_zero() {
1610 return None;
1611 }
1612 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1613
1614 let (balance1, balance2) = Self::get_reserves(asset1.clone(), asset2.clone()).ok()?;
1615
1616 if balance1.is_zero() {
1617 return None;
1618 }
1619
1620 let max_output =
1623 T::Assets::reducible_balance(asset2.clone(), &pool_account, Preserve, Polite);
1624 if amount > max_output {
1625 return None;
1626 }
1627
1628 if include_fee {
1629 let fee = Self::pool_fee_for(&asset1, &asset2).ok()?;
1630 Self::get_amount_in(fee, &amount, &balance1, &balance2).ok()
1631 } else {
1632 Self::quote(&amount, &balance2, &balance1).ok()
1633 }
1634 }
1635 }
1636}
1637
1638sp_api::decl_runtime_apis! {
1639 pub trait AssetConversionApi<Balance, AssetId>
1642 where
1643 Balance: frame_support::traits::tokens::Balance + MaybeDisplay,
1644 AssetId: Codec,
1645 {
1646 fn quote_price_tokens_for_exact_tokens(
1651 asset1: AssetId,
1652 asset2: AssetId,
1653 amount: Balance,
1654 include_fee: bool,
1655 ) -> Option<Balance>;
1656
1657 fn quote_price_exact_tokens_for_tokens(
1662 asset1: AssetId,
1663 asset2: AssetId,
1664 amount: Balance,
1665 include_fee: bool,
1666 ) -> Option<Balance>;
1667
1668 fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>;
1670 }
1671}
1672
1673sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);