#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::traits::{DefensiveOption, Incrementable};
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
mod types;
pub mod weights;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod mock;
use codec::Codec;
use frame_support::{
	ensure,
	traits::tokens::{AssetId, Balance},
};
use frame_system::{
	ensure_signed,
	pallet_prelude::{BlockNumberFor, OriginFor},
};
pub use pallet::*;
use sp_arithmetic::traits::Unsigned;
use sp_runtime::{
	traits::{
		CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Ensure, MaybeDisplay, TrailingZeroInput,
	},
	DispatchError,
};
use sp_std::prelude::*;
pub use types::*;
pub use weights::WeightInfo;
#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use frame_support::{
		pallet_prelude::*,
		traits::{
			fungible::{Inspect as InspectFungible, Mutate as MutateFungible},
			fungibles::{Create, Inspect, Mutate},
			tokens::{
				Fortitude::Polite,
				Precision::Exact,
				Preservation::{Expendable, Preserve},
			},
			AccountTouch, ContainsPair,
		},
		BoundedBTreeSet, PalletId,
	};
	use sp_arithmetic::Permill;
	use sp_runtime::{
		traits::{IntegerSquareRoot, One, Zero},
		Saturating,
	};
	#[pallet::pallet]
	pub struct Pallet<T>(_);
	#[pallet::config]
	pub trait Config: frame_system::Config {
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
		type Currency: InspectFungible<Self::AccountId, Balance = Self::Balance>
			+ MutateFungible<Self::AccountId>;
		type Balance: Balance;
		type AssetBalance: Balance;
		type HigherPrecisionBalance: IntegerSquareRoot
			+ One
			+ Ensure
			+ Unsigned
			+ From<u32>
			+ From<Self::AssetBalance>
			+ From<Self::Balance>
			+ TryInto<Self::AssetBalance>
			+ TryInto<Self::Balance>;
		type AssetId: AssetId;
		type MultiAssetId: AssetId + Ord + From<Self::AssetId>;
		type MultiAssetIdConverter: MultiAssetIdConverter<Self::MultiAssetId, Self::AssetId>;
		type PoolAssetId: AssetId + PartialOrd + Incrementable + From<u32>;
		type Assets: Inspect<Self::AccountId, AssetId = Self::AssetId, Balance = Self::AssetBalance>
			+ Mutate<Self::AccountId>
			+ AccountTouch<Self::AssetId, Self::AccountId>
			+ ContainsPair<Self::AssetId, Self::AccountId>;
		type PoolAssets: Inspect<Self::AccountId, AssetId = Self::PoolAssetId, Balance = Self::AssetBalance>
			+ Create<Self::AccountId>
			+ Mutate<Self::AccountId>
			+ AccountTouch<Self::PoolAssetId, Self::AccountId>;
		#[pallet::constant]
		type LPFee: Get<u32>;
		#[pallet::constant]
		type PoolSetupFee: Get<Self::Balance>;
		type PoolSetupFeeReceiver: Get<Self::AccountId>;
		#[pallet::constant]
		type LiquidityWithdrawalFee: Get<Permill>;
		#[pallet::constant]
		type MintMinLiquidity: Get<Self::AssetBalance>;
		#[pallet::constant]
		type MaxSwapPathLength: Get<u32>;
		#[pallet::constant]
		type PalletId: Get<PalletId>;
		#[pallet::constant]
		type AllowMultiAssetPools: Get<bool>;
		type WeightInfo: WeightInfo;
		#[cfg(feature = "runtime-benchmarks")]
		type BenchmarkHelper: BenchmarkHelper<Self::AssetId, Self::MultiAssetId>;
	}
	#[pallet::storage]
	pub type Pools<T: Config> =
		StorageMap<_, Blake2_128Concat, PoolIdOf<T>, PoolInfo<T::PoolAssetId>, OptionQuery>;
	#[pallet::storage]
	pub type NextPoolAssetId<T: Config> = StorageValue<_, T::PoolAssetId, OptionQuery>;
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
		PoolCreated {
			creator: T::AccountId,
			pool_id: PoolIdOf<T>,
			pool_account: T::AccountId,
			lp_token: T::PoolAssetId,
		},
		LiquidityAdded {
			who: T::AccountId,
			mint_to: T::AccountId,
			pool_id: PoolIdOf<T>,
			amount1_provided: T::AssetBalance,
			amount2_provided: T::AssetBalance,
			lp_token: T::PoolAssetId,
			lp_token_minted: T::AssetBalance,
		},
		LiquidityRemoved {
			who: T::AccountId,
			withdraw_to: T::AccountId,
			pool_id: PoolIdOf<T>,
			amount1: T::AssetBalance,
			amount2: T::AssetBalance,
			lp_token: T::PoolAssetId,
			lp_token_burned: T::AssetBalance,
			withdrawal_fee: Permill,
		},
		SwapExecuted {
			who: T::AccountId,
			send_to: T::AccountId,
			path: BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
			amount_in: T::AssetBalance,
			amount_out: T::AssetBalance,
		},
		Transfer {
			from: T::AccountId,
			to: T::AccountId,
			asset: T::MultiAssetId,
			amount: T::AssetBalance,
		},
	}
	#[pallet::error]
	pub enum Error<T> {
		EqualAssets,
		UnsupportedAsset,
		PoolExists,
		WrongDesiredAmount,
		AmountOneLessThanMinimal,
		AmountTwoLessThanMinimal,
		ReserveLeftLessThanMinimal,
		AmountOutTooHigh,
		PoolNotFound,
		Overflow,
		AssetOneDepositDidNotMeetMinimum,
		AssetTwoDepositDidNotMeetMinimum,
		AssetOneWithdrawalDidNotMeetMinimum,
		AssetTwoWithdrawalDidNotMeetMinimum,
		OptimalAmountLessThanDesired,
		InsufficientLiquidityMinted,
		ZeroLiquidity,
		ZeroAmount,
		InsufficientLiquidity,
		ProvidedMinimumNotSufficientForSwap,
		ProvidedMaximumNotSufficientForSwap,
		PoolMustContainNativeCurrency,
		InvalidPath,
		PathError,
		NonUniquePath,
		IncorrectPoolAssetId,
		CorrespondenceError,
	}
	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
		fn integrity_test() {
			assert!(
				T::MaxSwapPathLength::get() > 1,
				"the `MaxSwapPathLength` should be greater than 1",
			);
		}
	}
	#[pallet::call]
	impl<T: Config> Pallet<T> {
		#[pallet::call_index(0)]
		#[pallet::weight(T::WeightInfo::create_pool())]
		pub fn create_pool(
			origin: OriginFor<T>,
			asset1: T::MultiAssetId,
			asset2: T::MultiAssetId,
		) -> DispatchResult {
			let sender = ensure_signed(origin)?;
			ensure!(asset1 != asset2, Error::<T>::EqualAssets);
			let pool_id = Self::get_pool_id(asset1, asset2);
			ensure!(!Pools::<T>::contains_key(&pool_id), Error::<T>::PoolExists);
			let (asset1, asset2) = &pool_id;
			if !T::AllowMultiAssetPools::get() && !T::MultiAssetIdConverter::is_native(asset1) {
				Err(Error::<T>::PoolMustContainNativeCurrency)?;
			}
			let pool_account = Self::get_pool_account(&pool_id);
			frame_system::Pallet::<T>::inc_providers(&pool_account);
			T::Currency::transfer(
				&sender,
				&T::PoolSetupFeeReceiver::get(),
				T::PoolSetupFee::get(),
				Preserve,
			)?;
			match T::MultiAssetIdConverter::try_convert(asset1) {
				MultiAssetIdConversionResult::Converted(asset) =>
					if !T::Assets::contains(&asset, &pool_account) {
						T::Assets::touch(asset, pool_account.clone(), sender.clone())?
					},
				MultiAssetIdConversionResult::Unsupported(_) => Err(Error::<T>::UnsupportedAsset)?,
				MultiAssetIdConversionResult::Native => (),
			}
			match T::MultiAssetIdConverter::try_convert(asset2) {
				MultiAssetIdConversionResult::Converted(asset) =>
					if !T::Assets::contains(&asset, &pool_account) {
						T::Assets::touch(asset, pool_account.clone(), sender.clone())?
					},
				MultiAssetIdConversionResult::Unsupported(_) => Err(Error::<T>::UnsupportedAsset)?,
				MultiAssetIdConversionResult::Native => (),
			}
			let lp_token = NextPoolAssetId::<T>::get()
				.or(T::PoolAssetId::initial_value())
				.ok_or(Error::<T>::IncorrectPoolAssetId)?;
			let next_lp_token_id = lp_token.increment().ok_or(Error::<T>::IncorrectPoolAssetId)?;
			NextPoolAssetId::<T>::set(Some(next_lp_token_id));
			T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?;
			T::PoolAssets::touch(lp_token.clone(), pool_account.clone(), sender.clone())?;
			let pool_info = PoolInfo { lp_token: lp_token.clone() };
			Pools::<T>::insert(pool_id.clone(), pool_info);
			Self::deposit_event(Event::PoolCreated {
				creator: sender,
				pool_id,
				pool_account,
				lp_token,
			});
			Ok(())
		}
		#[pallet::call_index(1)]
		#[pallet::weight(T::WeightInfo::add_liquidity())]
		pub fn add_liquidity(
			origin: OriginFor<T>,
			asset1: T::MultiAssetId,
			asset2: T::MultiAssetId,
			amount1_desired: T::AssetBalance,
			amount2_desired: T::AssetBalance,
			amount1_min: T::AssetBalance,
			amount2_min: T::AssetBalance,
			mint_to: T::AccountId,
		) -> DispatchResult {
			let sender = ensure_signed(origin)?;
			let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
			let (amount1_desired, amount2_desired, amount1_min, amount2_min) =
				if pool_id.0 == asset1 {
					(amount1_desired, amount2_desired, amount1_min, amount2_min)
				} else {
					(amount2_desired, amount1_desired, amount2_min, amount1_min)
				};
			ensure!(
				amount1_desired > Zero::zero() && amount2_desired > Zero::zero(),
				Error::<T>::WrongDesiredAmount
			);
			let maybe_pool = Pools::<T>::get(&pool_id);
			let pool = maybe_pool.as_ref().ok_or(Error::<T>::PoolNotFound)?;
			let pool_account = Self::get_pool_account(&pool_id);
			let (asset1, asset2) = &pool_id;
			let reserve1 = Self::get_balance(&pool_account, asset1)?;
			let reserve2 = Self::get_balance(&pool_account, asset2)?;
			let amount1: T::AssetBalance;
			let amount2: T::AssetBalance;
			if reserve1.is_zero() || reserve2.is_zero() {
				amount1 = amount1_desired;
				amount2 = amount2_desired;
			} else {
				let amount2_optimal = Self::quote(&amount1_desired, &reserve1, &reserve2)?;
				if amount2_optimal <= amount2_desired {
					ensure!(
						amount2_optimal >= amount2_min,
						Error::<T>::AssetTwoDepositDidNotMeetMinimum
					);
					amount1 = amount1_desired;
					amount2 = amount2_optimal;
				} else {
					let amount1_optimal = Self::quote(&amount2_desired, &reserve2, &reserve1)?;
					ensure!(
						amount1_optimal <= amount1_desired,
						Error::<T>::OptimalAmountLessThanDesired
					);
					ensure!(
						amount1_optimal >= amount1_min,
						Error::<T>::AssetOneDepositDidNotMeetMinimum
					);
					amount1 = amount1_optimal;
					amount2 = amount2_desired;
				}
			}
			Self::validate_minimal_amount(amount1.saturating_add(reserve1), asset1)
				.map_err(|_| Error::<T>::AmountOneLessThanMinimal)?;
			Self::validate_minimal_amount(amount2.saturating_add(reserve2), asset2)
				.map_err(|_| Error::<T>::AmountTwoLessThanMinimal)?;
			Self::transfer(asset1, &sender, &pool_account, amount1, true)?;
			Self::transfer(asset2, &sender, &pool_account, amount2, true)?;
			let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
			let lp_token_amount: T::AssetBalance;
			if total_supply.is_zero() {
				lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?;
				T::PoolAssets::mint_into(
					pool.lp_token.clone(),
					&pool_account,
					T::MintMinLiquidity::get(),
				)?;
			} else {
				let side1 = Self::mul_div(&amount1, &total_supply, &reserve1)?;
				let side2 = Self::mul_div(&amount2, &total_supply, &reserve2)?;
				lp_token_amount = side1.min(side2);
			}
			ensure!(
				lp_token_amount > T::MintMinLiquidity::get(),
				Error::<T>::InsufficientLiquidityMinted
			);
			T::PoolAssets::mint_into(pool.lp_token.clone(), &mint_to, lp_token_amount)?;
			Self::deposit_event(Event::LiquidityAdded {
				who: sender,
				mint_to,
				pool_id,
				amount1_provided: amount1,
				amount2_provided: amount2,
				lp_token: pool.lp_token.clone(),
				lp_token_minted: lp_token_amount,
			});
			Ok(())
		}
		#[pallet::call_index(2)]
		#[pallet::weight(T::WeightInfo::remove_liquidity())]
		pub fn remove_liquidity(
			origin: OriginFor<T>,
			asset1: T::MultiAssetId,
			asset2: T::MultiAssetId,
			lp_token_burn: T::AssetBalance,
			amount1_min_receive: T::AssetBalance,
			amount2_min_receive: T::AssetBalance,
			withdraw_to: T::AccountId,
		) -> DispatchResult {
			let sender = ensure_signed(origin)?;
			let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
			let (amount1_min_receive, amount2_min_receive) = if pool_id.0 == asset1 {
				(amount1_min_receive, amount2_min_receive)
			} else {
				(amount2_min_receive, amount1_min_receive)
			};
			let (asset1, asset2) = pool_id.clone();
			ensure!(lp_token_burn > Zero::zero(), Error::<T>::ZeroLiquidity);
			let maybe_pool = Pools::<T>::get(&pool_id);
			let pool = maybe_pool.as_ref().ok_or(Error::<T>::PoolNotFound)?;
			let pool_account = Self::get_pool_account(&pool_id);
			let reserve1 = Self::get_balance(&pool_account, &asset1)?;
			let reserve2 = Self::get_balance(&pool_account, &asset2)?;
			let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone());
			let withdrawal_fee_amount = T::LiquidityWithdrawalFee::get() * lp_token_burn;
			let lp_redeem_amount = lp_token_burn.saturating_sub(withdrawal_fee_amount);
			let amount1 = Self::mul_div(&lp_redeem_amount, &reserve1, &total_supply)?;
			let amount2 = Self::mul_div(&lp_redeem_amount, &reserve2, &total_supply)?;
			ensure!(
				!amount1.is_zero() && amount1 >= amount1_min_receive,
				Error::<T>::AssetOneWithdrawalDidNotMeetMinimum
			);
			ensure!(
				!amount2.is_zero() && amount2 >= amount2_min_receive,
				Error::<T>::AssetTwoWithdrawalDidNotMeetMinimum
			);
			let reserve1_left = reserve1.saturating_sub(amount1);
			let reserve2_left = reserve2.saturating_sub(amount2);
			Self::validate_minimal_amount(reserve1_left, &asset1)
				.map_err(|_| Error::<T>::ReserveLeftLessThanMinimal)?;
			Self::validate_minimal_amount(reserve2_left, &asset2)
				.map_err(|_| Error::<T>::ReserveLeftLessThanMinimal)?;
			T::PoolAssets::burn_from(pool.lp_token.clone(), &sender, lp_token_burn, Exact, Polite)?;
			Self::transfer(&asset1, &pool_account, &withdraw_to, amount1, false)?;
			Self::transfer(&asset2, &pool_account, &withdraw_to, amount2, false)?;
			Self::deposit_event(Event::LiquidityRemoved {
				who: sender,
				withdraw_to,
				pool_id,
				amount1,
				amount2,
				lp_token: pool.lp_token.clone(),
				lp_token_burned: lp_token_burn,
				withdrawal_fee: T::LiquidityWithdrawalFee::get(),
			});
			Ok(())
		}
		#[pallet::call_index(3)]
		#[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens())]
		pub fn swap_exact_tokens_for_tokens(
			origin: OriginFor<T>,
			path: BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
			amount_in: T::AssetBalance,
			amount_out_min: T::AssetBalance,
			send_to: T::AccountId,
			keep_alive: bool,
		) -> DispatchResult {
			let sender = ensure_signed(origin)?;
			Self::do_swap_exact_tokens_for_tokens(
				sender,
				path,
				amount_in,
				Some(amount_out_min),
				send_to,
				keep_alive,
			)?;
			Ok(())
		}
		#[pallet::call_index(4)]
		#[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens())]
		pub fn swap_tokens_for_exact_tokens(
			origin: OriginFor<T>,
			path: BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
			amount_out: T::AssetBalance,
			amount_in_max: T::AssetBalance,
			send_to: T::AccountId,
			keep_alive: bool,
		) -> DispatchResult {
			let sender = ensure_signed(origin)?;
			Self::do_swap_tokens_for_exact_tokens(
				sender,
				path,
				amount_out,
				Some(amount_in_max),
				send_to,
				keep_alive,
			)?;
			Ok(())
		}
	}
	impl<T: Config> Pallet<T> {
		pub fn do_swap_exact_tokens_for_tokens(
			sender: T::AccountId,
			path: BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
			amount_in: T::AssetBalance,
			amount_out_min: Option<T::AssetBalance>,
			send_to: T::AccountId,
			keep_alive: bool,
		) -> Result<T::AssetBalance, DispatchError> {
			ensure!(amount_in > Zero::zero(), Error::<T>::ZeroAmount);
			if let Some(amount_out_min) = amount_out_min {
				ensure!(amount_out_min > Zero::zero(), Error::<T>::ZeroAmount);
			}
			Self::validate_swap_path(&path)?;
			let amounts = Self::get_amounts_out(&amount_in, &path)?;
			let amount_out =
				*amounts.last().defensive_ok_or("get_amounts_out() returned an empty result")?;
			if let Some(amount_out_min) = amount_out_min {
				ensure!(
					amount_out >= amount_out_min,
					Error::<T>::ProvidedMinimumNotSufficientForSwap
				);
			}
			Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
			Ok(amount_out)
		}
		pub fn do_swap_tokens_for_exact_tokens(
			sender: T::AccountId,
			path: BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
			amount_out: T::AssetBalance,
			amount_in_max: Option<T::AssetBalance>,
			send_to: T::AccountId,
			keep_alive: bool,
		) -> Result<T::AssetBalance, DispatchError> {
			ensure!(amount_out > Zero::zero(), Error::<T>::ZeroAmount);
			if let Some(amount_in_max) = amount_in_max {
				ensure!(amount_in_max > Zero::zero(), Error::<T>::ZeroAmount);
			}
			Self::validate_swap_path(&path)?;
			let amounts = Self::get_amounts_in(&amount_out, &path)?;
			let amount_in =
				*amounts.first().defensive_ok_or("get_amounts_in() returned an empty result")?;
			if let Some(amount_in_max) = amount_in_max {
				ensure!(
					amount_in <= amount_in_max,
					Error::<T>::ProvidedMaximumNotSufficientForSwap
				);
			}
			Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
			Ok(amount_in)
		}
		fn transfer(
			asset_id: &T::MultiAssetId,
			from: &T::AccountId,
			to: &T::AccountId,
			amount: T::AssetBalance,
			keep_alive: bool,
		) -> Result<T::AssetBalance, DispatchError> {
			let result = match T::MultiAssetIdConverter::try_convert(asset_id) {
				MultiAssetIdConversionResult::Converted(asset_id) =>
					T::Assets::transfer(asset_id, from, to, amount, Expendable),
				MultiAssetIdConversionResult::Native => {
					let preservation = match keep_alive {
						true => Preserve,
						false => Expendable,
					};
					let amount = Self::convert_asset_balance_to_native_balance(amount)?;
					Ok(Self::convert_native_balance_to_asset_balance(T::Currency::transfer(
						from,
						to,
						amount,
						preservation,
					)?)?)
				},
				MultiAssetIdConversionResult::Unsupported(_) =>
					Err(Error::<T>::UnsupportedAsset.into()),
			};
			if result.is_ok() {
				Self::deposit_event(Event::Transfer {
					from: from.clone(),
					to: to.clone(),
					asset: (*asset_id).clone(),
					amount,
				});
			}
			result
		}
		pub(crate) fn convert_native_balance_to_asset_balance(
			amount: T::Balance,
		) -> Result<T::AssetBalance, Error<T>> {
			T::HigherPrecisionBalance::from(amount)
				.try_into()
				.map_err(|_| Error::<T>::Overflow)
		}
		pub(crate) fn convert_asset_balance_to_native_balance(
			amount: T::AssetBalance,
		) -> Result<T::Balance, Error<T>> {
			T::HigherPrecisionBalance::from(amount)
				.try_into()
				.map_err(|_| Error::<T>::Overflow)
		}
		pub(crate) fn convert_hpb_to_asset_balance(
			amount: T::HigherPrecisionBalance,
		) -> Result<T::AssetBalance, Error<T>> {
			amount.try_into().map_err(|_| Error::<T>::Overflow)
		}
		pub(crate) fn do_swap(
			sender: T::AccountId,
			amounts: &Vec<T::AssetBalance>,
			path: BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
			send_to: T::AccountId,
			keep_alive: bool,
		) -> Result<(), DispatchError> {
			ensure!(amounts.len() > 1, Error::<T>::CorrespondenceError);
			if let Some([asset1, asset2]) = &path.get(0..2) {
				let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
				let pool_account = Self::get_pool_account(&pool_id);
				let first_amount = amounts.first().ok_or(Error::<T>::CorrespondenceError)?;
				Self::transfer(asset1, &sender, &pool_account, *first_amount, keep_alive)?;
				let mut i = 0;
				let path_len = path.len() as u32;
				for assets_pair in path.windows(2) {
					if let [asset1, asset2] = assets_pair {
						let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
						let pool_account = Self::get_pool_account(&pool_id);
						let amount_out =
							amounts.get((i + 1) as usize).ok_or(Error::<T>::CorrespondenceError)?;
						let to = if i < path_len - 2 {
							let asset3 = path.get((i + 2) as usize).ok_or(Error::<T>::PathError)?;
							Self::get_pool_account(&Self::get_pool_id(
								asset2.clone(),
								asset3.clone(),
							))
						} else {
							send_to.clone()
						};
						let reserve = Self::get_balance(&pool_account, asset2)?;
						let reserve_left = reserve.saturating_sub(*amount_out);
						Self::validate_minimal_amount(reserve_left, asset2)
							.map_err(|_| Error::<T>::ReserveLeftLessThanMinimal)?;
						Self::transfer(asset2, &pool_account, &to, *amount_out, true)?;
					}
					i.saturating_inc();
				}
				Self::deposit_event(Event::SwapExecuted {
					who: sender,
					send_to,
					path,
					amount_in: *first_amount,
					amount_out: *amounts.last().expect("Always has more than 1 element"),
				});
			} else {
				return Err(Error::<T>::InvalidPath.into())
			}
			Ok(())
		}
		pub fn get_pool_account(pool_id: &PoolIdOf<T>) -> T::AccountId {
			let encoded_pool_id = sp_io::hashing::blake2_256(&Encode::encode(pool_id)[..]);
			Decode::decode(&mut TrailingZeroInput::new(encoded_pool_id.as_ref()))
				.expect("infinite length input; no invalid inputs for type; qed")
		}
		fn get_balance(
			owner: &T::AccountId,
			asset: &T::MultiAssetId,
		) -> Result<T::AssetBalance, Error<T>> {
			match T::MultiAssetIdConverter::try_convert(asset) {
				MultiAssetIdConversionResult::Converted(asset_id) => Ok(
					<<T as Config>::Assets>::reducible_balance(asset_id, owner, Expendable, Polite),
				),
				MultiAssetIdConversionResult::Native =>
					Self::convert_native_balance_to_asset_balance(
						<<T as Config>::Currency>::reducible_balance(owner, Expendable, Polite),
					),
				MultiAssetIdConversionResult::Unsupported(_) =>
					Err(Error::<T>::UnsupportedAsset.into()),
			}
		}
		pub fn get_pool_id(asset1: T::MultiAssetId, asset2: T::MultiAssetId) -> PoolIdOf<T> {
			match (
				T::MultiAssetIdConverter::is_native(&asset1),
				T::MultiAssetIdConverter::is_native(&asset2),
			) {
				(true, false) => return (asset1, asset2),
				(false, true) => return (asset2, asset1),
				_ => {
					if asset1 <= asset2 {
						(asset1, asset2)
					} else {
						(asset2, asset1)
					}
				},
			}
		}
		pub fn get_reserves(
			asset1: &T::MultiAssetId,
			asset2: &T::MultiAssetId,
		) -> Result<(T::AssetBalance, T::AssetBalance), Error<T>> {
			let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
			let pool_account = Self::get_pool_account(&pool_id);
			let balance1 = Self::get_balance(&pool_account, asset1)?;
			let balance2 = Self::get_balance(&pool_account, asset2)?;
			if balance1.is_zero() || balance2.is_zero() {
				Err(Error::<T>::PoolNotFound)?;
			}
			Ok((balance1, balance2))
		}
		pub(crate) fn get_amounts_in(
			amount_out: &T::AssetBalance,
			path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
		) -> Result<Vec<T::AssetBalance>, DispatchError> {
			let mut amounts: Vec<T::AssetBalance> = vec![*amount_out];
			for assets_pair in path.windows(2).rev() {
				if let [asset1, asset2] = assets_pair {
					let (reserve_in, reserve_out) = Self::get_reserves(asset1, asset2)?;
					let prev_amount = amounts.last().expect("Always has at least one element");
					let amount_in = Self::get_amount_in(prev_amount, &reserve_in, &reserve_out)?;
					amounts.push(amount_in);
				}
			}
			amounts.reverse();
			Ok(amounts)
		}
		pub(crate) fn get_amounts_out(
			amount_in: &T::AssetBalance,
			path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
		) -> Result<Vec<T::AssetBalance>, DispatchError> {
			let mut amounts: Vec<T::AssetBalance> = vec![*amount_in];
			for assets_pair in path.windows(2) {
				if let [asset1, asset2] = assets_pair {
					let (reserve_in, reserve_out) = Self::get_reserves(asset1, asset2)?;
					let prev_amount = amounts.last().expect("Always has at least one element");
					let amount_out = Self::get_amount_out(prev_amount, &reserve_in, &reserve_out)?;
					amounts.push(amount_out);
				}
			}
			Ok(amounts)
		}
		pub fn quote_price_exact_tokens_for_tokens(
			asset1: T::MultiAssetId,
			asset2: T::MultiAssetId,
			amount: T::AssetBalance,
			include_fee: bool,
		) -> Option<T::AssetBalance> {
			let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
			let pool_account = Self::get_pool_account(&pool_id);
			let balance1 = Self::get_balance(&pool_account, &asset1).ok()?;
			let balance2 = Self::get_balance(&pool_account, &asset2).ok()?;
			if !balance1.is_zero() {
				if include_fee {
					Self::get_amount_out(&amount, &balance1, &balance2).ok()
				} else {
					Self::quote(&amount, &balance1, &balance2).ok()
				}
			} else {
				None
			}
		}
		pub fn quote_price_tokens_for_exact_tokens(
			asset1: T::MultiAssetId,
			asset2: T::MultiAssetId,
			amount: T::AssetBalance,
			include_fee: bool,
		) -> Option<T::AssetBalance> {
			let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
			let pool_account = Self::get_pool_account(&pool_id);
			let balance1 = Self::get_balance(&pool_account, &asset1).ok()?;
			let balance2 = Self::get_balance(&pool_account, &asset2).ok()?;
			if !balance1.is_zero() {
				if include_fee {
					Self::get_amount_in(&amount, &balance1, &balance2).ok()
				} else {
					Self::quote(&amount, &balance2, &balance1).ok()
				}
			} else {
				None
			}
		}
		pub fn quote(
			amount: &T::AssetBalance,
			reserve1: &T::AssetBalance,
			reserve2: &T::AssetBalance,
		) -> Result<T::AssetBalance, Error<T>> {
			Self::mul_div(amount, reserve2, reserve1)
		}
		pub(super) fn calc_lp_amount_for_zero_supply(
			amount1: &T::AssetBalance,
			amount2: &T::AssetBalance,
		) -> Result<T::AssetBalance, Error<T>> {
			let amount1 = T::HigherPrecisionBalance::from(*amount1);
			let amount2 = T::HigherPrecisionBalance::from(*amount2);
			let result = amount1
				.checked_mul(&amount2)
				.ok_or(Error::<T>::Overflow)?
				.integer_sqrt()
				.checked_sub(&T::MintMinLiquidity::get().into())
				.ok_or(Error::<T>::InsufficientLiquidityMinted)?;
			result.try_into().map_err(|_| Error::<T>::Overflow)
		}
		fn mul_div(
			a: &T::AssetBalance,
			b: &T::AssetBalance,
			c: &T::AssetBalance,
		) -> Result<T::AssetBalance, Error<T>> {
			let a = T::HigherPrecisionBalance::from(*a);
			let b = T::HigherPrecisionBalance::from(*b);
			let c = T::HigherPrecisionBalance::from(*c);
			let result = a
				.checked_mul(&b)
				.ok_or(Error::<T>::Overflow)?
				.checked_div(&c)
				.ok_or(Error::<T>::Overflow)?;
			result.try_into().map_err(|_| Error::<T>::Overflow)
		}
		pub fn get_amount_out(
			amount_in: &T::AssetBalance,
			reserve_in: &T::AssetBalance,
			reserve_out: &T::AssetBalance,
		) -> Result<T::AssetBalance, Error<T>> {
			let amount_in = T::HigherPrecisionBalance::from(*amount_in);
			let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
			let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
			if reserve_in.is_zero() || reserve_out.is_zero() {
				return Err(Error::<T>::ZeroLiquidity.into())
			}
			let amount_in_with_fee = amount_in
				.checked_mul(&(T::HigherPrecisionBalance::from(1000u32) - (T::LPFee::get().into())))
				.ok_or(Error::<T>::Overflow)?;
			let numerator =
				amount_in_with_fee.checked_mul(&reserve_out).ok_or(Error::<T>::Overflow)?;
			let denominator = reserve_in
				.checked_mul(&1000u32.into())
				.ok_or(Error::<T>::Overflow)?
				.checked_add(&amount_in_with_fee)
				.ok_or(Error::<T>::Overflow)?;
			let result = numerator.checked_div(&denominator).ok_or(Error::<T>::Overflow)?;
			result.try_into().map_err(|_| Error::<T>::Overflow)
		}
		pub fn get_amount_in(
			amount_out: &T::AssetBalance,
			reserve_in: &T::AssetBalance,
			reserve_out: &T::AssetBalance,
		) -> Result<T::AssetBalance, Error<T>> {
			let amount_out = T::HigherPrecisionBalance::from(*amount_out);
			let reserve_in = T::HigherPrecisionBalance::from(*reserve_in);
			let reserve_out = T::HigherPrecisionBalance::from(*reserve_out);
			if reserve_in.is_zero() || reserve_out.is_zero() {
				Err(Error::<T>::ZeroLiquidity.into())?
			}
			if amount_out >= reserve_out {
				Err(Error::<T>::AmountOutTooHigh.into())?
			}
			let numerator = reserve_in
				.checked_mul(&amount_out)
				.ok_or(Error::<T>::Overflow)?
				.checked_mul(&1000u32.into())
				.ok_or(Error::<T>::Overflow)?;
			let denominator = reserve_out
				.checked_sub(&amount_out)
				.ok_or(Error::<T>::Overflow)?
				.checked_mul(&(T::HigherPrecisionBalance::from(1000u32) - T::LPFee::get().into()))
				.ok_or(Error::<T>::Overflow)?;
			let result = numerator
				.checked_div(&denominator)
				.ok_or(Error::<T>::Overflow)?
				.checked_add(&One::one())
				.ok_or(Error::<T>::Overflow)?;
			result.try_into().map_err(|_| Error::<T>::Overflow)
		}
		fn validate_minimal_amount(
			value: T::AssetBalance,
			asset: &T::MultiAssetId,
		) -> Result<(), ()> {
			if T::MultiAssetIdConverter::is_native(asset) {
				let ed = T::Currency::minimum_balance();
				ensure!(
					T::HigherPrecisionBalance::from(value) >= T::HigherPrecisionBalance::from(ed),
					()
				);
			} else {
				let MultiAssetIdConversionResult::Converted(asset_id) =
					T::MultiAssetIdConverter::try_convert(asset)
				else {
					return Err(())
				};
				let minimal = T::Assets::minimum_balance(asset_id);
				ensure!(value >= minimal, ());
			}
			Ok(())
		}
		fn validate_swap_path(
			path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
		) -> Result<(), DispatchError> {
			ensure!(path.len() >= 2, Error::<T>::InvalidPath);
			let mut pools = BoundedBTreeSet::<PoolIdOf<T>, T::MaxSwapPathLength>::new();
			for assets_pair in path.windows(2) {
				if let [asset1, asset2] = assets_pair {
					let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
					let new_element =
						pools.try_insert(pool_id).map_err(|_| Error::<T>::Overflow)?;
					if !new_element {
						return Err(Error::<T>::NonUniquePath.into())
					}
				}
			}
			Ok(())
		}
		#[cfg(any(test, feature = "runtime-benchmarks"))]
		pub fn get_next_pool_asset_id() -> T::PoolAssetId {
			NextPoolAssetId::<T>::get()
				.or(T::PoolAssetId::initial_value())
				.expect("Next pool asset ID can not be None")
		}
	}
}
impl<T: Config> Swap<T::AccountId, T::HigherPrecisionBalance, T::MultiAssetId> for Pallet<T> {
	fn swap_exact_tokens_for_tokens(
		sender: T::AccountId,
		path: Vec<T::MultiAssetId>,
		amount_in: T::HigherPrecisionBalance,
		amount_out_min: Option<T::HigherPrecisionBalance>,
		send_to: T::AccountId,
		keep_alive: bool,
	) -> Result<T::HigherPrecisionBalance, DispatchError> {
		let path = path.try_into().map_err(|_| Error::<T>::PathError)?;
		let amount_out_min = amount_out_min.map(Self::convert_hpb_to_asset_balance).transpose()?;
		let amount_out = Self::do_swap_exact_tokens_for_tokens(
			sender,
			path,
			Self::convert_hpb_to_asset_balance(amount_in)?,
			amount_out_min,
			send_to,
			keep_alive,
		)?;
		Ok(amount_out.into())
	}
	fn swap_tokens_for_exact_tokens(
		sender: T::AccountId,
		path: Vec<T::MultiAssetId>,
		amount_out: T::HigherPrecisionBalance,
		amount_in_max: Option<T::HigherPrecisionBalance>,
		send_to: T::AccountId,
		keep_alive: bool,
	) -> Result<T::HigherPrecisionBalance, DispatchError> {
		let path = path.try_into().map_err(|_| Error::<T>::PathError)?;
		let amount_in_max = amount_in_max.map(Self::convert_hpb_to_asset_balance).transpose()?;
		let amount_in = Self::do_swap_tokens_for_exact_tokens(
			sender,
			path,
			Self::convert_hpb_to_asset_balance(amount_out)?,
			amount_in_max,
			send_to,
			keep_alive,
		)?;
		Ok(amount_in.into())
	}
}
sp_api::decl_runtime_apis! {
	pub trait AssetConversionApi<Balance, AssetBalance, AssetId> where
		Balance: Codec + MaybeDisplay,
		AssetBalance: frame_support::traits::tokens::Balance,
		AssetId: Codec
	{
		fn quote_price_tokens_for_exact_tokens(asset1: AssetId, asset2: AssetId, amount: AssetBalance, include_fee: bool) -> Option<Balance>;
		fn quote_price_exact_tokens_for_tokens(asset1: AssetId, asset2: AssetId, amount: AssetBalance, include_fee: bool) -> Option<Balance>;
		fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>;
	}
}
sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);