Skip to main content

pallet_asset_conversion/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Substrate Asset Conversion pallet
19//!
20//! Substrate Asset Conversion pallet based on the [Uniswap V2](https://github.com/Uniswap/v2-core) logic.
21//!
22//! ## Overview
23//!
24//! This pallet allows you to:
25//!
26//!  - [create a liquidity pool](`Pallet::create_pool()`) for 2 assets
27//!  - [provide the liquidity](`Pallet::add_liquidity()`) and receive back an LP token
28//!  - [exchange the LP token back to assets](`Pallet::remove_liquidity()`)
29//!  - [swap a specific amount of assets for another](`Pallet::swap_exact_tokens_for_tokens()`) if
30//!    there is a pool created, or
31//!  - [swap some assets for a specific amount of
32//!    another](`Pallet::swap_tokens_for_exact_tokens()`).
33//!  - [query for an exchange price](`AssetConversionApi::quote_price_exact_tokens_for_tokens`) via
34//!    a runtime call endpoint
35//!  - [query the size of a liquidity pool](`AssetConversionApi::get_reserves`) via a runtime api
36//!    endpoint.
37//!
38//! The `quote_price_exact_tokens_for_tokens` and `quote_price_tokens_for_exact_tokens` functions
39//! both take a path parameter of the route to take. If you want to swap from native asset to
40//! non-native asset 1, you would pass in a path of `[DOT, 1]` or `[1, DOT]`. If you want to swap
41//! from non-native asset 1 to non-native asset 2, you would pass in a path of `[1, DOT, 2]`.
42//!
43//! (For an example of configuring this pallet to use `Location` as an asset id, see the
44//! cumulus repo).
45//!
46//! Here is an example `state_call` that asks for a quote of a pool of native versus asset 1:
47//!
48//! ```text
49//! curl -sS -H "Content-Type: application/json" -d \
50//! '{"id":1, "jsonrpc":"2.0", "method": "state_call", "params": ["AssetConversionApi_quote_price_tokens_for_exact_tokens", "0x0101000000000000000000000011000000000000000000"]}' \
51//! http://localhost:9933/
52//! ```
53//! (This can be run against the kitchen sync node in the `node` folder of this repo.)
54#![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		/// Overarching event type.
117		#[allow(deprecated)]
118		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
119
120		/// The type in which the assets for swapping are measured.
121		type Balance: Balance;
122
123		/// A type used for calculations concerning the `Balance` type to avoid possible overflows.
124		type HigherPrecisionBalance: IntegerSquareRoot
125			+ One
126			+ Ensure
127			+ Unsigned
128			+ From<u32>
129			+ From<Self::Balance>
130			+ TryInto<Self::Balance>;
131
132		/// Type of asset class, sourced from [`Config::Assets`], utilized to offer liquidity to a
133		/// pool.
134		type AssetKind: Parameter + MaxEncodedLen + MaybeSerializeDeserialize;
135
136		/// Registry of assets utilized for providing liquidity to pools.
137		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		/// Liquidity pool identifier.
144		type PoolId: Parameter + MaxEncodedLen + Ord;
145
146		/// Provides means to resolve the [`Config::PoolId`] and it's `AccountId` from a pair
147		/// of [`Config::AssetKind`]s.
148		///
149		/// Examples: [`crate::types::WithFirstAsset`], [`crate::types::Ascending`].
150		type PoolLocator: PoolLocator<Self::AccountId, Self::AssetKind, Self::PoolId>;
151
152		/// Asset class for the lp tokens from [`Self::PoolAssets`].
153		type PoolAssetId: AssetId + PartialOrd + Incrementable + From<u32>;
154
155		/// Registry for the lp tokens. Ideally only this pallet should have create permissions on
156		/// the assets.
157		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		/// The fraction of every swap that the liquidity providers take as a fee.
164		///
165		/// Used as the default swap fee for any pool that has no per-pool override set in
166		/// [`PoolFees`]. See [`Pallet::pool_fee`].
167		#[pallet::constant]
168		type LPFee: Get<Permill>;
169
170		/// The origin permitted to set per-pool swap fees, via
171		/// [`Pallet::create_pool_with_fee`] and [`Pallet::set_pool_fee`].
172		type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
173
174		/// The maximum swap fee that can be set for a pool.
175		#[pallet::constant]
176		type MaxSwapFee: Get<Permill>;
177
178		/// A one-time fee to setup the pool.
179		#[pallet::constant]
180		type PoolSetupFee: Get<Self::Balance>;
181
182		/// Asset class from [`Config::Assets`] used to pay the [`Config::PoolSetupFee`].
183		#[pallet::constant]
184		type PoolSetupFeeAsset: Get<Self::AssetKind>;
185
186		/// Handler for the [`Config::PoolSetupFee`].
187		type PoolSetupFeeTarget: OnUnbalanced<CreditOf<Self>>;
188
189		/// A fee to withdraw the liquidity.
190		#[pallet::constant]
191		type LiquidityWithdrawalFee: Get<Permill>;
192
193		/// The minimum LP token amount that could be minted. Ameliorates rounding errors.
194		#[pallet::constant]
195		type MintMinLiquidity: Get<Self::Balance>;
196
197		/// The max number of hops in a swap.
198		#[pallet::constant]
199		type MaxSwapPathLength: Get<u32>;
200
201		/// The pallet's id, used for deriving its sovereign account ID.
202		#[pallet::constant]
203		type PalletId: Get<PalletId>;
204
205		/// Weight information for extrinsics in this pallet.
206		type WeightInfo: WeightInfo;
207
208		/// The benchmarks need a way to create asset ids from u32s.
209		#[cfg(feature = "runtime-benchmarks")]
210		type BenchmarkHelper: BenchmarkHelper<Self::AssetKind>;
211	}
212
213	/// Map from `PoolAssetId` to `PoolInfo`. This establishes whether a pool has been officially
214	/// created rather than people sending tokens directly to a pool's public account.
215	#[pallet::storage]
216	pub type Pools<T: Config> =
217		StorageMap<_, Blake2_128Concat, T::PoolId, PoolInfo<T::PoolAssetId>, OptionQuery>;
218
219	/// Stores the `PoolAssetId` that is going to be used for the next lp token.
220	/// This gets incremented whenever a new lp pool is created.
221	#[pallet::storage]
222	pub type NextPoolAssetId<T: Config> = StorageValue<_, T::PoolAssetId, OptionQuery>;
223
224	/// Per-pool swap fee overrides.
225	///
226	/// When a pool has no entry here, the global [`Config::LPFee`] applies. This storage is purely
227	/// additive: existing pools and runtimes that never set a per-pool fee behave exactly as before
228	/// and require no migration. See [`Pallet::pool_fee`] for the resolution logic.
229	#[pallet::storage]
230	pub type PoolFees<T: Config> = StorageMap<_, Blake2_128Concat, T::PoolId, Permill, OptionQuery>;
231
232	/// Genesis config for the asset conversion pallet.
233	#[pallet::genesis_config]
234	#[derive(frame_support::DefaultNoBound)]
235	pub struct GenesisConfig<T: Config> {
236		/// Pools to create at genesis with initial liquidity.
237		///
238		/// Each entry is `(asset1, asset2, liquidity_provider, amount1, amount2)`.
239		/// The `liquidity_provider` must hold sufficient balances of both assets
240		/// (e.g. via `pallet_balances` / `pallet_assets` genesis configs).
241		/// Set both amounts to zero to create a pool without initial liquidity.
242		/// No pool setup fee is charged at genesis.
243		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's events.
265	#[pallet::event]
266	#[pallet::generate_deposit(pub(super) fn deposit_event)]
267	pub enum Event<T: Config> {
268		/// A successful call of the `CreatePool` extrinsic will create this event.
269		PoolCreated {
270			/// The account that created the pool.
271			creator: T::AccountId,
272			/// The pool id associated with the pool. Note that the order of the assets may not be
273			/// the same as the order specified in the create pool extrinsic.
274			pool_id: T::PoolId,
275			/// The account ID of the pool.
276			pool_account: T::AccountId,
277			/// The id of the liquidity tokens that will be minted when assets are added to this
278			/// pool.
279			lp_token: T::PoolAssetId,
280		},
281
282		/// A pool's swap fee was set, either at creation via [`Pallet::create_pool_with_fee`]
283		/// or afterwards via [`Pallet::set_pool_fee`].
284		PoolFeeSet {
285			/// The pool whose fee was set.
286			pool_id: T::PoolId,
287			/// The swap fee now applied to the pool.
288			fee: Permill,
289		},
290
291		/// A successful call of the `AddLiquidity` extrinsic will create this event.
292		LiquidityAdded {
293			/// The account that the liquidity was taken from.
294			who: T::AccountId,
295			/// The account that the liquidity tokens were minted to.
296			mint_to: T::AccountId,
297			/// The pool id of the pool that the liquidity was added to.
298			pool_id: T::PoolId,
299			/// The amount of the first asset that was added to the pool.
300			amount1_provided: T::Balance,
301			/// The amount of the second asset that was added to the pool.
302			amount2_provided: T::Balance,
303			/// The id of the lp token that was minted.
304			lp_token: T::PoolAssetId,
305			/// The amount of lp tokens that were minted of that id.
306			lp_token_minted: T::Balance,
307		},
308
309		/// A successful call of the `RemoveLiquidity` extrinsic will create this event.
310		LiquidityRemoved {
311			/// The account that the liquidity tokens were burned from.
312			who: T::AccountId,
313			/// The account that the assets were transferred to.
314			withdraw_to: T::AccountId,
315			/// The pool id that the liquidity was removed from.
316			pool_id: T::PoolId,
317			/// The amount of the first asset that was removed from the pool.
318			amount1: T::Balance,
319			/// The amount of the second asset that was removed from the pool.
320			amount2: T::Balance,
321			/// The id of the lp token that was burned.
322			lp_token: T::PoolAssetId,
323			/// The amount of lp tokens that were burned of that id.
324			lp_token_burned: T::Balance,
325			/// Liquidity withdrawal fee (%).
326			withdrawal_fee: Permill,
327		},
328		/// Assets have been converted from one to another. Both `SwapExactTokenForToken`
329		/// and `SwapTokenForExactToken` will generate this event.
330		SwapExecuted {
331			/// Which account was the instigator of the swap.
332			who: T::AccountId,
333			/// The account that the assets were transferred to.
334			send_to: T::AccountId,
335			/// The amount of the first asset that was swapped.
336			amount_in: T::Balance,
337			/// The amount of the second asset that was received.
338			amount_out: T::Balance,
339			/// The route of asset IDs with amounts that the swap went through.
340			/// E.g. (A, amount_in) -> (Dot, amount_out) -> (B, amount_out)
341			path: BalancePath<T>,
342		},
343		/// Assets have been converted from one to another.
344		SwapCreditExecuted {
345			/// The amount of the first asset that was swapped.
346			amount_in: T::Balance,
347			/// The amount of the second asset that was received.
348			amount_out: T::Balance,
349			/// The route of asset IDs with amounts that the swap went through.
350			/// E.g. (A, amount_in) -> (Dot, amount_out) -> (B, amount_out)
351			path: BalancePath<T>,
352		},
353		/// Pool has been touched in order to fulfill operational requirements.
354		Touched {
355			/// The ID of the pool.
356			pool_id: T::PoolId,
357			/// The account initiating the touch.
358			who: T::AccountId,
359		},
360	}
361
362	#[pallet::error]
363	pub enum Error<T> {
364		/// Provided asset pair is not supported for pool.
365		InvalidAssetPair,
366		/// Pool already exists.
367		PoolExists,
368		/// Desired amount can't be zero.
369		WrongDesiredAmount,
370		/// Provided amount should be greater than or equal to the existential deposit/asset's
371		/// minimal amount.
372		AmountOneLessThanMinimal,
373		/// Provided amount should be greater than or equal to the existential deposit/asset's
374		/// minimal amount.
375		AmountTwoLessThanMinimal,
376		/// Reserve needs to always be greater than or equal to the existential deposit/asset's
377		/// minimal amount.
378		ReserveLeftLessThanMinimal,
379		/// Desired amount can't be equal to the pool reserve.
380		AmountOutTooHigh,
381		/// The pool doesn't exist.
382		PoolNotFound,
383		/// An overflow happened.
384		Overflow,
385		/// The minimal amount requirement for the first token in the pair wasn't met.
386		AssetOneDepositDidNotMeetMinimum,
387		/// The minimal amount requirement for the second token in the pair wasn't met.
388		AssetTwoDepositDidNotMeetMinimum,
389		/// The minimal amount requirement for the first token in the pair wasn't met.
390		AssetOneWithdrawalDidNotMeetMinimum,
391		/// The minimal amount requirement for the second token in the pair wasn't met.
392		AssetTwoWithdrawalDidNotMeetMinimum,
393		/// Optimal calculated amount is less than desired.
394		OptimalAmountLessThanDesired,
395		/// Insufficient liquidity minted.
396		InsufficientLiquidityMinted,
397		/// Requested liquidity can't be zero.
398		ZeroLiquidity,
399		/// Amount can't be zero.
400		ZeroAmount,
401		/// Calculated amount out is less than provided minimum amount.
402		ProvidedMinimumNotSufficientForSwap,
403		/// Provided maximum amount is not sufficient for swap.
404		ProvidedMaximumNotSufficientForSwap,
405		/// The provided path must consists of 2 assets at least.
406		InvalidPath,
407		/// The provided path must consists of unique assets.
408		NonUniquePath,
409		/// It was not possible to get or increment the Id of the pool.
410		IncorrectPoolAssetId,
411		/// The destination account cannot exist with the swapped funds.
412		BelowMinimum,
413		/// The pool exists but has no liquidity (at least one of the reserves is zero).
414		PoolEmpty,
415		/// The fee exceeds [`Config::MaxSwapFee`].
416		FeeTooHigh,
417		/// The pool account can't send one of its assets (e.g. the asset class is frozen or the
418		/// pool account is blocked).
419		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's callable functions.
437	#[pallet::call]
438	impl<T: Config> Pallet<T> {
439		/// Creates an empty liquidity pool and an associated new `lp_token` asset
440		/// (the id of which is returned in the `Event::PoolCreated` event).
441		///
442		/// Once a pool is created, someone may [`Pallet::add_liquidity`] to it.
443		#[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		/// Provide liquidity into the pool of `asset1` and `asset2`.
456		/// NOTE: an optimal amount of asset1 and asset2 will be calculated and
457		/// might be different than the provided `amount1_desired`/`amount2_desired`
458		/// thus you should provide the min amount you're happy to provide.
459		/// Params `amount1_min`/`amount2_min` represent that.
460		/// `mint_to` will be sent the liquidity tokens that represent this share of the pool.
461		///
462		/// NOTE: when encountering an incorrect exchange rate and non-withdrawable pool liquidity,
463		/// batch an atomic call with [`Pallet::add_liquidity`] and
464		/// [`Pallet::swap_exact_tokens_for_tokens`] or [`Pallet::swap_tokens_for_exact_tokens`]
465		/// calls to render the liquidity withdrawable and rectify the exchange rate.
466		///
467		/// Once liquidity is added, someone may successfully call
468		/// [`Pallet::swap_exact_tokens_for_tokens`].
469		#[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		/// Allows you to remove liquidity by providing the `lp_token_burn` tokens that will be
496		/// burned in the process. With the usage of `amount1_min_receive`/`amount2_min_receive`
497		/// it's possible to control the min amount of returned tokens you're happy with.
498		#[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		/// Swap the exact amount of `asset1` into `asset2`.
523		/// `amount_out_min` param allows you to specify the min amount of the `asset2`
524		/// you're happy to receive.
525		///
526		/// [`AssetConversionApi::quote_price_exact_tokens_for_tokens`] runtime call can be called
527		/// for a quote.
528		#[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		/// Swap any amount of `asset1` to get the exact amount of `asset2`.
551		/// `amount_in_max` param allows to specify the max amount of the `asset1`
552		/// you're happy to provide.
553		///
554		/// [`AssetConversionApi::quote_price_tokens_for_exact_tokens`] runtime call can be called
555		/// for a quote.
556		#[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		/// Touch an existing pool to fulfill prerequisites before providing liquidity, such as
579		/// ensuring that the pool's accounts are in place. It is typically useful when a pool
580		/// creator removes the pool's accounts and does not provide a liquidity. This action may
581		/// involve holding assets from the caller as a deposit for creating the pool's accounts.
582		///
583		/// The origin must be Signed.
584		///
585		/// - `asset1`: The asset ID of an existing pool with a pair (asset1, asset2).
586		/// - `asset2`: The asset ID of an existing pool with a pair (asset1, asset2).
587		///
588		/// Emits `Touched` event when successful.
589		#[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		/// Like [`Pallet::create_pool`], but sets an initial per-pool swap `fee` overriding the
622		/// global [`Config::LPFee`].
623		///
624		/// Requires [`Config::AdminOrigin`]. `creator` pays the pool setup fee and deposits.
625		/// `fee` must not exceed [`Config::MaxSwapFee`].
626		///
627		/// Emits both [`Event::PoolCreated`] and [`Event::PoolFeeSet`] on success.
628		#[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		/// Set the per-pool swap `fee` for an existing pool, overriding the global
643		/// [`Config::LPFee`].
644		///
645		/// Requires [`Config::AdminOrigin`]. `fee` must not exceed [`Config::MaxSwapFee`].
646		///
647		/// Emits [`Event::PoolFeeSet`] on success.
648		#[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		/// Create a pool at genesis, bypassing the setup fee.
666		///
667		/// The `lp_provider` must already hold sufficient balances of both assets.
668		/// If both `amount1` and `amount2` are non-zero, initial liquidity is added.
669		/// Returns the LP token amount minted to `lp_provider` (zero if no liquidity).
670		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			// Allocate LP token ID.
687			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			// Create LP token asset.
694			T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
695
696			// Touch asset accounts for the pool account.
697			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			// Register pool.
708			Pools::<T>::insert(pool_id, PoolInfo { lp_token: lp_token.clone() });
709
710			// Add initial liquidity if amounts are non-zero.
711			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		/// Create a new liquidity pool.
730		///
731		/// **Warning**: The storage must be rolled back on error.
732		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			// prepare pool_id
744			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			// pay the setup fee
752			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		/// Add liquidity to a pool.
794		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		/// Remove liquidity from a pool.
898		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			// burn the provided lp token amount that includes the fee
945			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		/// Swap exactly `amount_in` of asset `path[0]` for asset `path[1]`.
972		/// If an `amount_out_min` is specified, it will return an error if it is unable to acquire
973		/// the amount desired.
974		///
975		/// Withdraws the `path[0]` asset from `sender`, deposits the `path[1]` asset to `send_to`,
976		/// respecting `keep_alive`.
977		///
978		/// If successful, returns the amount of `path[1]` acquired for the `amount_in`.
979		///
980		/// WARNING: This may return an error after a partial storage mutation. It should be used
981		/// only inside a transactional storage context and an Err result must imply a storage
982		/// rollback.
983		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		/// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[1]`. If an
1020		/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
1021		/// too costly.
1022		///
1023		/// Withdraws `path[0]` asset from `sender`, deposits the `path[1]` asset to `send_to`,
1024		/// respecting `keep_alive`.
1025		///
1026		/// If successful returns the amount of the `path[0]` taken to provide `path[1]`.
1027		///
1028		/// WARNING: This may return an error after a partial storage mutation. It should be used
1029		/// only inside a transactional storage context and an Err result must imply a storage
1030		/// rollback.
1031		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		/// Swap exactly `credit_in` of asset `path[0]` for asset `path[last]`.  If `amount_out_min`
1069		/// is provided and the swap can't achieve at least this amount, an error is returned.
1070		///
1071		/// On a successful swap, the function returns the `credit_out` of `path[last]` obtained
1072		/// from the `credit_in`. On failure, it returns an `Err` containing the original
1073		/// `credit_in` and the associated error code.
1074		///
1075		/// WARNING: This may return an error after a partial storage mutation. It should be used
1076		/// only inside a transactional storage context and an Err result must imply a storage
1077		/// rollback.
1078		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		/// Swaps a portion of `credit_in` of `path[0]` asset to obtain the desired `amount_out` of
1115		/// the `path[last]` asset. The provided `credit_in` must be adequate to achieve the target
1116		/// `amount_out`, or an error will occur.
1117		///
1118		/// On success, the function returns a (`credit_out`, `credit_change`) tuple, where
1119		/// `credit_out` represents the acquired amount of the `path[last]` asset, and
1120		/// `credit_change` is the remaining portion from the `credit_in`. On failure, an `Err` with
1121		/// the initial `credit_in` and error code is returned.
1122		///
1123		/// WARNING: This may return an error after a partial storage mutation. It should be used
1124		/// only inside a transactional storage context and an Err result must imply a storage
1125		/// rollback.
1126		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					// Return the initial `credit_in` in full. `credit_change` must not be dropped
1161					// here, since the caller rolls back storage on error, including the issuance
1162					// change of the drop. Both halves come from the same `split`, so they share
1163					// the asset and `subsume` cannot fail.
1164					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		/// Swap assets along the `path`, withdrawing from `sender` and depositing in `send_to`.
1175		///
1176		/// Note: It's assumed that the provided `path` is valid.
1177		///
1178		/// WARNING: This may return an error after a partial storage mutation. It should be used
1179		/// only inside a transactional storage context and an Err result must imply a storage
1180		/// rollback.
1181		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		/// Swap assets along the specified `path`, consuming `credit_in` and producing
1197		/// `credit_out`.
1198		///
1199		/// If an error occurs, `credit_in` is returned back.
1200		///
1201		/// Note: It's assumed that the provided `path` is valid and `credit_in` corresponds to the
1202		/// first asset in the `path`.
1203		///
1204		/// WARNING: This may return an error after a partial storage mutation. It should be used
1205		/// only inside a transactional storage context and an Err result must imply a storage
1206		/// rollback.
1207		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		/// Removes `value` balance of `asset` from `who` account if possible.
1259		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				// TODO drop the ensure! when this issue addressed
1271				// https://github.com/paritytech/polkadot-sdk/issues/1698
1272				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		/// Get the `owner`'s balance of `asset`, which could be the chain's native asset or another
1279		/// fungible. Returns a value in the form of an `Balance`.
1280		pub(crate) fn get_balance(owner: &T::AccountId, asset: T::AssetKind) -> T::Balance {
1281			T::Assets::balance(asset, owner)
1282		}
1283
1284		/// Ensures nothing like a freeze or block stops `pool_account` from sending its `asset`.
1285		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			// Only a pool that can't send anything above its minimum balance counts as blocked.
1293			// Partial restrictions like locks are ignored, since anyone can add them.
1294			let blocked = balance > minimum && spendable.is_zero();
1295			ensure!(!blocked, Error::<T>::PoolUnavailable);
1296			Ok(())
1297		}
1298
1299		/// Resolve the effective swap fee for `pool_id`.
1300		///
1301		/// Returns the per-pool override from [`PoolFees`] if one is set, otherwise falls back to
1302		/// the global [`Config::LPFee`].
1303		pub fn pool_fee(pool_id: &T::PoolId) -> Permill {
1304			PoolFees::<T>::get(pool_id).unwrap_or_else(T::LPFee::get)
1305		}
1306
1307		/// Resolve the effective swap fee for the pool of the `asset1`/`asset2` pair.
1308		///
1309		/// See [`Self::pool_fee`] for the resolution logic.
1310		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		/// Leading to an amount at the end of a `path`, get the required amounts in.
1320		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		/// Following an amount into a `path`, get the corresponding amounts out.
1347		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		/// Calculates the optimal amount from the reserves.
1372		pub fn quote(
1373			amount: &T::Balance,
1374			reserve1: &T::Balance,
1375			reserve2: &T::Balance,
1376		) -> Result<T::Balance, Error<T>> {
1377			// (amount * reserve2) / reserve1
1378			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		/// Calculates amount out for a given swap `fee`.
1413		///
1414		/// Given an input amount of an asset and pair reserves, returns the maximum output amount
1415		/// of the other asset.
1416		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		/// Calculates amount in for a given swap `fee`.
1450		///
1451		/// Given an output amount of an asset and pair reserves, returns a required input amount
1452		/// of the other asset.
1453		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		/// Ensure that a path is valid.
1494		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			// validate all the pools in the path are unique
1499			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		/// Returns the next pool asset id for benchmark purposes only.
1515		#[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		/// Returns the balance of each asset in the pool.
1526		/// The tuple result is in the order requested (not necessarily the same as pool order).
1527		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		/// Gets a quote for swapping an exact amount of `asset1` for `asset2`.
1548		///
1549		/// If `include_fee` is true, the quote will include the liquidity provider fee.
1550		/// If the pool does not exist or has no liquidity, `None` is returned.
1551		/// Note that the price may have changed by the time the transaction is executed.
1552		/// (Use `amount_out_min` to control slippage.)
1553		/// Returns `Some(quoted_amount)` on success.
1554		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			// Swaps reject zero amounts, match that behavior.
1561			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			// Small inputs can round output to zero due to integer division.
1581			if amount_out.is_zero() {
1582				return None;
1583			}
1584
1585			// Swap withdrawals from pools use `keep_alive=true` (Preserve). Use the same
1586			// preservation level to determine the actual withdrawable amount.
1587			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		/// Gets a quote for swapping `amount` of `asset1` for an exact amount of `asset2`.
1596		///
1597		/// If `include_fee` is true, the quote will include the liquidity provider fee.
1598		/// If the pool does not exist or has no liquidity, `None` is returned.
1599		/// Note that the price may have changed by the time the transaction is executed.
1600		/// (Use `amount_in_max` to control slippage.)
1601		/// Returns `Some(quoted_amount)` on success.
1602		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			// Swaps reject zero amounts, match that behavior.
1609			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			// Swap withdrawals from pools use `keep_alive=true` (Preserve). Use the same
1621			// preservation level to determine the actual withdrawable amount.
1622			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	/// This runtime api allows people to query the size of the liquidity pools
1640	/// and quote prices for swaps.
1641	pub trait AssetConversionApi<Balance, AssetId>
1642	where
1643		Balance: frame_support::traits::tokens::Balance + MaybeDisplay,
1644		AssetId: Codec,
1645	{
1646		/// Provides a quote for [`Pallet::swap_tokens_for_exact_tokens`].
1647		///
1648		/// Note that the price may have changed by the time the transaction is executed.
1649		/// (Use `amount_in_max` to control slippage.)
1650		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		/// Provides a quote for [`Pallet::swap_exact_tokens_for_tokens`].
1658		///
1659		/// Note that the price may have changed by the time the transaction is executed.
1660		/// (Use `amount_out_min` to control slippage.)
1661		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		/// Returns the size of the liquidity pool for the given asset pair.
1669		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", $);