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