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 }
418
419 #[pallet::hooks]
420 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
421 fn integrity_test() {
422 assert!(
423 T::MaxSwapPathLength::get() > 1,
424 "the `MaxSwapPathLength` should be greater than 1",
425 );
426 assert!(
427 T::MaxSwapFee::get() < Permill::one(),
428 "the `MaxSwapFee` should be less than 100%",
429 );
430 }
431 }
432
433 #[pallet::call]
435 impl<T: Config> Pallet<T> {
436 #[pallet::call_index(0)]
441 #[pallet::weight(T::WeightInfo::create_pool())]
442 pub fn create_pool(
443 origin: OriginFor<T>,
444 asset1: Box<T::AssetKind>,
445 asset2: Box<T::AssetKind>,
446 ) -> DispatchResult {
447 let sender = ensure_signed(origin)?;
448 Self::do_create_pool(&sender, *asset1, *asset2, None)?;
449 Ok(())
450 }
451
452 #[pallet::call_index(1)]
467 #[pallet::weight(T::WeightInfo::add_liquidity())]
468 pub fn add_liquidity(
469 origin: OriginFor<T>,
470 asset1: Box<T::AssetKind>,
471 asset2: Box<T::AssetKind>,
472 amount1_desired: T::Balance,
473 amount2_desired: T::Balance,
474 amount1_min: T::Balance,
475 amount2_min: T::Balance,
476 mint_to: T::AccountId,
477 ) -> DispatchResult {
478 let sender = ensure_signed(origin)?;
479 Self::do_add_liquidity(
480 &sender,
481 *asset1,
482 *asset2,
483 amount1_desired,
484 amount2_desired,
485 amount1_min,
486 amount2_min,
487 &mint_to,
488 )?;
489 Ok(())
490 }
491
492 #[pallet::call_index(2)]
496 #[pallet::weight(T::WeightInfo::remove_liquidity())]
497 pub fn remove_liquidity(
498 origin: OriginFor<T>,
499 asset1: Box<T::AssetKind>,
500 asset2: Box<T::AssetKind>,
501 lp_token_burn: T::Balance,
502 amount1_min_receive: T::Balance,
503 amount2_min_receive: T::Balance,
504 withdraw_to: T::AccountId,
505 ) -> DispatchResult {
506 let sender = ensure_signed(origin)?;
507 Self::do_remove_liquidity(
508 &sender,
509 *asset1,
510 *asset2,
511 lp_token_burn,
512 amount1_min_receive,
513 amount2_min_receive,
514 &withdraw_to,
515 )?;
516 Ok(())
517 }
518
519 #[pallet::call_index(3)]
526 #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens(path.len() as u32))]
527 pub fn swap_exact_tokens_for_tokens(
528 origin: OriginFor<T>,
529 path: Vec<Box<T::AssetKind>>,
530 amount_in: T::Balance,
531 amount_out_min: T::Balance,
532 send_to: T::AccountId,
533 keep_alive: bool,
534 ) -> DispatchResult {
535 let sender = ensure_signed(origin)?;
536 Self::do_swap_exact_tokens_for_tokens(
537 sender,
538 path.into_iter().map(|a| *a).collect(),
539 amount_in,
540 Some(amount_out_min),
541 send_to,
542 keep_alive,
543 )?;
544 Ok(())
545 }
546
547 #[pallet::call_index(4)]
554 #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens(path.len() as u32))]
555 pub fn swap_tokens_for_exact_tokens(
556 origin: OriginFor<T>,
557 path: Vec<Box<T::AssetKind>>,
558 amount_out: T::Balance,
559 amount_in_max: T::Balance,
560 send_to: T::AccountId,
561 keep_alive: bool,
562 ) -> DispatchResult {
563 let sender = ensure_signed(origin)?;
564 Self::do_swap_tokens_for_exact_tokens(
565 sender,
566 path.into_iter().map(|a| *a).collect(),
567 amount_out,
568 Some(amount_in_max),
569 send_to,
570 keep_alive,
571 )?;
572 Ok(())
573 }
574
575 #[pallet::call_index(5)]
587 #[pallet::weight(T::WeightInfo::touch(3))]
588 pub fn touch(
589 origin: OriginFor<T>,
590 asset1: Box<T::AssetKind>,
591 asset2: Box<T::AssetKind>,
592 ) -> DispatchResultWithPostInfo {
593 let who = ensure_signed(origin)?;
594
595 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
596 .map_err(|_| Error::<T>::InvalidAssetPair)?;
597 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
598 let pool_account =
599 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
600
601 let mut refunds_number: u32 = 0;
602 if T::Assets::should_touch(*asset1.clone(), &pool_account) {
603 T::Assets::touch(*asset1, &pool_account, &who)?;
604 refunds_number += 1;
605 }
606 if T::Assets::should_touch(*asset2.clone(), &pool_account) {
607 T::Assets::touch(*asset2, &pool_account, &who)?;
608 refunds_number += 1;
609 }
610 if T::PoolAssets::should_touch(pool.lp_token.clone(), &pool_account) {
611 T::PoolAssets::touch(pool.lp_token, &pool_account, &who)?;
612 refunds_number += 1;
613 }
614 Self::deposit_event(Event::Touched { pool_id, who });
615 Ok(Some(T::WeightInfo::touch(refunds_number)).into())
616 }
617
618 #[pallet::call_index(6)]
626 #[pallet::weight(T::WeightInfo::create_pool_with_fee())]
627 pub fn create_pool_with_fee(
628 origin: OriginFor<T>,
629 creator: T::AccountId,
630 asset1: Box<T::AssetKind>,
631 asset2: Box<T::AssetKind>,
632 fee: Permill,
633 ) -> DispatchResult {
634 T::AdminOrigin::ensure_origin(origin)?;
635 Self::do_create_pool(&creator, *asset1, *asset2, Some(fee))?;
636 Ok(())
637 }
638
639 #[pallet::call_index(7)]
646 #[pallet::weight(T::WeightInfo::set_pool_fee())]
647 pub fn set_pool_fee(
648 origin: OriginFor<T>,
649 pool_id: T::PoolId,
650 fee: Permill,
651 ) -> DispatchResult {
652 T::AdminOrigin::ensure_origin(origin)?;
653 ensure!(fee <= T::MaxSwapFee::get(), Error::<T>::FeeTooHigh);
654 ensure!(Pools::<T>::contains_key(&pool_id), Error::<T>::PoolNotFound);
655 PoolFees::<T>::insert(&pool_id, fee);
656 Self::deposit_event(Event::PoolFeeSet { pool_id, fee });
657 Ok(())
658 }
659 }
660
661 impl<T: Config> Pallet<T> {
662 pub(crate) fn setup_pool_from_genesis(
668 asset1: &T::AssetKind,
669 asset2: &T::AssetKind,
670 lp_provider: &T::AccountId,
671 amount1: T::Balance,
672 amount2: T::Balance,
673 ) -> Result<T::Balance, DispatchError> {
674 ensure!(asset1 != asset2, Error::<T>::InvalidAssetPair);
675
676 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
677 .map_err(|_| Error::<T>::InvalidAssetPair)?;
678 ensure!(!Pools::<T>::contains_key(&pool_id), Error::<T>::PoolExists);
679
680 let pool_account =
681 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
682
683 let lp_token = NextPoolAssetId::<T>::get()
685 .or(T::PoolAssetId::initial_value())
686 .ok_or(Error::<T>::IncorrectPoolAssetId)?;
687 let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?;
688 NextPoolAssetId::<T>::set(Some(next_lp_token_id));
689
690 T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
692
693 if T::Assets::should_touch(asset1.clone(), &pool_account) {
695 T::Assets::touch(asset1.clone(), &pool_account, lp_provider)?;
696 }
697 if T::Assets::should_touch(asset2.clone(), &pool_account) {
698 T::Assets::touch(asset2.clone(), &pool_account, lp_provider)?;
699 }
700 if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) {
701 T::PoolAssets::touch(lp_token.clone(), &pool_account, lp_provider)?;
702 }
703
704 Pools::<T>::insert(pool_id, PoolInfo { lp_token: lp_token.clone() });
706
707 if !amount1.is_zero() && !amount2.is_zero() {
709 T::Assets::transfer(asset1.clone(), lp_provider, &pool_account, amount1, Preserve)?;
710 T::Assets::transfer(asset2.clone(), lp_provider, &pool_account, amount2, Preserve)?;
711
712 let lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?;
713 T::PoolAssets::mint_into(
714 lp_token.clone(),
715 &pool_account,
716 T::MintMinLiquidity::get(),
717 )?;
718 T::PoolAssets::mint_into(lp_token, lp_provider, lp_token_amount)?;
719
720 Ok(lp_token_amount)
721 } else {
722 Ok(Zero::zero())
723 }
724 }
725
726 pub(crate) fn do_create_pool(
730 creator: &T::AccountId,
731 asset1: T::AssetKind,
732 asset2: T::AssetKind,
733 initial_fee: Option<Permill>,
734 ) -> Result<T::PoolId, DispatchError> {
735 ensure!(asset1 != asset2, Error::<T>::InvalidAssetPair);
736 if let Some(fee) = initial_fee {
737 ensure!(fee <= T::MaxSwapFee::get(), Error::<T>::FeeTooHigh);
738 }
739
740 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
742 .map_err(|_| Error::<T>::InvalidAssetPair)?;
743 ensure!(!Pools::<T>::contains_key(&pool_id), Error::<T>::PoolExists);
744
745 let pool_account =
746 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
747
748 let fee =
750 Self::withdraw(T::PoolSetupFeeAsset::get(), creator, T::PoolSetupFee::get(), true)?;
751 T::PoolSetupFeeTarget::on_unbalanced(fee);
752
753 if T::Assets::should_touch(asset1.clone(), &pool_account) {
754 T::Assets::touch(asset1.clone(), &pool_account, creator)?
755 };
756
757 if T::Assets::should_touch(asset2.clone(), &pool_account) {
758 T::Assets::touch(asset2.clone(), &pool_account, creator)?
759 };
760
761 let lp_token = NextPoolAssetId::<T>::get()
762 .or(T::PoolAssetId::initial_value())
763 .ok_or(Error::<T>::IncorrectPoolAssetId)?;
764 let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?;
765 NextPoolAssetId::<T>::set(Some(next_lp_token_id));
766
767 T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
768 if T::PoolAssets::should_touch(lp_token.clone(), &pool_account) {
769 T::PoolAssets::touch(lp_token.clone(), &pool_account, creator)?
770 };
771
772 let pool_info = PoolInfo { lp_token: lp_token.clone() };
773 Pools::<T>::insert(pool_id.clone(), pool_info);
774
775 Self::deposit_event(Event::PoolCreated {
776 creator: creator.clone(),
777 pool_id: pool_id.clone(),
778 pool_account,
779 lp_token,
780 });
781
782 if let Some(fee) = initial_fee {
783 PoolFees::<T>::insert(&pool_id, fee);
784 Self::deposit_event(Event::PoolFeeSet { pool_id: pool_id.clone(), fee });
785 }
786
787 Ok(pool_id)
788 }
789
790 pub(crate) fn do_add_liquidity(
792 who: &T::AccountId,
793 asset1: T::AssetKind,
794 asset2: T::AssetKind,
795 amount1_desired: T::Balance,
796 amount2_desired: T::Balance,
797 amount1_min: T::Balance,
798 amount2_min: T::Balance,
799 mint_to: &T::AccountId,
800 ) -> Result<T::Balance, DispatchError> {
801 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
802 .map_err(|_| Error::<T>::InvalidAssetPair)?;
803
804 ensure!(
805 amount1_desired > Zero::zero() && amount2_desired > Zero::zero(),
806 Error::<T>::WrongDesiredAmount
807 );
808
809 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
810 let pool_account =
811 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
812
813 let reserve1 = Self::get_balance(&pool_account, asset1.clone());
814 let reserve2 = Self::get_balance(&pool_account, asset2.clone());
815
816 let amount1: T::Balance;
817 let amount2: T::Balance;
818 if reserve1.is_zero() || reserve2.is_zero() {
819 amount1 = amount1_desired;
820 amount2 = amount2_desired;
821 } else {
822 let amount2_optimal = Self::quote(&amount1_desired, &reserve1, &reserve2)?;
823
824 if amount2_optimal <= amount2_desired {
825 ensure!(
826 amount2_optimal >= amount2_min,
827 Error::<T>::AssetTwoDepositDidNotMeetMinimum
828 );
829 amount1 = amount1_desired;
830 amount2 = amount2_optimal;
831 } else {
832 let amount1_optimal = Self::quote(&amount2_desired, &reserve2, &reserve1)?;
833 ensure!(
834 amount1_optimal <= amount1_desired,
835 Error::<T>::OptimalAmountLessThanDesired
836 );
837 ensure!(
838 amount1_optimal >= amount1_min,
839 Error::<T>::AssetOneDepositDidNotMeetMinimum
840 );
841 amount1 = amount1_optimal;
842 amount2 = amount2_desired;
843 }
844 }
845
846 ensure!(
847 amount1.saturating_add(reserve1) >= T::Assets::minimum_balance(asset1.clone()),
848 Error::<T>::AmountOneLessThanMinimal
849 );
850 ensure!(
851 amount2.saturating_add(reserve2) >= T::Assets::minimum_balance(asset2.clone()),
852 Error::<T>::AmountTwoLessThanMinimal
853 );
854
855 T::Assets::transfer(asset1, who, &pool_account, amount1, Preserve)?;
856 T::Assets::transfer(asset2, who, &pool_account, amount2, Preserve)?;
857
858 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
859
860 let lp_token_amount: T::Balance;
861 if total_supply.is_zero() {
862 lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?;
863 T::PoolAssets::mint_into(
864 pool.lp_token.clone(),
865 &pool_account,
866 T::MintMinLiquidity::get(),
867 )?;
868 } else {
869 let side1 = Self::mul_div(&amount1, &total_supply, &reserve1)?;
870 let side2 = Self::mul_div(&amount2, &total_supply, &reserve2)?;
871 lp_token_amount = side1.min(side2);
872 }
873
874 ensure!(
875 lp_token_amount > T::MintMinLiquidity::get(),
876 Error::<T>::InsufficientLiquidityMinted
877 );
878
879 T::PoolAssets::mint_into(pool.lp_token.clone(), mint_to, lp_token_amount)?;
880
881 Self::deposit_event(Event::LiquidityAdded {
882 who: who.clone(),
883 mint_to: mint_to.clone(),
884 pool_id,
885 amount1_provided: amount1,
886 amount2_provided: amount2,
887 lp_token: pool.lp_token,
888 lp_token_minted: lp_token_amount,
889 });
890
891 Ok(lp_token_amount)
892 }
893
894 pub(crate) fn do_remove_liquidity(
896 who: &T::AccountId,
897 asset1: T::AssetKind,
898 asset2: T::AssetKind,
899 lp_token_burn: T::Balance,
900 amount1_min_receive: T::Balance,
901 amount2_min_receive: T::Balance,
902 withdraw_to: &T::AccountId,
903 ) -> Result<(T::Balance, T::Balance), DispatchError> {
904 let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
905 .map_err(|_| Error::<T>::InvalidAssetPair)?;
906
907 ensure!(lp_token_burn > Zero::zero(), Error::<T>::ZeroLiquidity);
908
909 let pool = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
910
911 let pool_account =
912 T::PoolLocator::address(&pool_id).map_err(|_| Error::<T>::InvalidAssetPair)?;
913 let (reserve1, reserve2) = Self::get_reserves(asset1.clone(), asset2.clone())?;
914
915 let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
916 let withdrawal_fee_amount = T::LiquidityWithdrawalFee::get() * lp_token_burn;
917 let lp_redeem_amount = lp_token_burn.saturating_sub(withdrawal_fee_amount);
918
919 let amount1 = Self::mul_div(&lp_redeem_amount, &reserve1, &total_supply)?;
920 let amount2 = Self::mul_div(&lp_redeem_amount, &reserve2, &total_supply)?;
921
922 ensure!(
923 !amount1.is_zero() && amount1 >= amount1_min_receive,
924 Error::<T>::AssetOneWithdrawalDidNotMeetMinimum
925 );
926 ensure!(
927 !amount2.is_zero() && amount2 >= amount2_min_receive,
928 Error::<T>::AssetTwoWithdrawalDidNotMeetMinimum
929 );
930 let reserve1_left = reserve1.saturating_sub(amount1);
931 let reserve2_left = reserve2.saturating_sub(amount2);
932 ensure!(
933 reserve1_left >= T::Assets::minimum_balance(asset1.clone()),
934 Error::<T>::ReserveLeftLessThanMinimal
935 );
936 ensure!(
937 reserve2_left >= T::Assets::minimum_balance(asset2.clone()),
938 Error::<T>::ReserveLeftLessThanMinimal
939 );
940
941 T::PoolAssets::burn_from(
943 pool.lp_token.clone(),
944 who,
945 lp_token_burn,
946 Expendable,
947 Exact,
948 Polite,
949 )?;
950
951 T::Assets::transfer(asset1, &pool_account, withdraw_to, amount1, Expendable)?;
952 T::Assets::transfer(asset2, &pool_account, withdraw_to, amount2, Expendable)?;
953
954 Self::deposit_event(Event::LiquidityRemoved {
955 who: who.clone(),
956 withdraw_to: withdraw_to.clone(),
957 pool_id,
958 amount1,
959 amount2,
960 lp_token: pool.lp_token,
961 lp_token_burned: lp_token_burn,
962 withdrawal_fee: T::LiquidityWithdrawalFee::get(),
963 });
964
965 Ok((amount1, amount2))
966 }
967
968 pub(crate) fn do_swap_exact_tokens_for_tokens(
981 sender: T::AccountId,
982 path: Vec<T::AssetKind>,
983 amount_in: T::Balance,
984 amount_out_min: Option<T::Balance>,
985 send_to: T::AccountId,
986 keep_alive: bool,
987 ) -> Result<T::Balance, DispatchError> {
988 ensure!(amount_in > Zero::zero(), Error::<T>::ZeroAmount);
989 if let Some(amount_out_min) = amount_out_min {
990 ensure!(amount_out_min > Zero::zero(), Error::<T>::ZeroAmount);
991 }
992
993 Self::validate_swap_path(&path)?;
994 let path = Self::balance_path_from_amount_in(amount_in, path)?;
995
996 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
997 if let Some(amount_out_min) = amount_out_min {
998 ensure!(
999 amount_out >= amount_out_min,
1000 Error::<T>::ProvidedMinimumNotSufficientForSwap
1001 );
1002 }
1003
1004 Self::swap(&sender, &path, &send_to, keep_alive)?;
1005
1006 Self::deposit_event(Event::SwapExecuted {
1007 who: sender,
1008 send_to,
1009 amount_in,
1010 amount_out,
1011 path,
1012 });
1013 Ok(amount_out)
1014 }
1015
1016 pub(crate) fn do_swap_tokens_for_exact_tokens(
1029 sender: T::AccountId,
1030 path: Vec<T::AssetKind>,
1031 amount_out: T::Balance,
1032 amount_in_max: Option<T::Balance>,
1033 send_to: T::AccountId,
1034 keep_alive: bool,
1035 ) -> Result<T::Balance, DispatchError> {
1036 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
1037 if let Some(amount_in_max) = amount_in_max {
1038 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
1039 }
1040
1041 Self::validate_swap_path(&path)?;
1042 let path = Self::balance_path_from_amount_out(amount_out, path)?;
1043
1044 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
1045 if let Some(amount_in_max) = amount_in_max {
1046 ensure!(
1047 amount_in <= amount_in_max,
1048 Error::<T>::ProvidedMaximumNotSufficientForSwap
1049 );
1050 }
1051
1052 Self::swap(&sender, &path, &send_to, keep_alive)?;
1053
1054 Self::deposit_event(Event::SwapExecuted {
1055 who: sender,
1056 send_to,
1057 amount_in,
1058 amount_out,
1059 path,
1060 });
1061
1062 Ok(amount_in)
1063 }
1064
1065 pub(crate) fn do_swap_exact_credit_tokens_for_tokens(
1076 path: Vec<T::AssetKind>,
1077 credit_in: CreditOf<T>,
1078 amount_out_min: Option<T::Balance>,
1079 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
1080 let amount_in = credit_in.peek();
1081 let inspect_path = |credit_asset| {
1082 ensure!(
1083 path.first().map_or(false, |a| *a == credit_asset),
1084 Error::<T>::InvalidPath
1085 );
1086 ensure!(!amount_in.is_zero(), Error::<T>::ZeroAmount);
1087 ensure!(amount_out_min.map_or(true, |a| !a.is_zero()), Error::<T>::ZeroAmount);
1088
1089 Self::validate_swap_path(&path)?;
1090 let path = Self::balance_path_from_amount_in(amount_in, path)?;
1091
1092 let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
1093 ensure!(
1094 amount_out_min.map_or(true, |a| amount_out >= a),
1095 Error::<T>::ProvidedMinimumNotSufficientForSwap
1096 );
1097 Ok((path, amount_out))
1098 };
1099 let (path, amount_out) = match inspect_path(credit_in.asset()) {
1100 Ok((p, a)) => (p, a),
1101 Err(e) => return Err((credit_in, e)),
1102 };
1103
1104 let credit_out = Self::credit_swap(credit_in, &path)?;
1105
1106 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
1107
1108 Ok(credit_out)
1109 }
1110
1111 pub(crate) fn do_swap_credit_tokens_for_exact_tokens(
1124 path: Vec<T::AssetKind>,
1125 credit_in: CreditOf<T>,
1126 amount_out: T::Balance,
1127 ) -> Result<(CreditOf<T>, CreditOf<T>), (CreditOf<T>, DispatchError)> {
1128 let amount_in_max = credit_in.peek();
1129 let inspect_path = |credit_asset| {
1130 ensure!(
1131 path.first().map_or(false, |a| a == &credit_asset),
1132 Error::<T>::InvalidPath
1133 );
1134 ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
1135 ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
1136
1137 Self::validate_swap_path(&path)?;
1138 let path = Self::balance_path_from_amount_out(amount_out, path)?;
1139
1140 let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::<T>::InvalidPath)?;
1141 ensure!(
1142 amount_in <= amount_in_max,
1143 Error::<T>::ProvidedMaximumNotSufficientForSwap
1144 );
1145
1146 Ok((path, amount_in))
1147 };
1148 let (path, amount_in) = match inspect_path(credit_in.asset()) {
1149 Ok((p, a)) => (p, a),
1150 Err(e) => return Err((credit_in, e)),
1151 };
1152
1153 let (credit_in, credit_change) = credit_in.split(amount_in);
1154 let credit_out = Self::credit_swap(credit_in, &path)?;
1155
1156 Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path });
1157
1158 Ok((credit_out, credit_change))
1159 }
1160
1161 fn swap(
1169 sender: &T::AccountId,
1170 path: &BalancePath<T>,
1171 send_to: &T::AccountId,
1172 keep_alive: bool,
1173 ) -> Result<(), DispatchError> {
1174 let (asset_in, amount_in) = path.first().ok_or(Error::<T>::InvalidPath)?;
1175 let credit_in = Self::withdraw(asset_in.clone(), sender, *amount_in, keep_alive)?;
1176
1177 let credit_out = Self::credit_swap(credit_in, path).map_err(|(_, e)| e)?;
1178 T::Assets::resolve(send_to, credit_out).map_err(|_| Error::<T>::BelowMinimum)?;
1179
1180 Ok(())
1181 }
1182
1183 fn credit_swap(
1195 credit_in: CreditOf<T>,
1196 path: &BalancePath<T>,
1197 ) -> Result<CreditOf<T>, (CreditOf<T>, DispatchError)> {
1198 let resolve_path = || -> Result<CreditOf<T>, DispatchError> {
1199 for pos in 0..=path.len() {
1200 if let Some([(asset1, _), (asset2, amount_out)]) = path.get(pos..=pos + 1) {
1201 let pool_from = T::PoolLocator::pool_address(asset1, asset2)
1202 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1203
1204 if let Some((asset3, _)) = path.get(pos + 2) {
1205 let pool_to = T::PoolLocator::pool_address(asset2, asset3)
1206 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1207
1208 T::Assets::transfer(
1209 asset2.clone(),
1210 &pool_from,
1211 &pool_to,
1212 *amount_out,
1213 Preserve,
1214 )?;
1215 } else {
1216 let credit_out =
1217 Self::withdraw(asset2.clone(), &pool_from, *amount_out, true)?;
1218 return Ok(credit_out);
1219 }
1220 }
1221 }
1222 Err(Error::<T>::InvalidPath.into())
1223 };
1224
1225 let credit_out = match resolve_path() {
1226 Ok(c) => c,
1227 Err(e) => return Err((credit_in, e)),
1228 };
1229
1230 let pool_to = if let Some([(asset1, _), (asset2, _)]) = path.get(0..2) {
1231 match T::PoolLocator::pool_address(asset1, asset2) {
1232 Ok(address) => address,
1233 Err(_) => return Err((credit_in, Error::<T>::InvalidAssetPair.into())),
1234 }
1235 } else {
1236 return Err((credit_in, Error::<T>::InvalidPath.into()));
1237 };
1238
1239 T::Assets::resolve(&pool_to, credit_in)
1240 .map_err(|c| (c, Error::<T>::BelowMinimum.into()))?;
1241
1242 Ok(credit_out)
1243 }
1244
1245 fn withdraw(
1247 asset: T::AssetKind,
1248 who: &T::AccountId,
1249 value: T::Balance,
1250 keep_alive: bool,
1251 ) -> Result<CreditOf<T>, DispatchError> {
1252 let preservation = match keep_alive {
1253 true => Preserve,
1254 false => Expendable,
1255 };
1256 if preservation == Preserve {
1257 let free = T::Assets::reducible_balance(asset.clone(), who, preservation, Polite);
1260 ensure!(free >= value, TokenError::NotExpendable);
1261 }
1262 T::Assets::withdraw(asset, who, value, Exact, preservation, Polite)
1263 }
1264
1265 pub(crate) fn get_balance(owner: &T::AccountId, asset: T::AssetKind) -> T::Balance {
1268 T::Assets::balance(asset, owner)
1269 }
1270
1271 pub fn pool_fee(pool_id: &T::PoolId) -> Permill {
1276 PoolFees::<T>::get(pool_id).unwrap_or_else(T::LPFee::get)
1277 }
1278
1279 pub(crate) fn pool_fee_for(
1283 asset1: &T::AssetKind,
1284 asset2: &T::AssetKind,
1285 ) -> Result<Permill, DispatchError> {
1286 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
1287 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1288 Ok(Self::pool_fee(&pool_id))
1289 }
1290
1291 pub(crate) fn balance_path_from_amount_out(
1293 amount_out: T::Balance,
1294 path: Vec<T::AssetKind>,
1295 ) -> Result<BalancePath<T>, DispatchError> {
1296 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1297 let mut amount_in: T::Balance = amount_out;
1298
1299 let mut iter = path.into_iter().rev().peekable();
1300 while let Some(asset2) = iter.next() {
1301 let asset1 = match iter.peek() {
1302 Some(a) => a,
1303 None => {
1304 balance_path.push((asset2, amount_in));
1305 break;
1306 },
1307 };
1308 let fee = Self::pool_fee_for(asset1, &asset2)?;
1309 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1310 balance_path.push((asset2, amount_in));
1311 amount_in = Self::get_amount_in(fee, &amount_in, &reserve_in, &reserve_out)?;
1312 }
1313 balance_path.reverse();
1314
1315 Ok(balance_path)
1316 }
1317
1318 pub(crate) fn balance_path_from_amount_in(
1320 amount_in: T::Balance,
1321 path: Vec<T::AssetKind>,
1322 ) -> Result<BalancePath<T>, DispatchError> {
1323 let mut balance_path: BalancePath<T> = Vec::with_capacity(path.len());
1324 let mut amount_out: T::Balance = amount_in;
1325
1326 let mut iter = path.into_iter().peekable();
1327 while let Some(asset1) = iter.next() {
1328 let asset2 = match iter.peek() {
1329 Some(a) => a,
1330 None => {
1331 balance_path.push((asset1, amount_out));
1332 break;
1333 },
1334 };
1335 let fee = Self::pool_fee_for(&asset1, asset2)?;
1336 let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?;
1337 balance_path.push((asset1, amount_out));
1338 amount_out = Self::get_amount_out(fee, &amount_out, &reserve_in, &reserve_out)?;
1339 }
1340 Ok(balance_path)
1341 }
1342
1343 pub fn quote(
1345 amount: &T::Balance,
1346 reserve1: &T::Balance,
1347 reserve2: &T::Balance,
1348 ) -> Result<T::Balance, Error<T>> {
1349 Self::mul_div(amount, reserve2, reserve1)
1351 }
1352
1353 pub(super) fn calc_lp_amount_for_zero_supply(
1354 amount1: &T::Balance,
1355 amount2: &T::Balance,
1356 ) -> Result<T::Balance, Error<T>> {
1357 let amount1 = T::HigherPrecisionBalance::from(*amount1);
1358 let amount2 = T::HigherPrecisionBalance::from(*amount2);
1359
1360 let result = amount1
1361 .checked_mul(&amount2)
1362 .ok_or(Error::<T>::Overflow)?
1363 .integer_sqrt()
1364 .checked_sub(&T::MintMinLiquidity::get().into())
1365 .ok_or(Error::<T>::InsufficientLiquidityMinted)?;
1366
1367 result.try_into().map_err(|_| Error::<T>::Overflow)
1368 }
1369
1370 fn mul_div(a: &T::Balance, b: &T::Balance, c: &T::Balance) -> Result<T::Balance, Error<T>> {
1371 let a = T::HigherPrecisionBalance::from(*a);
1372 let b = T::HigherPrecisionBalance::from(*b);
1373 let c = T::HigherPrecisionBalance::from(*c);
1374
1375 let result = a
1376 .checked_mul(&b)
1377 .ok_or(Error::<T>::Overflow)?
1378 .checked_div(&c)
1379 .ok_or(Error::<T>::Overflow)?;
1380
1381 result.try_into().map_err(|_| Error::<T>::Overflow)
1382 }
1383
1384 pub fn get_amount_out(
1389 fee: Permill,
1390 amount_in: &T::Balance,
1391 reserve_in: &T::Balance,
1392 reserve_out: &T::Balance,
1393 ) -> Result<T::Balance, Error<T>> {
1394 let amount_in = T::HigherPrecisionBalance::from(*amount_in);
1395 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1396 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1397
1398 if reserve_in.is_zero() || reserve_out.is_zero() {
1399 return Err(Error::<T>::ZeroLiquidity);
1400 }
1401
1402 let fee_complement = fee.left_from_one().deconstruct();
1403 let amount_in_with_fee = amount_in
1404 .checked_mul(&T::HigherPrecisionBalance::from(fee_complement))
1405 .ok_or(Error::<T>::Overflow)?;
1406
1407 let numerator =
1408 amount_in_with_fee.checked_mul(&reserve_out).ok_or(Error::<T>::Overflow)?;
1409
1410 let denominator = reserve_in
1411 .checked_mul(&T::HigherPrecisionBalance::from(Permill::ACCURACY))
1412 .ok_or(Error::<T>::Overflow)?
1413 .checked_add(&amount_in_with_fee)
1414 .ok_or(Error::<T>::Overflow)?;
1415
1416 let result = numerator.checked_div(&denominator).ok_or(Error::<T>::Overflow)?;
1417
1418 result.try_into().map_err(|_| Error::<T>::Overflow)
1419 }
1420
1421 pub fn get_amount_in(
1426 fee: Permill,
1427 amount_out: &T::Balance,
1428 reserve_in: &T::Balance,
1429 reserve_out: &T::Balance,
1430 ) -> Result<T::Balance, Error<T>> {
1431 let amount_out = T::HigherPrecisionBalance::from(*amount_out);
1432 let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
1433 let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
1434
1435 if reserve_in.is_zero() || reserve_out.is_zero() {
1436 Err(Error::<T>::ZeroLiquidity)?
1437 }
1438
1439 if amount_out >= reserve_out {
1440 Err(Error::<T>::AmountOutTooHigh)?
1441 }
1442
1443 let fee_complement = fee.left_from_one().deconstruct();
1444 let numerator = reserve_in
1445 .checked_mul(&amount_out)
1446 .ok_or(Error::<T>::Overflow)?
1447 .checked_mul(&T::HigherPrecisionBalance::from(Permill::ACCURACY))
1448 .ok_or(Error::<T>::Overflow)?;
1449
1450 let denominator = reserve_out
1451 .checked_sub(&amount_out)
1452 .ok_or(Error::<T>::Overflow)?
1453 .checked_mul(&T::HigherPrecisionBalance::from(fee_complement))
1454 .ok_or(Error::<T>::Overflow)?;
1455
1456 let result = numerator
1457 .checked_div(&denominator)
1458 .ok_or(Error::<T>::Overflow)?
1459 .checked_add(&One::one())
1460 .ok_or(Error::<T>::Overflow)?;
1461
1462 result.try_into().map_err(|_| Error::<T>::Overflow)
1463 }
1464
1465 fn validate_swap_path(path: &Vec<T::AssetKind>) -> Result<(), DispatchError> {
1467 ensure!(path.len() >= 2, Error::<T>::InvalidPath);
1468 ensure!(path.len() as u32 <= T::MaxSwapPathLength::get(), Error::<T>::InvalidPath);
1469
1470 let mut pools = BTreeSet::<T::PoolId>::new();
1472 for assets_pair in path.windows(2) {
1473 if let [asset1, asset2] = assets_pair {
1474 let pool_id = T::PoolLocator::pool_id(asset1, asset2)
1475 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1476
1477 let new_element = pools.insert(pool_id);
1478 if !new_element {
1479 return Err(Error::<T>::NonUniquePath.into());
1480 }
1481 }
1482 }
1483 Ok(())
1484 }
1485
1486 #[cfg(any(test, feature = "runtime-benchmarks"))]
1488 pub fn get_next_pool_asset_id() -> T::PoolAssetId {
1489 NextPoolAssetId::<T>::get()
1490 .or(T::PoolAssetId::initial_value())
1491 .expect("Next pool asset ID can not be None")
1492 }
1493 }
1494
1495 #[pallet::view_functions]
1496 impl<T: Config> Pallet<T> {
1497 pub fn get_reserves(
1500 asset1: T::AssetKind,
1501 asset2: T::AssetKind,
1502 ) -> Result<(T::Balance, T::Balance), Error<T>> {
1503 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2)
1504 .map_err(|_| Error::<T>::InvalidAssetPair)?;
1505
1506 let balance1 = Self::get_balance(&pool_account, asset1);
1507 let balance2 = Self::get_balance(&pool_account, asset2);
1508
1509 if balance1.is_zero() || balance2.is_zero() {
1510 Err(Error::<T>::PoolEmpty)?;
1511 }
1512
1513 Ok((balance1, balance2))
1514 }
1515
1516 pub fn quote_price_exact_tokens_for_tokens(
1524 asset1: T::AssetKind,
1525 asset2: T::AssetKind,
1526 amount: T::Balance,
1527 include_fee: bool,
1528 ) -> Option<T::Balance> {
1529 if amount.is_zero() {
1531 return None;
1532 }
1533
1534 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1535
1536 let (balance1, balance2) = Self::get_reserves(asset1.clone(), asset2.clone()).ok()?;
1537
1538 if balance1.is_zero() {
1539 return None;
1540 }
1541
1542 let amount_out = if include_fee {
1543 let fee = Self::pool_fee_for(&asset1, &asset2).ok()?;
1544 Self::get_amount_out(fee, &amount, &balance1, &balance2).ok()?
1545 } else {
1546 Self::quote(&amount, &balance1, &balance2).ok()?
1547 };
1548
1549 if amount_out.is_zero() {
1551 return None;
1552 }
1553
1554 let max_output = T::Assets::reducible_balance(asset2, &pool_account, Preserve, Polite);
1557 if amount_out > max_output {
1558 return None;
1559 }
1560
1561 Some(amount_out)
1562 }
1563
1564 pub fn quote_price_tokens_for_exact_tokens(
1572 asset1: T::AssetKind,
1573 asset2: T::AssetKind,
1574 amount: T::Balance,
1575 include_fee: bool,
1576 ) -> Option<T::Balance> {
1577 if amount.is_zero() {
1579 return None;
1580 }
1581 let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?;
1582
1583 let (balance1, balance2) = Self::get_reserves(asset1.clone(), asset2.clone()).ok()?;
1584
1585 if balance1.is_zero() {
1586 return None;
1587 }
1588
1589 let max_output =
1592 T::Assets::reducible_balance(asset2.clone(), &pool_account, Preserve, Polite);
1593 if amount > max_output {
1594 return None;
1595 }
1596
1597 if include_fee {
1598 let fee = Self::pool_fee_for(&asset1, &asset2).ok()?;
1599 Self::get_amount_in(fee, &amount, &balance1, &balance2).ok()
1600 } else {
1601 Self::quote(&amount, &balance2, &balance1).ok()
1602 }
1603 }
1604 }
1605}
1606
1607sp_api::decl_runtime_apis! {
1608 pub trait AssetConversionApi<Balance, AssetId>
1611 where
1612 Balance: frame_support::traits::tokens::Balance + MaybeDisplay,
1613 AssetId: Codec,
1614 {
1615 fn quote_price_tokens_for_exact_tokens(
1620 asset1: AssetId,
1621 asset2: AssetId,
1622 amount: Balance,
1623 include_fee: bool,
1624 ) -> Option<Balance>;
1625
1626 fn quote_price_exact_tokens_for_tokens(
1631 asset1: AssetId,
1632 asset2: AssetId,
1633 amount: Balance,
1634 include_fee: bool,
1635 ) -> Option<Balance>;
1636
1637 fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>;
1639 }
1640}
1641
1642sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);