From 55cc271c4d97293128b6d9bff4eeaf242d6b634d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Enrique=20Mu=C3=B1oz=20Mart=C3=ADn?= Date: Fri, 16 Feb 2024 14:18:26 +0100 Subject: [PATCH] Refactor: Extract swapping part from FI into `pallet-swaps` (#1724) * pallet structure * finished implementation * adapt pallets to trait changes * Use swapping in FI from pallet-swaps * adapt runtimes * adapt integration-tests * taplo fmt * fix clippy * fix IT * fix typo * change pallet index --- Cargo.lock | 20 ++ Cargo.toml | 4 +- libs/mocks/src/token_swaps.rs | 10 +- libs/traits/src/lib.rs | 104 +++++- libs/types/src/investments.rs | 43 +-- pallets/foreign-investments/Cargo.toml | 3 + pallets/foreign-investments/src/entities.rs | 29 +- pallets/foreign-investments/src/impls.rs | 95 +++-- pallets/foreign-investments/src/lib.rs | 62 +--- pallets/foreign-investments/src/mock.rs | 60 ++-- pallets/foreign-investments/src/swaps.rs | 222 ------------ pallets/foreign-investments/src/tests.rs | 290 +--------------- pallets/order-book/src/benchmarking.rs | 2 +- pallets/order-book/src/lib.rs | 10 +- pallets/order-book/src/mock.rs | 7 +- pallets/order-book/src/tests.rs | 2 +- pallets/swaps/Cargo.toml | 55 +++ pallets/swaps/src/lib.rs | 317 +++++++++++++++++ pallets/swaps/src/mock.rs | 88 +++++ pallets/swaps/src/tests.rs | 328 ++++++++++++++++++ runtime/altair/Cargo.toml | 4 + runtime/altair/src/lib.rs | 17 +- runtime/altair/src/liquidity_pools.rs | 5 +- runtime/centrifuge/Cargo.toml | 4 + runtime/centrifuge/src/lib.rs | 12 +- runtime/centrifuge/src/liquidity_pools.rs | 7 +- runtime/development/Cargo.toml | 4 + runtime/development/src/lib.rs | 12 +- runtime/development/src/liquidity_pools.rs | 7 +- runtime/integration-tests/Cargo.toml | 1 + .../src/generic/cases/liquidity_pools.rs | 68 +--- .../integration-tests/src/generic/config.rs | 6 +- 32 files changed, 1115 insertions(+), 783 deletions(-) delete mode 100644 pallets/foreign-investments/src/swaps.rs create mode 100644 pallets/swaps/Cargo.toml create mode 100644 pallets/swaps/src/lib.rs create mode 100644 pallets/swaps/src/mock.rs create mode 100644 pallets/swaps/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 5ed3e6fda6..2d30fb00d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,6 +198,7 @@ dependencies = [ "pallet-scheduler", "pallet-session", "pallet-sudo", + "pallet-swaps", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", @@ -1074,6 +1075,7 @@ dependencies = [ "pallet-scheduler", "pallet-session", "pallet-sudo", + "pallet-swaps", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", @@ -2527,6 +2529,7 @@ dependencies = [ "pallet-scheduler", "pallet-session", "pallet-sudo", + "pallet-swaps", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", @@ -7243,6 +7246,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "pallet-swaps", "parity-scale-codec 3.6.9", "scale-info", "sp-core", @@ -8139,6 +8143,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-swaps" +version = "1.0.0" +dependencies = [ + "cfg-mocks", + "cfg-traits", + "frame-support", + "frame-system", + "parity-scale-codec 3.6.9", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-timestamp" version = "4.0.0-dev" @@ -10977,6 +10996,7 @@ dependencies = [ "pallet-scheduler", "pallet-session", "pallet-sudo", + "pallet-swaps", "pallet-timestamp", "pallet-transaction-payment", "pallet-transfer-allowlist", diff --git a/Cargo.toml b/Cargo.toml index c38c61f26b..9d2ddf81ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,8 +36,9 @@ members = [ "pallets/pool-registry", "pallets/restricted-tokens", "pallets/restricted-xtokens", - "pallets/transfer-allowlist", "pallets/rewards", + "pallets/swaps", + "pallets/transfer-allowlist", "runtime/altair", "runtime/centrifuge", "runtime/development", @@ -286,6 +287,7 @@ pallet-pool-system = { path = "pallets/pool-system", default-features = false } pallet-restricted-tokens = { path = "pallets/restricted-tokens", default-features = false } pallet-restricted-xtokens = { path = "pallets/restricted-xtokens", default-features = false } pallet-rewards = { path = "pallets/rewards", default-features = false } +pallet-swaps = { path = "pallets/swaps", default-features = false } pallet-transfer-allowlist = { path = "pallets/transfer-allowlist", default-features = false } # Centrifuge libs diff --git a/libs/mocks/src/token_swaps.rs b/libs/mocks/src/token_swaps.rs index 4eb8deb6af..5a99a7775e 100644 --- a/libs/mocks/src/token_swaps.rs +++ b/libs/mocks/src/token_swaps.rs @@ -1,6 +1,6 @@ #[frame_support::pallet] pub mod pallet { - use cfg_traits::{OrderRatio, TokenSwaps}; + use cfg_traits::{OrderRatio, Swap, TokenSwaps}; use frame_support::pallet_prelude::*; use mock_builder::{execute_call, register_call}; @@ -11,7 +11,6 @@ pub mod pallet { type BalanceOut; type Ratio; type OrderId; - type OrderDetails; } #[pallet::pallet] @@ -59,7 +58,9 @@ pub mod pallet { register_call!(move |(a, b)| f(a, b)); } - pub fn mock_get_order_details(f: impl Fn(T::OrderId) -> Option + 'static) { + pub fn mock_get_order_details( + f: impl Fn(T::OrderId) -> Option> + 'static, + ) { register_call!(f); } @@ -79,7 +80,6 @@ pub mod pallet { type BalanceIn = T::BalanceOut; type BalanceOut = T::BalanceIn; type CurrencyId = T::CurrencyId; - type OrderDetails = T::OrderDetails; type OrderId = T::OrderId; type Ratio = T::Ratio; @@ -109,7 +109,7 @@ pub mod pallet { execute_call!((a, b)) } - fn get_order_details(a: Self::OrderId) -> Option { + fn get_order_details(a: Self::OrderId) -> Option> { execute_call!(a) } diff --git a/libs/traits/src/lib.rs b/libs/traits/src/lib.rs index cf5a24413b..0a58c85254 100644 --- a/libs/traits/src/lib.rs +++ b/libs/traits/src/lib.rs @@ -510,13 +510,41 @@ pub enum OrderRatio { Custom(Ratio), } +/// A simple representation of a currency swap. +#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub struct Swap { + /// The incoming currency, i.e. the desired one. + pub currency_in: Currency, + + /// The outgoing currency, i.e. the one which should be replaced. + pub currency_out: Currency, + + /// The amount of outcoming currency that will be swapped. + pub amount_out: Amount, +} + +impl Swap { + pub fn has_same_currencies(&self) -> bool { + self.currency_in == self.currency_out + } + + pub fn is_same_direction(&self, other: &Self) -> Result { + if self.currency_in == other.currency_in && self.currency_out == other.currency_out { + Ok(true) + } else if self.currency_in == other.currency_out && self.currency_out == other.currency_in { + Ok(false) + } else { + Err(DispatchError::Other("Swap contains different currencies")) + } + } +} + pub trait TokenSwaps { type CurrencyId; type BalanceOut; type BalanceIn; type Ratio; type OrderId; - type OrderDetails; /// Swap tokens selling `amount_out` of `currency_out` and buying /// `currency_in` given an order ratio. @@ -544,7 +572,7 @@ pub trait TokenSwaps { fn cancel_order(order: Self::OrderId) -> DispatchResult; /// Retrieve the details of the order if it exists. - fn get_order_details(order: Self::OrderId) -> Option; + fn get_order_details(order: Self::OrderId) -> Option>; /// Makes a conversion between 2 currencies using the market ratio between /// them @@ -555,6 +583,78 @@ pub trait TokenSwaps { ) -> Result; } +/// A representation of a currency swap in process. +#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub struct SwapState { + /// Swap not yet processed with the pending outcomming amount + pub remaining: Swap, + + /// Amount of incoming currency already swapped + pub swapped_in: AmountIn, + + /// Amount of incoming currency already swapped denominated in outgoing + /// currency + pub swapped_out: AmountOut, +} + +/// Used as result of `Pallet::apply_swap()` +/// Amounts are donominated referenced by the `new_swap` paramenter given to +/// `apply_swap()` +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct SwapStatus { + /// The incoming amount already swapped and available to use. + pub swapped: Amount, + + /// The outgoing amount pending to be swapped + pub pending: Amount, +} + +/// Trait to perform swaps without handling directly an order book +pub trait Swaps { + type Amount; + type CurrencyId; + type SwapId; + + /// Apply a swap over a current possible swap state. + /// - If there was no previous swap, it adds it. + /// - If there was a swap in the same direction, it increments it. + /// - If there was a swap in the opposite direction: + /// - If the amount is smaller, it decrements it. + /// - If the amount is the same, it removes the inverse swap. + /// - If the amount is greater, it removes the inverse swap and create + /// another with the excess + /// + /// The returned status contains the swapped amount after this call + /// (denominated in the incoming currency) and the pending amounts to be + /// swapped. + fn apply_swap( + who: &AccountId, + swap_id: Self::SwapId, + swap: Swap, + ) -> Result, DispatchError>; + + /// Returns the pending amount for a pending swap. The direction of the swap + /// is determined by the `from_currency` parameter. The amount returned is + /// denominated in the same currency as the given `from_currency`. + fn pending_amount( + who: &AccountId, + swap_id: Self::SwapId, + from_currency: Self::CurrencyId, + ) -> Result; + + /// Check that validates that if swapping pair is supported. + fn valid_pair(currency_in: Self::CurrencyId, currency_out: Self::CurrencyId) -> bool; + + /// Makes a conversion between 2 currencies using the market ratio between + /// them + // TODO: Should be removed after #1723 + fn convert_by_market( + currency_in: Self::CurrencyId, + currency_out: Self::CurrencyId, + amount_out: Self::Amount, + ) -> Result; +} + /// Trait to transmit a change of status for anything uniquely identifiable. /// /// NOTE: The main use case to handle asynchronous operations. diff --git a/libs/types/src/investments.rs b/libs/types/src/investments.rs index 64be9aae4a..addd573378 100644 --- a/libs/types/src/investments.rs +++ b/libs/types/src/investments.rs @@ -11,12 +11,12 @@ // GNU General Public License for more details. use cfg_primitives::OrderId; -use frame_support::{dispatch::fmt::Debug, RuntimeDebug}; +use frame_support::RuntimeDebug; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ traits::{EnsureAddAssign, Zero}, - DispatchError, DispatchResult, + DispatchResult, }; use sp_std::cmp::PartialEq; @@ -142,45 +142,6 @@ impl } } -/// A simple representation of a currency swap. -#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] -pub struct Swap { - /// The incoming currency, i.e. the desired one. - pub currency_in: Currency, - /// The outgoing currency, i.e. the one which should be replaced. - pub currency_out: Currency, - /// The amount of outcoming currency that will be swapped. - pub amount_out: Balance, -} - -impl Swap { - pub fn has_same_currencies(&self) -> bool { - self.currency_in == self.currency_out - } - - pub fn is_same_direction(&self, other: &Self) -> Result { - if self.currency_in == other.currency_in && self.currency_out == other.currency_out { - Ok(true) - } else if self.currency_in == other.currency_out && self.currency_out == other.currency_in { - Ok(false) - } else { - Err(DispatchError::Other("Swap contains different currencies")) - } - } -} - -/// A representation of a currency swap in process. -#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] -pub struct SwapState { - /// Swap not yet processed with the pending outcomming amount - pub remaining: Swap, - /// Amount of incoming currency already swapped - pub swapped_in: BalanceIn, - /// Amount of incoming currency already swapped denominated in outgoing - /// currency - pub swapped_out: BalanceOut, -} - /// A representation of an executed investment decrement. #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] pub struct ExecutedForeignDecreaseInvest { diff --git a/pallets/foreign-investments/Cargo.toml b/pallets/foreign-investments/Cargo.toml index b50dfe24cf..a8747a0c89 100644 --- a/pallets/foreign-investments/Cargo.toml +++ b/pallets/foreign-investments/Cargo.toml @@ -32,6 +32,7 @@ sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } cfg-mocks = { workspace = true, default-features = true } +pallet-swaps = { workspace = true, default-features = true } [features] default = ["std"] @@ -54,6 +55,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "cfg-mocks/runtime-benchmarks", + "pallet-swaps/runtime-benchmarks", ] try-runtime = [ "cfg-traits/try-runtime", @@ -62,4 +64,5 @@ try-runtime = [ "frame-system/try-runtime", "sp-runtime/try-runtime", "cfg-mocks/try-runtime", + "pallet-swaps/try-runtime", ] diff --git a/pallets/foreign-investments/src/entities.rs b/pallets/foreign-investments/src/entities.rs index 67b463525e..5b313623f8 100644 --- a/pallets/foreign-investments/src/entities.rs +++ b/pallets/foreign-investments/src/entities.rs @@ -1,8 +1,8 @@ //! Types with Config access. This module does not mutate FI storage -use cfg_traits::{investments::Investment, TokenSwaps}; +use cfg_traits::{investments::Investment, Swap, Swaps}; use cfg_types::investments::{ - CollectedAmount, ExecutedForeignCollect, ExecutedForeignDecreaseInvest, Swap, + CollectedAmount, ExecutedForeignCollect, ExecutedForeignDecreaseInvest, }; use frame_support::{dispatch::DispatchResult, ensure, RuntimeDebugNoBound}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; @@ -18,9 +18,7 @@ use sp_std::cmp::min; use crate::{ pallet::{Config, Error}, - pool_currency_of, - swaps::Swaps, - Action, SwapOf, + pool_currency_of, Action, SwapOf, }; /// Type used to be able to generate conversions from pool to foreign and @@ -185,7 +183,7 @@ impl InvestmentInfo { // It's ok to use the market ratio because this amount will be // cancelled in this instant. - let increasing_pool_amount = T::TokenSwaps::convert_by_market( + let increasing_pool_amount = T::Swaps::convert_by_market( pool_currency, self.foreign_currency, min(foreign_amount, increasing_foreign_amount).into(), @@ -378,13 +376,8 @@ impl InvestmentInfo { who: &T::AccountId, investment_id: T::InvestmentId, ) -> Result { - Ok(Swaps::::pending_amount_for( - who, - investment_id, - Action::Investment, - self.foreign_currency, - ) - .into()) + let swap_id = (investment_id, Action::Investment); + Ok(T::Swaps::pending_amount(who, swap_id, self.foreign_currency)?.into()) } /// In foreign currency denomination @@ -393,13 +386,9 @@ impl InvestmentInfo { who: &T::AccountId, investment_id: T::InvestmentId, ) -> Result { - Ok(Swaps::::pending_amount_for( - who, - investment_id, - Action::Investment, - pool_currency_of::(investment_id)?, - ) - .into()) + let swap_id = (investment_id, Action::Investment); + let pool_currency = pool_currency_of::(investment_id)?; + Ok(T::Swaps::pending_amount(who, swap_id, pool_currency)?.into()) } pub fn is_completed( diff --git a/pallets/foreign-investments/src/impls.rs b/pallets/foreign-investments/src/impls.rs index ca000d8cbb..125f77523e 100644 --- a/pallets/foreign-investments/src/impls.rs +++ b/pallets/foreign-investments/src/impls.rs @@ -2,7 +2,7 @@ use cfg_traits::{ investments::{ForeignInvestment, Investment, InvestmentCollector, TrancheCurrency}, - PoolInspect, StatusNotificationHook, TokenSwaps, + PoolInspect, StatusNotificationHook, SwapState, Swaps, }; use cfg_types::investments::CollectedAmount; use frame_support::pallet_prelude::*; @@ -12,9 +12,7 @@ use sp_std::marker::PhantomData; use crate::{ entities::{InvestmentInfo, RedemptionInfo}, pallet::{Config, Error, ForeignInvestmentInfo, ForeignRedemptionInfo, Pallet}, - pool_currency_of, - swaps::Swaps, - Action, SwapStateOf, + pool_currency_of, Action, SwapId, }; impl ForeignInvestment for Pallet { @@ -35,7 +33,8 @@ impl ForeignInvestment for Pallet { info.ensure_same_foreign(foreign_currency)?; let swap = info.pre_increase_swap(who, investment_id, foreign_amount)?; - let status = Swaps::::apply(who, investment_id, Action::Investment, swap.clone())?; + let swap_id = (investment_id, Action::Investment); + let status = T::Swaps::apply_swap(who, swap_id, swap.clone())?; let mut msg = None; if !status.swapped.is_zero() { @@ -81,7 +80,8 @@ impl ForeignInvestment for Pallet { info.ensure_same_foreign(foreign_currency)?; let swap = info.pre_decrease_swap(who, investment_id, foreign_amount)?; - let status = Swaps::::apply(who, investment_id, Action::Investment, swap.clone())?; + let swap_id = (investment_id, Action::Investment); + let status = T::Swaps::apply_swap(who, swap_id, swap.clone())?; let mut msg = None; if !status.swapped.is_zero() { @@ -208,7 +208,7 @@ impl ForeignInvestment for Pallet { true } else { T::PoolInspect::currency_for(investment_id.of_pool()) - .map(|pool_currency| T::TokenSwaps::valid_pair(pool_currency, currency)) + .map(|pool_currency| T::Swaps::valid_pair(pool_currency, currency)) .unwrap_or(false) } } @@ -218,55 +218,49 @@ impl ForeignInvestment for Pallet { true } else { T::PoolInspect::currency_for(investment_id.of_pool()) - .map(|pool_currency| T::TokenSwaps::valid_pair(currency, pool_currency)) + .map(|pool_currency| T::Swaps::valid_pair(currency, pool_currency)) .unwrap_or(false) } } } -pub struct FulfilledSwapOrderHook(PhantomData); -impl StatusNotificationHook for FulfilledSwapOrderHook { +pub struct FulfilledSwapHook(PhantomData); +impl StatusNotificationHook for FulfilledSwapHook { type Error = DispatchError; - type Id = T::SwapId; - type Status = SwapStateOf; - - fn notify_status_change(swap_id: T::SwapId, swap_state: SwapStateOf) -> DispatchResult { - match Swaps::::foreign_id_from(swap_id) { - Ok((who, investment_id, action)) => { - let pool_currency = pool_currency_of::(investment_id)?; - let swapped_amount_in = swap_state.swapped_in; - let swapped_amount_out = swap_state.swapped_out; - let pending_amount = swap_state.remaining.amount_out; - - if pending_amount.is_zero() { - Swaps::::update_id(&who, investment_id, action, None)?; - } + type Id = (T::AccountId, SwapId); + type Status = SwapState; - match action { - Action::Investment => match pool_currency == swap_state.remaining.currency_in { - true => SwapDone::::for_increase_investment( - &who, - investment_id, - swapped_amount_in.into(), - swapped_amount_out.into(), - ), - false => SwapDone::::for_decrease_investment( - &who, - investment_id, - swapped_amount_in.into(), - swapped_amount_out.into(), - pending_amount.into(), - ), - }, - Action::Redemption => SwapDone::::for_redemption( - &who, - investment_id, - swapped_amount_in.into(), - pending_amount.into(), - ), - } - } - Err(_) => Ok(()), // The event is not for foreign investments + fn notify_status_change( + (who, (investment_id, action)): Self::Id, + swap_state: Self::Status, + ) -> DispatchResult { + let pool_currency = pool_currency_of::(investment_id)?; + let swapped_amount_in = swap_state.swapped_in; + let swapped_amount_out = swap_state.swapped_out; + let pending_amount = swap_state.remaining.amount_out; + + match action { + Action::Investment => match pool_currency == swap_state.remaining.currency_in { + true => SwapDone::::for_increase_investment( + &who, + investment_id, + swapped_amount_in.into(), + swapped_amount_out.into(), + ), + false => SwapDone::::for_decrease_investment( + &who, + investment_id, + swapped_amount_in.into(), + swapped_amount_out.into(), + pending_amount.into(), + ), + }, + Action::Redemption => SwapDone::::for_redemption( + &who, + investment_id, + swapped_amount_in.into(), + pending_amount.into(), + ), } } } @@ -328,7 +322,8 @@ impl StatusNotificationHook for CollectedRedemptionHook { })?; if let Some(swap) = swap { - let status = Swaps::::apply(&who, investment_id, Action::Redemption, swap)?; + let swap_id = (investment_id, Action::Redemption); + let status = T::Swaps::apply_swap(&who, swap_id, swap)?; if !status.swapped.is_zero() { SwapDone::::for_redemption( diff --git a/pallets/foreign-investments/src/lib.rs b/pallets/foreign-investments/src/lib.rs index 3f21eea530..542b7b1256 100644 --- a/pallets/foreign-investments/src/lib.rs +++ b/pallets/foreign-investments/src/lib.rs @@ -25,9 +25,7 @@ //! notifications for collected investments via `CollectedInvestmentHook` and //! for collected redemptions via `CollectedRedemptionHook`]. //! - The implementer of the pallet's associated `TokenSwaps` type sends -//! notifications for fulfilled swap orders via the `FulfilledSwapOrderHook`. -//! - The implementer of the pallet's associated `TokenSwaps` type sends -//! notifications for fulfilled swap orders via the `FulfilledSwapOrderHook`. +//! notifications for fulfilled swap orders via the `FulfilledSwapHook`. //! - The implementer of the pallet's associated //! `DecreasedForeignInvestOrderHook` type handles the refund of the decreased //! amount to the investor. @@ -37,12 +35,11 @@ #![cfg_attr(not(feature = "std"), no_std)] -use cfg_types::investments::{Swap, SwapState}; -pub use impls::{CollectedInvestmentHook, CollectedRedemptionHook, FulfilledSwapOrderHook}; +use cfg_traits::Swap; +pub use impls::{CollectedInvestmentHook, CollectedRedemptionHook, FulfilledSwapHook}; pub use pallet::*; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -pub use swaps::Swaps; #[cfg(test)] mod mock; @@ -52,7 +49,6 @@ mod tests; mod entities; mod impls; -mod swaps; #[derive( Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, @@ -72,9 +68,8 @@ pub type ForeignId = ( /// Swap alias pub type SwapOf = Swap<::SwapBalance, ::CurrencyId>; -/// Swap state alias -pub type SwapStateOf = - SwapState<::SwapBalance, ::SwapBalance, ::CurrencyId>; +/// Identification of a swap from foreing-investment perspective +pub type SwapId = (::InvestmentId, Action); /// TrancheId Identification pub type TrancheIdOf = <::PoolInspect as cfg_traits::PoolInspect< @@ -101,11 +96,11 @@ pub fn pool_currency_of( pub mod pallet { use cfg_traits::{ investments::{Investment, InvestmentCollector, TrancheCurrency}, - PoolInspect, StatusNotificationHook, TokenSwaps, + PoolInspect, StatusNotificationHook, Swaps, }; use cfg_types::investments::{ExecutedForeignCollect, ExecutedForeignDecreaseInvest}; use frame_support::pallet_prelude::*; - use sp_runtime::{traits::AtLeast32BitUnsigned, FixedPointOperand}; + use sp_runtime::traits::AtLeast32BitUnsigned; use super::*; @@ -120,10 +115,8 @@ pub mod pallet { type ForeignBalance: Parameter + Member + AtLeast32BitUnsigned - + FixedPointOperand + Default + Copy - + MaybeSerializeDeserialize + MaxEncodedLen + Into + From @@ -133,10 +126,8 @@ pub mod pallet { type PoolBalance: Parameter + Member + AtLeast32BitUnsigned - + FixedPointOperand + Default + Copy - + MaybeSerializeDeserialize + MaxEncodedLen + Into + From @@ -146,24 +137,12 @@ pub mod pallet { type TrancheBalance: Parameter + Member + AtLeast32BitUnsigned - + FixedPointOperand + Default + Copy - + MaybeSerializeDeserialize + MaxEncodedLen; /// Any balances used in TokenSwaps - type SwapBalance: Parameter - + Member - + AtLeast32BitUnsigned - + FixedPointOperand - + Default - + Copy - + MaybeSerializeDeserialize - + MaxEncodedLen; - - /// The token swap order identifying type - type SwapId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + MaxEncodedLen; + type SwapBalance: Parameter + Member + AtLeast32BitUnsigned + Default + Copy + MaxEncodedLen; /// The currency type of transferrable tokens type CurrencyId: Parameter + Member + Copy + MaxEncodedLen; @@ -192,13 +171,11 @@ pub mod pallet { /// The type which exposes token swap order functionality such as /// placing and cancelling orders - type TokenSwaps: TokenSwaps< + type Swaps: Swaps< Self::AccountId, CurrencyId = Self::CurrencyId, - BalanceIn = Self::SwapBalance, - BalanceOut = Self::SwapBalance, - OrderId = Self::SwapId, - OrderDetails = SwapOf, + Amount = Self::SwapBalance, + SwapId = SwapId, >; /// The hook type which acts upon a finalized investment decrement. @@ -264,28 +241,11 @@ pub mod pallet { entities::RedemptionInfo, >; - /// Maps a `SwapId` to its corresponding `ForeignId` - /// - /// NOTE: The storage is killed when the swap order no longer exists - #[pallet::storage] - pub(super) type SwapIdToForeignId = - StorageMap<_, Blake2_128Concat, T::SwapId, ForeignId>; - - /// Maps a `ForeignId` to its corresponding `SwapId` - /// - /// NOTE: The storage is killed when the swap order no longer exists - #[pallet::storage] - pub(super) type ForeignIdToSwapId = - StorageMap<_, Blake2_128Concat, ForeignId, T::SwapId>; - #[pallet::error] pub enum Error { /// Failed to retrieve the `ForeignInvestInfo`. InfoNotFound, - /// Failed to retrieve the swap order. - SwapOrderNotFound, - /// Failed to retrieve the pool for the given pool id. PoolNotFound, diff --git a/pallets/foreign-investments/src/mock.rs b/pallets/foreign-investments/src/mock.rs index 8e3e44e01c..a765aacfbe 100644 --- a/pallets/foreign-investments/src/mock.rs +++ b/pallets/foreign-investments/src/mock.rs @@ -1,9 +1,5 @@ -use cfg_mocks::{ - pallet_mock_investment, pallet_mock_pools, pallet_mock_status_notification, - pallet_mock_token_swaps, -}; use cfg_traits::investments::TrancheCurrency; -use cfg_types::investments::{ExecutedForeignCollect, ExecutedForeignDecreaseInvest, Swap}; +use cfg_types::investments::{ExecutedForeignCollect, ExecutedForeignDecreaseInvest}; use frame_support::traits::{ConstU16, ConstU32, ConstU64}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -14,7 +10,7 @@ use sp_runtime::{ FixedU128, }; -use crate::pallet as pallet_foreign_investments; +use crate::{pallet as pallet_foreign_investments, FulfilledSwapHook, SwapId}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -23,11 +19,13 @@ pub type AccountId = u64; pub type Balance = u128; pub type TrancheId = u32; pub type PoolId = u64; -pub type SwapId = u64; +pub type OrderId = u64; pub type CurrencyId = u8; pub type Ratio = FixedU128; -#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[derive( + Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] pub struct InvestmentId(pub PoolId, pub TrancheId); impl TrancheCurrency for InvestmentId { @@ -51,12 +49,13 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system, - MockInvestment: pallet_mock_investment, - MockTokenSwaps: pallet_mock_token_swaps, - MockDecreaseInvestHook: pallet_mock_status_notification::, - MockCollectInvestHook: pallet_mock_status_notification::, - MockCollectRedeemHook: pallet_mock_status_notification::, - MockPools: pallet_mock_pools, + MockInvestment: cfg_mocks::investment::pallet, + MockTokenSwaps: cfg_mocks::token_swaps::pallet, + MockDecreaseInvestHook: cfg_mocks::status_notification::pallet::, + MockCollectInvestHook: cfg_mocks::status_notification::pallet::, + MockCollectRedeemHook: cfg_mocks::status_notification::pallet::, + MockPools: cfg_mocks::pools::pallet, + Swaps: pallet_swaps::pallet, ForeignInvestment: pallet_foreign_investments, } ); @@ -88,41 +87,40 @@ impl frame_system::Config for Runtime { type Version = (); } -impl pallet_mock_investment::Config for Runtime { +impl cfg_mocks::investment::pallet::Config for Runtime { type Amount = Balance; type CurrencyId = CurrencyId; type InvestmentId = InvestmentId; type TrancheAmount = Balance; } -impl pallet_mock_token_swaps::Config for Runtime { +impl cfg_mocks::token_swaps::pallet::Config for Runtime { type BalanceIn = Balance; type BalanceOut = Balance; type CurrencyId = CurrencyId; - type OrderDetails = Swap; - type OrderId = SwapId; + type OrderId = OrderId; type Ratio = FixedU128; } -type Hook1 = pallet_mock_status_notification::Instance1; -impl pallet_mock_status_notification::Config for Runtime { +type Hook1 = cfg_mocks::status_notification::pallet::Instance1; +impl cfg_mocks::status_notification::pallet::Config for Runtime { type Id = (AccountId, InvestmentId); type Status = ExecutedForeignDecreaseInvest; } -type Hook2 = pallet_mock_status_notification::Instance2; -impl pallet_mock_status_notification::Config for Runtime { +type Hook2 = cfg_mocks::status_notification::pallet::Instance2; +impl cfg_mocks::status_notification::pallet::Config for Runtime { type Id = (AccountId, InvestmentId); type Status = ExecutedForeignCollect; } -type Hook3 = pallet_mock_status_notification::Instance3; -impl pallet_mock_status_notification::Config for Runtime { +type Hook3 = cfg_mocks::status_notification::pallet::Instance3; +impl cfg_mocks::status_notification::pallet::Config for Runtime { type Id = (AccountId, InvestmentId); type Status = ExecutedForeignCollect; } -impl pallet_mock_pools::Config for Runtime { +impl cfg_mocks::pools::pallet::Config for Runtime { type Balance = Balance; type BalanceRatio = Ratio; type CurrencyId = CurrencyId; @@ -131,6 +129,15 @@ impl pallet_mock_pools::Config for Runtime { type TrancheId = TrancheId; } +impl pallet_swaps::Config for Runtime { + type Balance = Balance; + type CurrencyId = CurrencyId; + type FulfilledSwap = FulfilledSwapHook; + type OrderBook = MockTokenSwaps; + type OrderId = OrderId; + type SwapId = SwapId; +} + impl pallet_foreign_investments::Config for Runtime { type CollectedForeignInvestmentHook = MockCollectInvestHook; type CollectedForeignRedemptionHook = MockCollectRedeemHook; @@ -142,8 +149,7 @@ impl pallet_foreign_investments::Config for Runtime { type PoolBalance = Balance; type PoolInspect = MockPools; type SwapBalance = Balance; - type SwapId = SwapId; - type TokenSwaps = MockTokenSwaps; + type Swaps = Swaps; type TrancheBalance = Balance; } diff --git a/pallets/foreign-investments/src/swaps.rs b/pallets/foreign-investments/src/swaps.rs deleted file mode 100644 index 528fece6ac..0000000000 --- a/pallets/foreign-investments/src/swaps.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! Abstracts the swapping logic - -use cfg_traits::{OrderRatio, TokenSwaps}; -use frame_support::pallet_prelude::*; -use sp_runtime::traits::{EnsureAdd, EnsureSub, Zero}; -use sp_std::cmp::Ordering; - -use crate::{ - pallet::{Config, Error}, - Action, ForeignIdToSwapId, SwapIdToForeignId, SwapOf, -}; - -/// Internal type used as result of `Pallet::apply_swap()` -/// Amounts are donominated referenced by the `new_swap` paramenter given to -/// `apply_swap()` -#[derive(RuntimeDebugNoBound, PartialEq)] -pub struct SwapStatus { - /// The incoming amount already swapped and available to use. - pub swapped: T::SwapBalance, - - /// The outgoing amount pending to be swapped - pub pending: T::SwapBalance, - - /// The swap id for a possible reminder swap order after `apply_swap()` - pub swap_id: Option, -} - -/// Type that has methods related to swap actions -pub struct Swaps(PhantomData); -impl Swaps { - /// Inserts, updates or removes a swap id associated to a foreign - /// action. - pub fn update_id( - who: &T::AccountId, - investment_id: T::InvestmentId, - action: Action, - new_swap_id: Option, - ) -> DispatchResult { - let previous_swap_id = ForeignIdToSwapId::::get((who, investment_id, action)); - - if previous_swap_id != new_swap_id { - if let Some(old_id) = previous_swap_id { - SwapIdToForeignId::::remove(old_id); - // Must be removed before potentially re-adding an entry below - ForeignIdToSwapId::::remove((who.clone(), investment_id, action)); - } - - if let Some(new_id) = new_swap_id { - SwapIdToForeignId::::insert(new_id, (who.clone(), investment_id, action)); - ForeignIdToSwapId::::insert((who.clone(), investment_id, action), new_id); - } - } - - Ok(()) - } - - pub fn foreign_id_from( - swap_id: T::SwapId, - ) -> Result<(T::AccountId, T::InvestmentId, Action), DispatchError> { - SwapIdToForeignId::::get(swap_id).ok_or(Error::::SwapOrderNotFound.into()) - } - - pub fn swap_id_from( - account: &T::AccountId, - investment_id: T::InvestmentId, - action: Action, - ) -> Result { - ForeignIdToSwapId::::get((account, investment_id, action)) - .ok_or(Error::::SwapOrderNotFound.into()) - } - - /// Returns the pending swap amount for the direction that ends up in - /// `currency_in` - pub fn pending_amount_for( - who: &T::AccountId, - investment_id: T::InvestmentId, - action: Action, - currency_out: T::CurrencyId, - ) -> T::SwapBalance { - ForeignIdToSwapId::::get((who, investment_id, action)) - .and_then(T::TokenSwaps::get_order_details) - .filter(|swap| swap.currency_out == currency_out) - .map(|swap| swap.amount_out) - .unwrap_or_default() - } - - /// A wrap over `apply_over_swap()` that makes the swap from an - /// investment PoV - pub fn apply( - who: &T::AccountId, - investment_id: T::InvestmentId, - action: Action, - new_swap: SwapOf, - ) -> Result, DispatchError> { - // Bypassing the swap if both currencies are the same - if new_swap.currency_in == new_swap.currency_out { - return Ok(SwapStatus { - swapped: new_swap.amount_out, - pending: T::SwapBalance::zero(), - swap_id: None, - }); - } - - let swap_id = ForeignIdToSwapId::::get((who, investment_id, action)); - let status = Swaps::::apply_over_swap(who, new_swap.clone(), swap_id)?; - Swaps::::update_id(who, investment_id, action, status.swap_id)?; - - Ok(status) - } - - /// Apply a swap over a current possible swap state. - /// - If there was no previous swap, it adds it. - /// - If there was a swap in the same direction, it increments it. - /// - If there was a swap in the opposite direction: - /// - If the amount is smaller, it decrements it. - /// - If the amount is the same, it removes the inverse swap. - /// - If the amount is greater, it removes the inverse swap and create - /// another with the excess - /// - /// The returned status contains the swapped amounts after this call and - /// the pending amounts to be swapped of both swap directions. - pub fn apply_over_swap( - who: &T::AccountId, - new_swap: SwapOf, - over_swap_id: Option, - ) -> Result, DispatchError> { - match over_swap_id { - None => { - let swap_id = T::TokenSwaps::place_order( - who.clone(), - new_swap.currency_in, - new_swap.currency_out, - new_swap.amount_out, - OrderRatio::Market, - )?; - - Ok(SwapStatus { - swapped: T::SwapBalance::zero(), - pending: new_swap.amount_out, - swap_id: Some(swap_id), - }) - } - Some(swap_id) => { - let swap = T::TokenSwaps::get_order_details(swap_id) - .ok_or(Error::::SwapOrderNotFound)?; - - if swap.is_same_direction(&new_swap)? { - let amount_to_swap = swap.amount_out.ensure_add(new_swap.amount_out)?; - T::TokenSwaps::update_order(swap_id, amount_to_swap, OrderRatio::Market)?; - - Ok(SwapStatus { - swapped: T::SwapBalance::zero(), - pending: amount_to_swap, - swap_id: Some(swap_id), - }) - } else { - let inverse_swap = swap; - - let new_swap_amount_in = T::TokenSwaps::convert_by_market( - new_swap.currency_in, - new_swap.currency_out, - new_swap.amount_out, - )?; - - match inverse_swap.amount_out.cmp(&new_swap_amount_in) { - Ordering::Greater => { - let amount_to_swap = - inverse_swap.amount_out.ensure_sub(new_swap_amount_in)?; - - T::TokenSwaps::update_order( - swap_id, - amount_to_swap, - OrderRatio::Market, - )?; - - Ok(SwapStatus { - swapped: new_swap_amount_in, - pending: T::SwapBalance::zero(), - swap_id: Some(swap_id), - }) - } - Ordering::Equal => { - T::TokenSwaps::cancel_order(swap_id)?; - - Ok(SwapStatus { - swapped: new_swap_amount_in, - pending: T::SwapBalance::zero(), - swap_id: None, - }) - } - Ordering::Less => { - T::TokenSwaps::cancel_order(swap_id)?; - - let inverse_swap_amount_in = T::TokenSwaps::convert_by_market( - inverse_swap.currency_in, - inverse_swap.currency_out, - inverse_swap.amount_out, - )?; - - let amount_to_swap = - new_swap.amount_out.ensure_sub(inverse_swap_amount_in)?; - - let swap_id = T::TokenSwaps::place_order( - who.clone(), - new_swap.currency_in, - new_swap.currency_out, - amount_to_swap, - OrderRatio::Market, - )?; - - Ok(SwapStatus { - swapped: inverse_swap.amount_out, - pending: amount_to_swap, - swap_id: Some(swap_id), - }) - } - } - } - } - } - } -} diff --git a/pallets/foreign-investments/src/tests.rs b/pallets/foreign-investments/src/tests.rs index 75358bf5bb..240a50576f 100644 --- a/pallets/foreign-investments/src/tests.rs +++ b/pallets/foreign-investments/src/tests.rs @@ -1,19 +1,18 @@ use cfg_traits::{ investments::{ForeignInvestment as _, Investment, TrancheCurrency}, - OrderRatio, StatusNotificationHook, TokenSwaps, + StatusNotificationHook, Swap, SwapState, TokenSwaps, }; use cfg_types::investments::{ - CollectedAmount, ExecutedForeignCollect, ExecutedForeignDecreaseInvest, Swap, SwapState, + CollectedAmount, ExecutedForeignCollect, ExecutedForeignDecreaseInvest, }; use frame_support::{assert_err, assert_ok}; use sp_std::sync::{Arc, Mutex}; use crate::{ entities::{Correlation, InvestmentInfo, RedemptionInfo}, - impls::{CollectedInvestmentHook, CollectedRedemptionHook, FulfilledSwapOrderHook}, + impls::{CollectedInvestmentHook, CollectedRedemptionHook}, mock::*, pallet::ForeignInvestmentInfo, - swaps::{SwapStatus, Swaps}, *, }; @@ -21,7 +20,6 @@ const USER: AccountId = 1; const INVESTMENT_ID: InvestmentId = InvestmentId(42, 23); const FOREIGN_CURR: CurrencyId = 5; const POOL_CURR: CurrencyId = 10; -const SWAP_ID: SwapId = 1; const STABLE_RATIO: Balance = 10; // Means: 1 foreign curr is 10 pool curr const TRANCHE_RATIO: Balance = 5; // Means: 1 pool curr is 5 tranche curr const AMOUNT: Balance = pool_to_foreign(200); @@ -77,7 +75,7 @@ mod util { amount_out: amount_out, }) }); - Ok(SWAP_ID) + Ok(0) }); MockTokenSwaps::mock_update_order(|swap_id, amount_out, _| { @@ -127,8 +125,8 @@ mod util { /// Emulates a swap partial fulfill pub fn fulfill_last_swap(action: Action, amount_out: Balance) { - let swap_id = ForeignIdToSwapId::::get((USER, INVESTMENT_ID, action)).unwrap(); - let swap = MockTokenSwaps::get_order_details(swap_id).unwrap(); + let order_id = Swaps::order_id(&USER, (INVESTMENT_ID, action)).unwrap(); + let swap = MockTokenSwaps::get_order_details(order_id).unwrap(); MockTokenSwaps::mock_get_order_details(move |_| { Some(Swap { amount_out: swap.amount_out - amount_out, @@ -136,8 +134,8 @@ mod util { }) }); - FulfilledSwapOrderHook::::notify_status_change( - swap_id, + Swaps::notify_status_change( + order_id, SwapState { remaining: Swap { amount_out: swap.amount_out - amount_out, @@ -188,260 +186,6 @@ mod util { } } -mod swaps { - use super::*; - - fn assert_swap_id_registered(swap_id: SwapId) { - let foreign_id = (USER, INVESTMENT_ID, Action::Investment); - - assert_eq!(SwapIdToForeignId::::get(swap_id), Some(foreign_id)); - assert_eq!(ForeignIdToSwapId::::get(foreign_id), Some(swap_id)); - } - - #[test] - fn swap_over_no_swap() { - new_test_ext().execute_with(|| { - MockTokenSwaps::mock_place_order(|who, curr_in, curr_out, amount, ratio| { - assert_eq!(who, USER); - assert_eq!(curr_in, POOL_CURR); - assert_eq!(curr_out, FOREIGN_CURR); - assert_eq!(amount, AMOUNT); - assert_eq!(ratio, OrderRatio::Market); - - Ok(SWAP_ID) - }); - - assert_ok!( - Swaps::::apply( - &USER, - INVESTMENT_ID, - Action::Investment, - Swap { - currency_in: POOL_CURR, - currency_out: FOREIGN_CURR, - amount_out: AMOUNT, - }, - ), - SwapStatus { - swapped: 0, - pending: AMOUNT, - swap_id: Some(SWAP_ID), - } - ); - - assert_swap_id_registered(SWAP_ID); - }); - } - - #[test] - fn swap_over_same_direction_swap() { - const PREVIOUS_AMOUNT: Balance = AMOUNT + pool_to_foreign(50); - - new_test_ext().execute_with(|| { - MockTokenSwaps::mock_get_order_details(move |swap_id| { - assert_eq!(swap_id, SWAP_ID); - - Some(Swap { - currency_in: POOL_CURR, - currency_out: FOREIGN_CURR, - amount_out: PREVIOUS_AMOUNT, - }) - }); - MockTokenSwaps::mock_update_order(|swap_id, amount, ratio| { - assert_eq!(swap_id, SWAP_ID); - assert_eq!(amount, PREVIOUS_AMOUNT + AMOUNT); - assert_eq!(ratio, OrderRatio::Market); - - Ok(()) - }); - - Swaps::::update_id(&USER, INVESTMENT_ID, Action::Investment, Some(SWAP_ID)) - .unwrap(); - - assert_ok!( - Swaps::::apply( - &USER, - INVESTMENT_ID, - Action::Investment, - Swap { - currency_out: FOREIGN_CURR, - currency_in: POOL_CURR, - amount_out: AMOUNT, - }, - ), - SwapStatus { - swapped: 0, - pending: PREVIOUS_AMOUNT + AMOUNT, - swap_id: Some(SWAP_ID), - } - ); - - assert_swap_id_registered(SWAP_ID); - }); - } - - #[test] - fn swap_over_greater_inverse_swap() { - const PREVIOUS_AMOUNT: Balance = AMOUNT + pool_to_foreign(50); - - new_test_ext().execute_with(|| { - MockTokenSwaps::mock_convert_by_market(|to, from, amount_from| { - Ok(util::convert_currencies(to, from, amount_from)) - }); - MockTokenSwaps::mock_get_order_details(|swap_id| { - assert_eq!(swap_id, SWAP_ID); - - // Inverse swap - Some(Swap { - currency_in: FOREIGN_CURR, - currency_out: POOL_CURR, - amount_out: foreign_to_pool(PREVIOUS_AMOUNT), - }) - }); - MockTokenSwaps::mock_update_order(|swap_id, amount, ratio| { - assert_eq!(swap_id, SWAP_ID); - assert_eq!(amount, foreign_to_pool(PREVIOUS_AMOUNT - AMOUNT)); - assert_eq!(ratio, OrderRatio::Market); - - Ok(()) - }); - - Swaps::::update_id(&USER, INVESTMENT_ID, Action::Investment, Some(SWAP_ID)) - .unwrap(); - - assert_ok!( - Swaps::::apply( - &USER, - INVESTMENT_ID, - Action::Investment, - Swap { - currency_out: FOREIGN_CURR, - currency_in: POOL_CURR, - amount_out: AMOUNT, - }, - ), - SwapStatus { - swapped: foreign_to_pool(AMOUNT), - pending: 0, - swap_id: Some(SWAP_ID), - } - ); - - assert_swap_id_registered(SWAP_ID); - }); - } - - #[test] - fn swap_over_same_inverse_swap() { - new_test_ext().execute_with(|| { - MockTokenSwaps::mock_convert_by_market(|to, from, amount_from| { - Ok(util::convert_currencies(to, from, amount_from)) - }); - MockTokenSwaps::mock_get_order_details(|swap_id| { - assert_eq!(swap_id, SWAP_ID); - - // Inverse swap - Some(Swap { - currency_in: FOREIGN_CURR, - currency_out: POOL_CURR, - amount_out: foreign_to_pool(AMOUNT), - }) - }); - MockTokenSwaps::mock_cancel_order(|swap_id| { - assert_eq!(swap_id, SWAP_ID); - Ok(()) - }); - - Swaps::::update_id(&USER, INVESTMENT_ID, Action::Investment, Some(SWAP_ID)) - .unwrap(); - - assert_ok!( - Swaps::::apply( - &USER, - INVESTMENT_ID, - Action::Investment, - Swap { - currency_out: FOREIGN_CURR, - currency_in: POOL_CURR, - amount_out: AMOUNT, - }, - ), - SwapStatus { - swapped: foreign_to_pool(AMOUNT), - pending: 0, - swap_id: None, - } - ); - - assert_eq!(SwapIdToForeignId::::get(SWAP_ID), None); - assert_eq!( - ForeignIdToSwapId::::get((USER, INVESTMENT_ID, Action::Investment)), - None - ); - }); - } - - #[test] - fn swap_over_smaller_inverse_swap() { - const PREVIOUS_AMOUNT: Balance = AMOUNT - pool_to_foreign(50); - const NEW_SWAP_ID: SwapId = SWAP_ID + 1; - - new_test_ext().execute_with(|| { - MockTokenSwaps::mock_convert_by_market(|to, from, amount_from| { - Ok(util::convert_currencies(to, from, amount_from)) - }); - MockTokenSwaps::mock_get_order_details(|swap_id| { - assert_eq!(swap_id, SWAP_ID); - - // Inverse swap - Some(Swap { - currency_in: FOREIGN_CURR, - currency_out: POOL_CURR, - amount_out: foreign_to_pool(PREVIOUS_AMOUNT), - }) - }); - MockTokenSwaps::mock_cancel_order(|swap_id| { - assert_eq!(swap_id, SWAP_ID); - - Ok(()) - }); - MockTokenSwaps::mock_place_order(|who, curr_in, curr_out, amount, ratio| { - assert_eq!(who, USER); - assert_eq!(curr_in, POOL_CURR); - assert_eq!(curr_out, FOREIGN_CURR); - assert_eq!(amount, AMOUNT - PREVIOUS_AMOUNT); - assert_eq!(ratio, OrderRatio::Market); - - Ok(NEW_SWAP_ID) - }); - - Swaps::::update_id(&USER, INVESTMENT_ID, Action::Investment, Some(SWAP_ID)) - .unwrap(); - - assert_ok!( - Swaps::::apply( - &USER, - INVESTMENT_ID, - Action::Investment, - Swap { - currency_out: FOREIGN_CURR, - currency_in: POOL_CURR, - amount_out: AMOUNT, - }, - ), - SwapStatus { - swapped: foreign_to_pool(PREVIOUS_AMOUNT), - pending: AMOUNT - PREVIOUS_AMOUNT, - swap_id: Some(NEW_SWAP_ID), - } - ); - - assert_eq!(SwapIdToForeignId::::get(SWAP_ID), None); - assert_swap_id_registered(NEW_SWAP_ID); - }); - } -} - mod investment { use super::*; @@ -1644,24 +1388,6 @@ mod redemption { mod notifications { use super::*; - #[test] - fn fulfill_not_fail_if_not_found() { - new_test_ext().execute_with(|| { - assert_ok!(FulfilledSwapOrderHook::::notify_status_change( - SWAP_ID, - SwapState { - remaining: Swap { - amount_out: 0, - currency_in: 0, - currency_out: 1, - }, - swapped_in: 0, - swapped_out: 0, - } - )); - }); - } - #[test] fn collect_investment_not_fail_if_not_found() { new_test_ext().execute_with(|| { diff --git a/pallets/order-book/src/benchmarking.rs b/pallets/order-book/src/benchmarking.rs index ec9e63cfe8..be43b7a56c 100644 --- a/pallets/order-book/src/benchmarking.rs +++ b/pallets/order-book/src/benchmarking.rs @@ -10,7 +10,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_traits::{ConversionToAssetBalance, ValueProvider}; +use cfg_traits::{ConversionToAssetBalance, OrderRatio, TokenSwaps, ValueProvider}; use cfg_types::tokens::{AssetMetadata, CustomMetadata}; use frame_benchmarking::{account, v2::*}; use frame_support::traits::{fungibles::Mutate as _, Get}; diff --git a/pallets/order-book/src/lib.rs b/pallets/order-book/src/lib.rs index 456df9f64d..0577fd0049 100644 --- a/pallets/order-book/src/lib.rs +++ b/pallets/order-book/src/lib.rs @@ -30,18 +30,17 @@ mod benchmarking; pub mod weights; -pub use cfg_traits::{OrderRatio, TokenSwaps}; pub use pallet::*; pub use weights::WeightInfo; #[frame_support::pallet] pub mod pallet { use cfg_primitives::conversion::convert_balance_decimals; - use cfg_traits::{ConversionToAssetBalance, StatusNotificationHook, ValueProvider}; - use cfg_types::{ - investments::{Swap, SwapState}, - tokens::CustomMetadata, + use cfg_traits::{ + ConversionToAssetBalance, OrderRatio, StatusNotificationHook, Swap, SwapState, TokenSwaps, + ValueProvider, }; + use cfg_types::{self, tokens::CustomMetadata}; use frame_support::{ pallet_prelude::{DispatchResult, Member, StorageDoubleMap, StorageValue, *}, traits::{ @@ -779,7 +778,6 @@ pub mod pallet { type BalanceIn = T::BalanceIn; type BalanceOut = T::BalanceOut; type CurrencyId = T::CurrencyId; - type OrderDetails = Swap; type OrderId = T::OrderIdNonce; type Ratio = T::Ratio; diff --git a/pallets/order-book/src/mock.rs b/pallets/order-book/src/mock.rs index c7b7bb6e3f..13c6affadc 100644 --- a/pallets/order-book/src/mock.rs +++ b/pallets/order-book/src/mock.rs @@ -11,11 +11,8 @@ // GNU General Public License for more details. use cfg_mocks::pallet_mock_fees; -use cfg_traits::ConversionToAssetBalance; -use cfg_types::{ - investments::SwapState, - tokens::{CurrencyId, CustomMetadata}, -}; +use cfg_traits::{ConversionToAssetBalance, SwapState}; +use cfg_types::tokens::{CurrencyId, CustomMetadata}; use frame_support::{ parameter_types, traits::{ConstU32, GenesisBuild}, diff --git a/pallets/order-book/src/tests.rs b/pallets/order-book/src/tests.rs index ed0d81897b..13e5ef072d 100644 --- a/pallets/order-book/src/tests.rs +++ b/pallets/order-book/src/tests.rs @@ -10,7 +10,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_types::investments::{Swap, SwapState}; +use cfg_traits::{OrderRatio, Swap, SwapState, TokenSwaps}; use frame_support::{ assert_err, assert_noop, assert_ok, traits::fungibles::{Inspect, InspectHold}, diff --git a/pallets/swaps/Cargo.toml b/pallets/swaps/Cargo.toml new file mode 100644 index 0000000000..191a55d851 --- /dev/null +++ b/pallets/swaps/Cargo.toml @@ -0,0 +1,55 @@ +[package] +description = "Pallet to perform tokens swaps" +name = "pallet-swaps" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +documentation.workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +cfg-traits = { workspace = true } + +[dev-dependencies] +sp-io = { workspace = true, default-features = true } + +cfg-mocks = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "cfg-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "cfg-traits/runtime-benchmarks", + "cfg-mocks/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", + "cfg-traits/try-runtime", + "cfg-mocks/try-runtime", +] diff --git a/pallets/swaps/src/lib.rs b/pallets/swaps/src/lib.rs new file mode 100644 index 0000000000..397bb80cdd --- /dev/null +++ b/pallets/swaps/src/lib.rs @@ -0,0 +1,317 @@ +// Copyright 2024 Centrifuge Foundation (centrifuge.io). +// This file is part of Centrifuge Chain project. + +// Centrifuge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version (see http://www.gnu.org/licenses). + +// Centrifuge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +//! # Swaps pallet: Enables applying swaps independently of previous swaps in the same or opposite +//! directions. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use cfg_traits::{ + OrderRatio, StatusNotificationHook, Swap, SwapState, SwapStatus, Swaps, TokenSwaps, + }; + use frame_support::pallet_prelude::*; + use sp_runtime::traits::{AtLeast32BitUnsigned, EnsureAdd, EnsureSub, Zero}; + use sp_std::cmp::Ordering; + + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + /// Configure the pallet by specifying the parameters and types on which it + /// depends. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Represents an amount that can be swapped + type Balance: Parameter + Member + AtLeast32BitUnsigned + Default + Copy + MaxEncodedLen; + + /// An identification for a swap + type SwapId: Parameter + Member + Copy + Ord + MaxEncodedLen; + + /// An identification for an order + type OrderId: Parameter + Member + Copy + Ord + MaxEncodedLen; + + /// The currency type of transferrable tokens + type CurrencyId: Parameter + Member + Copy + MaxEncodedLen; + + /// The type which exposes token swap order functionality + type OrderBook: TokenSwaps< + Self::AccountId, + CurrencyId = Self::CurrencyId, + BalanceIn = Self::Balance, + BalanceOut = Self::Balance, + OrderId = Self::OrderId, + >; + + /// The hook which acts upon a (partially) fulfilled the swap + type FulfilledSwap: StatusNotificationHook< + Id = (Self::AccountId, Self::SwapId), + Status = SwapState, + Error = DispatchError, + >; + } + + /// Maps a `OrderId` to its corresponding `AccountId` and `SwapId` + /// + /// NOTE: The storage is killed when the swap order no longer exists + #[pallet::storage] + pub(super) type OrderIdToSwapId = + StorageMap<_, Blake2_128Concat, T::OrderId, (T::AccountId, T::SwapId)>; + + /// Maps an `AccountId` and `SwapId` to its corresponding `OrderId` + /// + /// NOTE: The storage is killed when the swap order no longer exists + #[pallet::storage] + pub(super) type SwapIdToOrderId = + StorageMap<_, Blake2_128Concat, (T::AccountId, T::SwapId), T::OrderId>; + + #[pallet::error] + pub enum Error { + /// Failed to retrieve the order. + OrderNotFound, + + /// Failed to retrieve the swap. + SwapNotFound, + } + + impl Pallet { + pub fn swap_id(order_id: T::OrderId) -> Result<(T::AccountId, T::SwapId), DispatchError> { + OrderIdToSwapId::::get(order_id).ok_or(Error::::SwapNotFound.into()) + } + + pub fn order_id( + account: &T::AccountId, + swap_id: T::SwapId, + ) -> Result { + SwapIdToOrderId::::get((account, swap_id)).ok_or(Error::::OrderNotFound.into()) + } + + pub(crate) fn update_id( + who: &T::AccountId, + swap_id: T::SwapId, + new_order_id: Option, + ) -> DispatchResult { + let previous_order_id = SwapIdToOrderId::::get((who, swap_id)); + + if previous_order_id != new_order_id { + if let Some(old_id) = previous_order_id { + OrderIdToSwapId::::remove(old_id); + SwapIdToOrderId::::remove((who.clone(), swap_id)); + } + + if let Some(new_id) = new_order_id { + OrderIdToSwapId::::insert(new_id, (who.clone(), swap_id)); + SwapIdToOrderId::::insert((who.clone(), swap_id), new_id); + } + } + + Ok(()) + } + + #[allow(clippy::type_complexity)] + fn apply_over_swap( + who: &T::AccountId, + new_swap: Swap, + over_swap_id: Option, + ) -> Result<(SwapStatus, Option), DispatchError> { + match over_swap_id { + None => { + let order_id = T::OrderBook::place_order( + who.clone(), + new_swap.currency_in, + new_swap.currency_out, + new_swap.amount_out, + OrderRatio::Market, + )?; + + Ok(( + SwapStatus { + swapped: T::Balance::zero(), + pending: new_swap.amount_out, + }, + Some(order_id), + )) + } + Some(order_id) => { + let swap = T::OrderBook::get_order_details(order_id) + .ok_or(Error::::OrderNotFound)?; + + if swap.is_same_direction(&new_swap)? { + let amount_to_swap = swap.amount_out.ensure_add(new_swap.amount_out)?; + T::OrderBook::update_order(order_id, amount_to_swap, OrderRatio::Market)?; + + Ok(( + SwapStatus { + swapped: T::Balance::zero(), + pending: amount_to_swap, + }, + Some(order_id), + )) + } else { + let inverse_swap = swap; + + let new_swap_amount_in = T::OrderBook::convert_by_market( + new_swap.currency_in, + new_swap.currency_out, + new_swap.amount_out, + )?; + + match inverse_swap.amount_out.cmp(&new_swap_amount_in) { + Ordering::Greater => { + let amount_to_swap = + inverse_swap.amount_out.ensure_sub(new_swap_amount_in)?; + + T::OrderBook::update_order( + order_id, + amount_to_swap, + OrderRatio::Market, + )?; + + Ok(( + SwapStatus { + swapped: new_swap_amount_in, + pending: T::Balance::zero(), + }, + Some(order_id), + )) + } + Ordering::Equal => { + T::OrderBook::cancel_order(order_id)?; + + Ok(( + SwapStatus { + swapped: new_swap_amount_in, + pending: T::Balance::zero(), + }, + None, + )) + } + Ordering::Less => { + T::OrderBook::cancel_order(order_id)?; + + let inverse_swap_amount_in = T::OrderBook::convert_by_market( + inverse_swap.currency_in, + inverse_swap.currency_out, + inverse_swap.amount_out, + )?; + + let amount_to_swap = + new_swap.amount_out.ensure_sub(inverse_swap_amount_in)?; + + let order_id = T::OrderBook::place_order( + who.clone(), + new_swap.currency_in, + new_swap.currency_out, + amount_to_swap, + OrderRatio::Market, + )?; + + Ok(( + SwapStatus { + swapped: inverse_swap.amount_out, + pending: amount_to_swap, + }, + Some(order_id), + )) + } + } + } + } + } + } + } + + /// Trait to perform swaps without handling directly an order book + impl Swaps for Pallet { + type Amount = T::Balance; + type CurrencyId = T::CurrencyId; + type SwapId = T::SwapId; + + fn apply_swap( + who: &T::AccountId, + swap_id: Self::SwapId, + swap: Swap, + ) -> Result, DispatchError> { + // Bypassing the swap if both currencies are the same + if swap.currency_in == swap.currency_out { + return Ok(SwapStatus { + swapped: swap.amount_out, + pending: T::Balance::zero(), + }); + } + + let previous_order_id = SwapIdToOrderId::::get((who, swap_id)); + + let (status, new_order_id) = Self::apply_over_swap(who, swap, previous_order_id)?; + + Self::update_id(who, swap_id, new_order_id)?; + + Ok(status) + } + + fn pending_amount( + who: &T::AccountId, + swap_id: Self::SwapId, + from_currency: Self::CurrencyId, + ) -> Result { + Ok(SwapIdToOrderId::::get((who, swap_id)) + .and_then(T::OrderBook::get_order_details) + .filter(|swap| swap.currency_out == from_currency) + .map(|swap| swap.amount_out) + .unwrap_or_default()) + } + + fn valid_pair(currency_in: Self::CurrencyId, currency_out: Self::CurrencyId) -> bool { + T::OrderBook::valid_pair(currency_in, currency_out) + } + + fn convert_by_market( + currency_in: Self::CurrencyId, + currency_out: Self::CurrencyId, + amount_out: Self::Amount, + ) -> Result { + T::OrderBook::convert_by_market(currency_in, currency_out, amount_out) + } + } + + impl StatusNotificationHook for Pallet { + type Error = DispatchError; + type Id = T::OrderId; + type Status = SwapState; + + fn notify_status_change( + order_id: T::OrderId, + swap_state: SwapState, + ) -> DispatchResult { + if let Ok((who, swap_id)) = Self::swap_id(order_id) { + if swap_state.remaining.amount_out.is_zero() { + Self::update_id(&who, swap_id, None)?; + } + + T::FulfilledSwap::notify_status_change((who, swap_id), swap_state)?; + } + + Ok(()) + } + } +} diff --git a/pallets/swaps/src/mock.rs b/pallets/swaps/src/mock.rs new file mode 100644 index 0000000000..a70e8df1d6 --- /dev/null +++ b/pallets/swaps/src/mock.rs @@ -0,0 +1,88 @@ +use cfg_traits::SwapState; +use frame_support::traits::{ConstU16, ConstU32, ConstU64}; +use sp_runtime::{ + testing::{Header, H256}, + traits::{BlakeTwo256, IdentityLookup}, + FixedU128, +}; + +use crate::pallet as pallet_swaps; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +pub type AccountId = u64; +pub type Balance = u128; +pub type OrderId = u64; +pub type SwapId = u32; +pub type CurrencyId = u8; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + MockTokenSwaps: cfg_mocks::token_swaps::pallet, + FulfilledSwapHook: cfg_mocks::status_notification::pallet, + Swaps: pallet_swaps, + } +); + +impl frame_system::Config for Runtime { + type AccountData = (); + type AccountId = AccountId; + type BaseCallFilter = frame_support::traits::Everything; + type BlockHashCount = ConstU64<250>; + type BlockLength = (); + type BlockNumber = u64; + type BlockWeights = (); + type DbWeight = (); + type Hash = H256; + type Hashing = BlakeTwo256; + type Header = Header; + type Index = u64; + type Lookup = IdentityLookup; + type MaxConsumers = ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = (); + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type SS58Prefix = ConstU16<42>; + type SystemWeightInfo = (); + type Version = (); +} + +impl cfg_mocks::token_swaps::pallet::Config for Runtime { + type BalanceIn = Balance; + type BalanceOut = Balance; + type CurrencyId = CurrencyId; + type OrderId = OrderId; + type Ratio = FixedU128; +} + +impl cfg_mocks::status_notification::pallet::Config for Runtime { + type Id = (AccountId, SwapId); + type Status = SwapState; +} + +impl pallet_swaps::Config for Runtime { + type Balance = Balance; + type CurrencyId = CurrencyId; + type FulfilledSwap = FulfilledSwapHook; + type OrderBook = MockTokenSwaps; + type OrderId = OrderId; + type SwapId = SwapId; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + sp_io::TestExternalities::new(storage) +} diff --git a/pallets/swaps/src/tests.rs b/pallets/swaps/src/tests.rs new file mode 100644 index 0000000000..ea45c99c8a --- /dev/null +++ b/pallets/swaps/src/tests.rs @@ -0,0 +1,328 @@ +use cfg_traits::{ + OrderRatio, StatusNotificationHook, Swap, SwapState, SwapStatus, Swaps as TSwaps, +}; +use frame_support::assert_ok; + +use crate::{mock::*, *}; + +const USER: AccountId = 1; +const CURRENCY_A: CurrencyId = 5; +const CURRENCY_B: CurrencyId = 10; +const ORDER_ID: OrderId = 1; +const SWAP_ID: SwapId = 1; +const RATIO: Balance = 10; // Means: 1 currency A is 10 currency B +const AMOUNT: Balance = b_to_a(200); + +/// amount of currency A to amount of currency B +pub const fn a_to_b(amount_a: Balance) -> Balance { + amount_a * RATIO +} + +/// amount of currency B to amount of currency A +pub const fn b_to_a(amount_b: Balance) -> Balance { + amount_b / RATIO +} + +mod util { + use super::*; + + pub fn convert_currencies(to: CurrencyId, from: CurrencyId, amount_from: Balance) -> Balance { + match (from, to) { + (CURRENCY_B, CURRENCY_A) => b_to_a(amount_from), + (CURRENCY_A, CURRENCY_B) => a_to_b(amount_from), + _ => amount_from, + } + } +} + +mod swaps { + use super::*; + + fn assert_swap_id_registered(order_id: OrderId) { + assert_eq!( + OrderIdToSwapId::::get(order_id), + Some((USER, SWAP_ID)) + ); + assert_eq!( + SwapIdToOrderId::::get((USER, SWAP_ID)), + Some(order_id) + ); + } + + #[test] + fn swap_over_no_swap() { + new_test_ext().execute_with(|| { + MockTokenSwaps::mock_place_order(|who, curr_in, curr_out, amount, ratio| { + assert_eq!(who, USER); + assert_eq!(curr_in, CURRENCY_B); + assert_eq!(curr_out, CURRENCY_A); + assert_eq!(amount, AMOUNT); + assert_eq!(ratio, OrderRatio::Market); + + Ok(ORDER_ID) + }); + + assert_ok!( + >::apply_swap( + &USER, + SWAP_ID, + Swap { + currency_in: CURRENCY_B, + currency_out: CURRENCY_A, + amount_out: AMOUNT, + }, + ), + SwapStatus { + swapped: 0, + pending: AMOUNT, + } + ); + + assert_swap_id_registered(ORDER_ID); + }); + } + + #[test] + fn swap_over_same_direction_swap() { + const PREVIOUS_AMOUNT: Balance = AMOUNT + b_to_a(50); + + new_test_ext().execute_with(|| { + MockTokenSwaps::mock_get_order_details(move |swap_id| { + assert_eq!(swap_id, ORDER_ID); + + Some(Swap { + currency_in: CURRENCY_B, + currency_out: CURRENCY_A, + amount_out: PREVIOUS_AMOUNT, + }) + }); + MockTokenSwaps::mock_update_order(|swap_id, amount, ratio| { + assert_eq!(swap_id, ORDER_ID); + assert_eq!(amount, PREVIOUS_AMOUNT + AMOUNT); + assert_eq!(ratio, OrderRatio::Market); + + Ok(()) + }); + + Swaps::update_id(&USER, SWAP_ID, Some(ORDER_ID)).unwrap(); + + assert_ok!( + >::apply_swap( + &USER, + SWAP_ID, + Swap { + currency_out: CURRENCY_A, + currency_in: CURRENCY_B, + amount_out: AMOUNT, + }, + ), + SwapStatus { + swapped: 0, + pending: PREVIOUS_AMOUNT + AMOUNT, + } + ); + + assert_swap_id_registered(ORDER_ID); + }); + } + + #[test] + fn swap_over_greater_inverse_swap() { + const PREVIOUS_AMOUNT: Balance = AMOUNT + b_to_a(50); + + new_test_ext().execute_with(|| { + MockTokenSwaps::mock_convert_by_market(|to, from, amount_from| { + Ok(util::convert_currencies(to, from, amount_from)) + }); + MockTokenSwaps::mock_get_order_details(|swap_id| { + assert_eq!(swap_id, ORDER_ID); + + // Inverse swap + Some(Swap { + currency_in: CURRENCY_A, + currency_out: CURRENCY_B, + amount_out: a_to_b(PREVIOUS_AMOUNT), + }) + }); + MockTokenSwaps::mock_update_order(|swap_id, amount, ratio| { + assert_eq!(swap_id, ORDER_ID); + assert_eq!(amount, a_to_b(PREVIOUS_AMOUNT - AMOUNT)); + assert_eq!(ratio, OrderRatio::Market); + + Ok(()) + }); + + Swaps::update_id(&USER, SWAP_ID, Some(ORDER_ID)).unwrap(); + + assert_ok!( + >::apply_swap( + &USER, + SWAP_ID, + Swap { + currency_out: CURRENCY_A, + currency_in: CURRENCY_B, + amount_out: AMOUNT, + }, + ), + SwapStatus { + swapped: a_to_b(AMOUNT), + pending: 0, + } + ); + + assert_swap_id_registered(ORDER_ID); + }); + } + + #[test] + fn swap_over_same_inverse_swap() { + new_test_ext().execute_with(|| { + MockTokenSwaps::mock_convert_by_market(|to, from, amount_from| { + Ok(util::convert_currencies(to, from, amount_from)) + }); + MockTokenSwaps::mock_get_order_details(|swap_id| { + assert_eq!(swap_id, ORDER_ID); + + // Inverse swap + Some(Swap { + currency_in: CURRENCY_A, + currency_out: CURRENCY_B, + amount_out: a_to_b(AMOUNT), + }) + }); + MockTokenSwaps::mock_cancel_order(|swap_id| { + assert_eq!(swap_id, ORDER_ID); + Ok(()) + }); + + Swaps::update_id(&USER, SWAP_ID, Some(ORDER_ID)).unwrap(); + + assert_ok!( + >::apply_swap( + &USER, + SWAP_ID, + Swap { + currency_out: CURRENCY_A, + currency_in: CURRENCY_B, + amount_out: AMOUNT, + }, + ), + SwapStatus { + swapped: a_to_b(AMOUNT), + pending: 0, + } + ); + + assert_eq!(OrderIdToSwapId::::get(ORDER_ID), None); + assert_eq!(SwapIdToOrderId::::get((USER, SWAP_ID)), None); + }); + } + + #[test] + fn swap_over_smaller_inverse_swap() { + const PREVIOUS_AMOUNT: Balance = AMOUNT - b_to_a(50); + const NEW_ORDER_ID: OrderId = ORDER_ID + 1; + + new_test_ext().execute_with(|| { + MockTokenSwaps::mock_convert_by_market(|to, from, amount_from| { + Ok(util::convert_currencies(to, from, amount_from)) + }); + MockTokenSwaps::mock_get_order_details(|swap_id| { + assert_eq!(swap_id, ORDER_ID); + + // Inverse swap + Some(Swap { + currency_in: CURRENCY_A, + currency_out: CURRENCY_B, + amount_out: a_to_b(PREVIOUS_AMOUNT), + }) + }); + MockTokenSwaps::mock_cancel_order(|swap_id| { + assert_eq!(swap_id, ORDER_ID); + + Ok(()) + }); + MockTokenSwaps::mock_place_order(|who, curr_in, curr_out, amount, ratio| { + assert_eq!(who, USER); + assert_eq!(curr_in, CURRENCY_B); + assert_eq!(curr_out, CURRENCY_A); + assert_eq!(amount, AMOUNT - PREVIOUS_AMOUNT); + assert_eq!(ratio, OrderRatio::Market); + + Ok(NEW_ORDER_ID) + }); + + Swaps::update_id(&USER, SWAP_ID, Some(ORDER_ID)).unwrap(); + + assert_ok!( + >::apply_swap( + &USER, + SWAP_ID, + Swap { + currency_out: CURRENCY_A, + currency_in: CURRENCY_B, + amount_out: AMOUNT, + }, + ), + SwapStatus { + swapped: a_to_b(PREVIOUS_AMOUNT), + pending: AMOUNT - PREVIOUS_AMOUNT, + } + ); + + assert_eq!(OrderIdToSwapId::::get(ORDER_ID), None); + assert_swap_id_registered(NEW_ORDER_ID); + }); + } +} + +mod fulfill { + use super::*; + + #[test] + fn correct_notification() { + new_test_ext().execute_with(|| { + Swaps::update_id(&USER, SWAP_ID, Some(ORDER_ID)).unwrap(); + + let swap_state = SwapState { + remaining: Swap { + amount_out: AMOUNT, + currency_in: CURRENCY_A, + currency_out: CURRENCY_B, + }, + swapped_in: AMOUNT * 2, + swapped_out: AMOUNT / 2, + }; + + FulfilledSwapHook::mock_notify_status_change({ + let swap_state = swap_state.clone(); + move |id, status| { + assert_eq!(id, (USER, SWAP_ID)); + assert_eq!(status, swap_state); + Ok(()) + } + }); + + assert_ok!(Swaps::notify_status_change(ORDER_ID, swap_state)); + }); + } + + #[test] + fn skip_notification() { + new_test_ext().execute_with(|| { + let swap_state = SwapState { + remaining: Swap { + amount_out: AMOUNT, + currency_in: CURRENCY_A, + currency_out: CURRENCY_B, + }, + swapped_in: AMOUNT * 2, + swapped_out: AMOUNT / 2, + }; + + // It does not send an event because it's not an order registered in + // pallet_swaps + assert_ok!(Swaps::notify_status_change(ORDER_ID, swap_state)); + }); + } +} diff --git a/runtime/altair/Cargo.toml b/runtime/altair/Cargo.toml index bdfb695dd4..91dfcd2e29 100644 --- a/runtime/altair/Cargo.toml +++ b/runtime/altair/Cargo.toml @@ -127,6 +127,7 @@ pallet-rewards = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-sudo = { workspace = true } +pallet-swaps = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transfer-allowlist = { workspace = true } @@ -254,6 +255,7 @@ std = [ "pallet-scheduler/std", "pallet-session/std", "pallet-sudo/std", + "pallet-swaps/std", "pallet-timestamp/std", "pallet-transaction-payment/std", "pallet-transfer-allowlist/std", @@ -339,6 +341,7 @@ runtime-benchmarks = [ "pallet-remarks/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", + "pallet-swaps/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-transfer-allowlist/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", @@ -426,6 +429,7 @@ try-runtime = [ "pallet-scheduler/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", + "pallet-swaps/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", "pallet-transfer-allowlist/try-runtime", diff --git a/runtime/altair/src/lib.rs b/runtime/altair/src/lib.rs index 8ad37a9fe2..28d4195a23 100644 --- a/runtime/altair/src/lib.rs +++ b/runtime/altair/src/lib.rs @@ -1697,7 +1697,7 @@ impl pallet_order_book::Config for Runtime { type DecimalConverter = runtime_common::foreign_investments::NativeBalanceDecimalConverter; type FeederId = Feeder; - type FulfilledOrderHook = pallet_foreign_investments::FulfilledSwapOrderHook; + type FulfilledOrderHook = Swaps; type MinFulfillmentAmountNative = MinFulfillmentAmountNative; type OrderIdNonce = u64; type OrderPairVecSize = OrderPairVecSize; @@ -1707,6 +1707,15 @@ impl pallet_order_book::Config for Runtime { type Weights = weights::pallet_order_book::WeightInfo; } +impl pallet_swaps::Config for Runtime { + type Balance = Balance; + type CurrencyId = CurrencyId; + type FulfilledSwap = pallet_foreign_investments::FulfilledSwapHook; + type OrderBook = OrderBook; + type OrderId = OrderId; + type SwapId = pallet_foreign_investments::SwapId; +} + parameter_types! { pub const MaxRemarksPerCall: u32 = 10; } @@ -1771,9 +1780,10 @@ construct_runtime!( Preimage: pallet_preimage::{Pallet, Call, Storage, Event} = 70, Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 72, - // our pallets + // our pallets (part 1) Fees: pallet_fees::{Pallet, Call, Storage, Config, Event} = 90, Anchor: pallet_anchors::{Pallet, Call, Storage} = 91, + // Removed: Claims = 92 CrowdloanClaim: pallet_crowdloan_claim::{Pallet, Call, Storage, Event} = 93, CrowdloanReward: pallet_crowdloan_reward::{Pallet, Call, Storage, Event} = 94, CollatorAllowlist: pallet_collator_allowlist::{Pallet, Call, Storage, Config, Event} = 95, @@ -1823,7 +1833,8 @@ construct_runtime!( EthereumTransaction: pallet_ethereum_transaction::{Pallet, Storage} = 164, LiquidityPoolsAxelarGateway: axelar_gateway_precompile::{Pallet, Call, Storage, Event} = 165, - // Removed: Migration = 199 + // Our pallets (part 2) + Swaps: pallet_swaps::{Pallet, Storage} = 200, } ); diff --git a/runtime/altair/src/liquidity_pools.rs b/runtime/altair/src/liquidity_pools.rs index 46deac4b38..b14cb24642 100644 --- a/runtime/altair/src/liquidity_pools.rs +++ b/runtime/altair/src/liquidity_pools.rs @@ -30,7 +30,7 @@ use runtime_common::{ use crate::{ ForeignInvestments, Investments, LiquidityPools, LiquidityPoolsGateway, LocationToAccountId, - OrderBook, OrmlAssetRegistry, Permissions, PoolSystem, Runtime, RuntimeEvent, RuntimeOrigin, + OrmlAssetRegistry, Permissions, PoolSystem, Runtime, RuntimeEvent, RuntimeOrigin, Swaps, Timestamp, Tokens, TransferAllowList, TreasuryAccount, }; @@ -45,8 +45,7 @@ impl pallet_foreign_investments::Config for Runtime { type PoolBalance = Balance; type PoolInspect = PoolSystem; type SwapBalance = Balance; - type SwapId = u64; - type TokenSwaps = OrderBook; + type Swaps = Swaps; type TrancheBalance = Balance; } diff --git a/runtime/centrifuge/Cargo.toml b/runtime/centrifuge/Cargo.toml index 7e6c35f39f..3279f58853 100644 --- a/runtime/centrifuge/Cargo.toml +++ b/runtime/centrifuge/Cargo.toml @@ -127,6 +127,7 @@ pallet-rewards = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-sudo = { workspace = true } +pallet-swaps = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transfer-allowlist = { workspace = true } @@ -254,6 +255,7 @@ std = [ "pallet-scheduler/std", "pallet-session/std", "pallet-sudo/std", + "pallet-swaps/std", "pallet-timestamp/std", "pallet-transaction-payment/std", "pallet-transfer-allowlist/std", @@ -338,6 +340,7 @@ runtime-benchmarks = [ "pallet-rewards/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", + "pallet-swaps/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-transfer-allowlist/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", @@ -425,6 +428,7 @@ try-runtime = [ "pallet-scheduler/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", + "pallet-swaps/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", "pallet-transfer-allowlist/try-runtime", diff --git a/runtime/centrifuge/src/lib.rs b/runtime/centrifuge/src/lib.rs index ee5095065b..17df1d46d7 100644 --- a/runtime/centrifuge/src/lib.rs +++ b/runtime/centrifuge/src/lib.rs @@ -1793,7 +1793,7 @@ impl pallet_order_book::Config for Runtime { type DecimalConverter = runtime_common::foreign_investments::NativeBalanceDecimalConverter; type FeederId = Feeder; - type FulfilledOrderHook = pallet_foreign_investments::FulfilledSwapOrderHook; + type FulfilledOrderHook = Swaps; type MinFulfillmentAmountNative = MinFulfillmentAmountNative; type OrderIdNonce = u64; type OrderPairVecSize = OrderPairVecSize; @@ -1803,6 +1803,15 @@ impl pallet_order_book::Config for Runtime { type Weights = weights::pallet_order_book::WeightInfo; } +impl pallet_swaps::Config for Runtime { + type Balance = Balance; + type CurrencyId = CurrencyId; + type FulfilledSwap = pallet_foreign_investments::FulfilledSwapHook; + type OrderBook = OrderBook; + type OrderId = OrderId; + type SwapId = pallet_foreign_investments::SwapId; +} + impl pallet_transfer_allowlist::Config for Runtime { type CurrencyId = FilterCurrency; type Deposit = AllowanceDeposit; @@ -1927,6 +1936,7 @@ construct_runtime!( Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 185, Keystore: pallet_keystore::{Pallet, Call, Storage, Event} = 186, Loans: pallet_loans::{Pallet, Call, Storage, Event} = 187, + Swaps: pallet_swaps::{Pallet, Storage} = 188, } ); diff --git a/runtime/centrifuge/src/liquidity_pools.rs b/runtime/centrifuge/src/liquidity_pools.rs index 26c081947a..03b5fbed38 100644 --- a/runtime/centrifuge/src/liquidity_pools.rs +++ b/runtime/centrifuge/src/liquidity_pools.rs @@ -30,8 +30,8 @@ use runtime_common::{ use crate::{ ForeignInvestments, Investments, LiquidityPools, LiquidityPoolsAxelarGateway, - LiquidityPoolsGateway, LocationToAccountId, OrderBook, OrmlAssetRegistry, Permissions, - PoolSystem, Runtime, RuntimeEvent, RuntimeOrigin, Timestamp, Tokens, TransferAllowList, + LiquidityPoolsGateway, LocationToAccountId, OrmlAssetRegistry, Permissions, PoolSystem, + Runtime, RuntimeEvent, RuntimeOrigin, Swaps, Timestamp, Tokens, TransferAllowList, TreasuryAccount, }; @@ -46,8 +46,7 @@ impl pallet_foreign_investments::Config for Runtime { type PoolBalance = Balance; type PoolInspect = PoolSystem; type SwapBalance = Balance; - type SwapId = u64; - type TokenSwaps = OrderBook; + type Swaps = Swaps; type TrancheBalance = Balance; } diff --git a/runtime/development/Cargo.toml b/runtime/development/Cargo.toml index 60ca03fce6..e95b4763ef 100644 --- a/runtime/development/Cargo.toml +++ b/runtime/development/Cargo.toml @@ -126,6 +126,7 @@ pallet-rewards = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-sudo = { workspace = true } +pallet-swaps = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transfer-allowlist = { workspace = true } @@ -252,6 +253,7 @@ std = [ "pallet-scheduler/std", "pallet-session/std", "pallet-sudo/std", + "pallet-swaps/std", "pallet-timestamp/std", "pallet-transaction-payment/std", "pallet-transfer-allowlist/std", @@ -337,6 +339,7 @@ runtime-benchmarks = [ "pallet-rewards/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", + "pallet-swaps/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-transfer-allowlist/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", @@ -424,6 +427,7 @@ try-runtime = [ "pallet-scheduler/try-runtime", "pallet-session/try-runtime", "pallet-sudo/try-runtime", + "pallet-swaps/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", "pallet-transfer-allowlist/try-runtime", diff --git a/runtime/development/src/lib.rs b/runtime/development/src/lib.rs index f85a63b95b..55d6a08e9e 100644 --- a/runtime/development/src/lib.rs +++ b/runtime/development/src/lib.rs @@ -1770,7 +1770,7 @@ impl pallet_order_book::Config for Runtime { type DecimalConverter = runtime_common::foreign_investments::NativeBalanceDecimalConverter; type FeederId = Feeder; - type FulfilledOrderHook = pallet_foreign_investments::FulfilledSwapOrderHook; + type FulfilledOrderHook = Swaps; type MinFulfillmentAmountNative = MinFulfillmentAmountNative; type OrderIdNonce = u64; type OrderPairVecSize = OrderPairVecSize; @@ -1780,6 +1780,15 @@ impl pallet_order_book::Config for Runtime { type Weights = weights::pallet_order_book::WeightInfo; } +impl pallet_swaps::Config for Runtime { + type Balance = Balance; + type CurrencyId = CurrencyId; + type FulfilledSwap = pallet_foreign_investments::FulfilledSwapHook; + type OrderBook = OrderBook; + type OrderId = OrderId; + type SwapId = pallet_foreign_investments::SwapId; +} + parameter_types! { pub const MaxRemarksPerCall: u32 = 10; } @@ -1895,6 +1904,7 @@ construct_runtime!( // our pallets part 2 PoolFees: pallet_pool_fees::{Pallet, Call, Storage, Event} = 250, Remarks: pallet_remarks::{Pallet, Call, Event} = 251, + Swaps: pallet_swaps::{Pallet, Storage} = 252, } ); diff --git a/runtime/development/src/liquidity_pools.rs b/runtime/development/src/liquidity_pools.rs index ee650f21f5..e2d73fb101 100644 --- a/runtime/development/src/liquidity_pools.rs +++ b/runtime/development/src/liquidity_pools.rs @@ -30,8 +30,8 @@ use runtime_common::{ use crate::{ ForeignInvestments, Investments, LiquidityPools, LiquidityPoolsAxelarGateway, - LiquidityPoolsGateway, LocationToAccountId, OrderBook, OrmlAssetRegistry, Permissions, - PoolSystem, Runtime, RuntimeEvent, RuntimeOrigin, Timestamp, Tokens, TransferAllowList, + LiquidityPoolsGateway, LocationToAccountId, OrmlAssetRegistry, Permissions, PoolSystem, + Runtime, RuntimeEvent, RuntimeOrigin, Swaps, Timestamp, Tokens, TransferAllowList, TreasuryAccount, }; @@ -46,8 +46,7 @@ impl pallet_foreign_investments::Config for Runtime { type PoolBalance = Balance; type PoolInspect = PoolSystem; type SwapBalance = Balance; - type SwapId = u64; - type TokenSwaps = OrderBook; + type Swaps = Swaps; type TrancheBalance = Balance; } diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index f373afdb7b..3650d2d256 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -139,6 +139,7 @@ pallet-rewards = { workspace = true, features = ["std"] } pallet-scheduler = { workspace = true, features = ["std"] } pallet-session = { workspace = true, features = ["std"] } pallet-sudo = { workspace = true, features = ["std"] } +pallet-swaps = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true, features = ["std"] } pallet-transaction-payment = { workspace = true, features = ["std"] } pallet-transfer-allowlist = { workspace = true, features = ["std"] } diff --git a/runtime/integration-tests/src/generic/cases/liquidity_pools.rs b/runtime/integration-tests/src/generic/cases/liquidity_pools.rs index 74582335aa..e41ccb2842 100644 --- a/runtime/integration-tests/src/generic/cases/liquidity_pools.rs +++ b/runtime/integration-tests/src/generic/cases/liquidity_pools.rs @@ -1,5 +1,6 @@ use cfg_primitives::{ - currency_decimals, parachains, AccountId, Balance, CouncilCollective, PoolId, TrancheId, + currency_decimals, parachains, AccountId, Balance, CouncilCollective, OrderId, PoolId, + TrancheId, }; use cfg_traits::{ investments::{ForeignInvestment, Investment, OrderManager, TrancheCurrency}, @@ -530,6 +531,15 @@ mod development { ) } + pub fn default_order_id(investor: &AccountId) -> OrderId { + let default_swap_id = ( + default_investment_id::(), + pallet_foreign_investments::Action::Investment, + ); + pallet_swaps::Pallet::::order_id(&investor, default_swap_id) + .expect("Swap order exists; qed") + } + /// Returns the default investment account derived from the /// `DEFAULT_POOL_ID` and its default tranche. pub fn default_investment_account() -> AccountId { @@ -2218,7 +2228,6 @@ mod development { // Create new pool create_currency_pool::(pool_id, currency_id, currency_decimals.into()); let investment_currency_id: CurrencyId = default_investment_id::().into(); - // Set permissions and execute initial investment do_initial_increase_investment::( pool_id, @@ -3829,15 +3838,9 @@ mod development { investor.clone(), foreign_currency, ); - let swap_order_id = pallet_foreign_investments::Swaps::::swap_id_from( - &investor, - default_investment_id::(), - pallet_foreign_investments::Action::Investment, - ) - .expect("Swap order exists; qed"); fulfill_swap_into_pool::( pool_id, - swap_order_id, + default_order_id::(&investor), invest_amount_pool_denominated, invest_amount_foreign_denominated, trader, @@ -3950,15 +3953,9 @@ mod development { investor.clone(), foreign_currency, ); - let swap_order_id = pallet_foreign_investments::Swaps::::swap_id_from( - &investor, - default_investment_id::(), - pallet_foreign_investments::Action::Investment, - ) - .expect("Swap order exists; qed"); fulfill_swap_into_pool::( pool_id, - swap_order_id, + default_order_id::(&investor), invest_amount_pool_denominated, invest_amount_foreign_denominated, trader.clone(), @@ -4027,15 +4024,9 @@ mod development { )); // Swap decreased amount - let swap_order_id = pallet_foreign_investments::Swaps::::swap_id_from( - &investor, - default_investment_id::(), - pallet_foreign_investments::Action::Investment, - ) - .expect("Swap order exists; qed"); assert_ok!(pallet_order_book::Pallet::::fill_order( RawOrigin::Signed(trader.clone()).into(), - swap_order_id, + default_order_id::(&investor), invest_amount_pool_denominated )); assert!(frame_system::Pallet::::events().iter().any(|e| { @@ -4105,12 +4096,7 @@ mod development { ); // Fulfilling order should propagate it from swapping to investing - let swap_order_id = pallet_foreign_investments::Swaps::::swap_id_from( - &investor, - default_investment_id::(), - pallet_foreign_investments::Action::Investment, - ) - .expect("Swap order exists; qed"); + let swap_order_id = default_order_id::(&investor); fulfill_swap_into_pool::( pool_id, swap_order_id, @@ -4146,13 +4132,7 @@ mod development { msg.clone() )); - // Fulfill the decrease swap order - let swap_order_id = pallet_foreign_investments::Swaps::::swap_id_from( - &investor, - default_investment_id::(), - pallet_foreign_investments::Action::Investment, - ) - .expect("Swap order exists; qed"); + let swap_order_id = default_order_id::(&investor); assert_ok!(pallet_order_book::Pallet::::fill_order( RawOrigin::Signed(trader.clone()).into(), swap_order_id, @@ -4236,15 +4216,9 @@ mod development { investor.clone(), foreign_currency, ); - let swap_order_id = pallet_foreign_investments::Swaps::::swap_id_from( - &investor, - default_investment_id::(), - pallet_foreign_investments::Action::Investment, - ) - .expect("Swap order exists; qed"); fulfill_swap_into_pool::( pool_id, - swap_order_id, + default_order_id::(&investor), invest_amount_pool_denominated, invest_amount_foreign_denominated, trader.clone(), @@ -4264,15 +4238,9 @@ mod development { )); // Fulfill decrease swap partially - let swap_order_id = pallet_foreign_investments::Swaps::::swap_id_from( - &investor, - default_investment_id::(), - pallet_foreign_investments::Action::Investment, - ) - .expect("Swap order exists; qed"); assert_ok!(pallet_order_book::Pallet::::fill_order( RawOrigin::Signed(trader.clone()).into(), - swap_order_id, + default_order_id::(&investor), 3 * invest_amount_pool_denominated / 4 )); diff --git a/runtime/integration-tests/src/generic/config.rs b/runtime/integration-tests/src/generic/config.rs index 5e9f08333d..a63ffec5ce 100644 --- a/runtime/integration-tests/src/generic/config.rs +++ b/runtime/integration-tests/src/generic/config.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use cfg_primitives::{ AccountId, Address, AuraId, Balance, BlockNumber, CollectionId, CouncilCollective, Header, - Index, ItemId, LoanId, PoolId, Signature, TrancheId, + Index, ItemId, LoanId, OrderId, PoolId, Signature, TrancheId, }; use cfg_traits::Millis; use cfg_types::{ @@ -136,13 +136,13 @@ pub trait Runtime: OrderIdNonce = u64, Ratio = Ratio, FeederId = Feeder, - > + pallet_foreign_investments::Config< + > + pallet_swaps::Config> + + pallet_foreign_investments::Config< ForeignBalance = Balance, PoolBalance = Balance, TrancheBalance = Balance, InvestmentId = TrancheCurrency, CurrencyId = CurrencyId, - SwapId = u64, > + pallet_preimage::Config + pallet_collective::Config + pallet_democracy::Config>