diff --git a/Cargo.lock b/Cargo.lock index d9baf8680..f69edad15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4938,7 +4938,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "256.0.0" +version = "257.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", @@ -5028,6 +5028,7 @@ dependencies = [ "pallet-state-trie-migration", "pallet-timestamp", "pallet-tips", + "pallet-trade-event", "pallet-transaction-multi-payment", "pallet-transaction-pause", "pallet-transaction-payment", @@ -8444,7 +8445,7 @@ dependencies = [ [[package]] name = "pallet-lbp" -version = "4.8.4" +version = "4.9.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -8453,6 +8454,7 @@ dependencies = [ "hydradx-traits", "orml-tokens", "orml-traits", + "pallet-trade-event", "parity-scale-codec", "primitive-types", "proptest", @@ -8692,7 +8694,7 @@ dependencies = [ [[package]] name = "pallet-omnipool" -version = "4.3.2" +version = "4.4.0" dependencies = [ "bitflags 1.3.2", "frame-benchmarking", @@ -8705,6 +8707,7 @@ dependencies = [ "orml-tokens", "orml-traits", "pallet-balances", + "pallet-trade-event", "parity-scale-codec", "pretty_assertions", "primitive-types", @@ -8746,7 +8749,7 @@ dependencies = [ [[package]] name = "pallet-otc" -version = "2.0.1" +version = "2.1.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -8754,6 +8757,7 @@ dependencies = [ "hydradx-traits", "orml-tokens", "orml-traits", + "pallet-trade-event", "parity-scale-codec", "pretty_assertions", "proptest", @@ -8936,7 +8940,7 @@ dependencies = [ [[package]] name = "pallet-route-executor" -version = "2.6.0" +version = "2.7.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -8948,8 +8952,10 @@ dependencies = [ "orml-traits", "pallet-balances", "pallet-currencies", + "pallet-trade-event", "parity-scale-codec", "pretty_assertions", + "primitives", "scale-info", "serde", "sp-core", @@ -9036,7 +9042,7 @@ dependencies = [ [[package]] name = "pallet-stableswap" -version = "3.6.4" +version = "3.7.0" dependencies = [ "bitflags 1.3.2", "frame-benchmarking", @@ -9046,6 +9052,7 @@ dependencies = [ "hydradx-traits", "orml-tokens", "orml-traits", + "pallet-trade-event", "parity-scale-codec", "proptest", "scale-info", @@ -9208,6 +9215,20 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.2)", ] +[[package]] +name = "pallet-trade-event" +version = "1.0.0" +dependencies = [ + "frame-support", + "frame-system", + "hydradx-traits", + "parity-scale-codec", + "primitives", + "scale-info", + "sp-api", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.2)", +] + [[package]] name = "pallet-transaction-multi-payment" version = "10.0.4" @@ -9424,7 +9445,7 @@ dependencies = [ [[package]] name = "pallet-xyk" -version = "6.4.4" +version = "6.5.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -9435,6 +9456,7 @@ dependencies = [ "orml-tokens", "orml-traits", "pallet-asset-registry", + "pallet-trade-event", "parity-scale-codec", "primitive-types", "proptest", diff --git a/Cargo.toml b/Cargo.toml index f2af9f50e..5c064f8fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ members = [ 'pallets/evm-accounts', 'pallets/dynamic-evm-fee', 'pallets/xyk-liquidity-mining', + 'pallets/trade-event', 'precompiles/call-permit', 'runtime-mock' ] @@ -104,6 +105,7 @@ pallet-xyk-liquidity-mining = { path = "pallets/xyk-liquidity-mining", default-f pallet-referrals = { path = "pallets/referrals", default-features = false} pallet-evm-accounts = { path = "pallets/evm-accounts", default-features = false} pallet-evm-accounts-rpc-runtime-api = { path = "pallets/evm-accounts/rpc/runtime-api", default-features = false} +pallet-trade-event = { path = "pallets/trade-event", default-features = false} hydra-dx-build-script-utils = { path = "utils/build-script-utils", default-features = false } scraper = { path = "scraper", default-features = false } diff --git a/pallets/lbp/Cargo.toml b/pallets/lbp/Cargo.toml index 14263533f..3db919325 100644 --- a/pallets/lbp/Cargo.toml +++ b/pallets/lbp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-lbp" -version = "4.8.4" +version = "4.9.0" description = "HydraDX Liquidity Bootstrapping Pool Pallet" authors = ["GalacticCouncil"] edition = "2021" @@ -20,9 +20,10 @@ scale-info = { version = "2.3.1", default-features = false, features = ["derive" primitive-types = { default-features = false, version = "0.12.0" } serde = { features = ["derive"], optional = true, version = "1.0.136" } -## Local dependencies +# HydraDX dependencies hydra-dx-math = { workspace = true } hydradx-traits = { workspace = true } +pallet-trade-event = { workspace = true } ## ORML dependencies orml-traits = { workspace = true } @@ -66,5 +67,6 @@ std = [ 'frame-benchmarking/std', "scale-info/std", "hydra-dx-math/std", + "pallet-trade-event/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/lbp/src/lib.rs b/pallets/lbp/src/lib.rs index 96e159c53..7bc23b621 100644 --- a/pallets/lbp/src/lib.rs +++ b/pallets/lbp/src/lib.rs @@ -37,6 +37,7 @@ use frame_system::ensure_signed; use frame_system::pallet_prelude::BlockNumberFor; use hydra_dx_math::types::LBPWeight; use hydradx_traits::{AMMTransfer, AssetPairAccountIdFor, CanCreatePool, LockedBalance, AMM}; +use pallet_trade_event::IncrementalIdType; use orml_traits::{MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency}; use scale_info::TypeInfo; @@ -183,7 +184,7 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_trade_event::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Multi currency for transfer of currencies @@ -341,6 +342,7 @@ pub mod pallet { }, /// Sale executed. + /// Deprecated. Replaced by pallet_trade_event::Swapped SellExecuted { who: T::AccountId, asset_in: AssetId, @@ -352,6 +354,7 @@ pub mod pallet { }, /// Purchase executed. + /// Deprecated. Replaced by pallet_trade_event::Swapped BuyExecuted { who: T::AccountId, asset_out: AssetId, @@ -717,7 +720,8 @@ pub mod pallet { /// - `amount`: The amount of `asset_in` /// - `max_limit`: minimum amount of `asset_out` / amount of asset_out to be obtained from the pool in exchange for `asset_in`. /// - /// Emits `SellExecuted` when successful. + /// Emits `SellExecuted` when successful. Deprecated. + /// Emits `pallet_trade_event::Swapped` when successful. #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::sell())] pub fn sell( @@ -729,7 +733,14 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - >::sell(&who, AssetPair { asset_in, asset_out }, amount, max_limit, false)?; + >::sell( + &who, + AssetPair { asset_in, asset_out }, + amount, + max_limit, + false, + None, + )?; Ok(()) } @@ -747,7 +758,8 @@ pub mod pallet { /// - `amount`: The amount of `asset_out`. /// - `max_limit`: maximum amount of `asset_in` to be sold in exchange for `asset_out`. /// - /// Emits `BuyExecuted` when successful. + /// Emits `BuyExecuted` when successful. Deprecated. + /// Emits `pallet_trade_event::Swapped` when successful. #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::buy())] pub fn buy( @@ -759,7 +771,7 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - >::buy(&who, AssetPair { asset_in, asset_out }, amount, max_limit, false)?; + >::buy(&who, AssetPair { asset_in, asset_out }, amount, max_limit, false)?; Ok(()) } @@ -930,7 +942,9 @@ impl Pallet { } } -impl AMM> for Pallet { +impl AMM, IncrementalIdType> + for Pallet +{ fn exists(assets: AssetPair) -> bool { let pair_account = Self::pair_account_from_assets(assets.asset_in, assets.asset_out); >::contains_key(&pair_account) @@ -1098,9 +1112,13 @@ impl AMM> for Pallet) -> DispatchResult { + fn execute_sell( + transfer: &AMMTransfer, + batch_id: Option, + ) -> DispatchResult { Self::execute_trade(transfer)?; + // TODO: Deprecated, remove when ready Self::deposit_event(Event::::SellExecuted { who: transfer.origin.clone(), asset_in: transfer.assets.asset_in, @@ -1111,6 +1129,18 @@ impl AMM> for Pallet::deposit_trade_event( + transfer.origin.clone(), + pallet_trade_event::PoolType::LBP, + pallet_trade_event::TradeOperation::Sell, + transfer.assets.asset_in, + transfer.assets.asset_out, + transfer.amount, + transfer.amount_b, + vec![transfer.fee], + batch_id, + ); + Ok(()) } @@ -1235,6 +1265,7 @@ impl AMM> for Pallet>) -> DispatchResult { Self::execute_trade(transfer)?; + // TODO: Deprecated, remove when ready Self::deposit_event(Event::::BuyExecuted { who: transfer.origin.clone(), asset_out: transfer.assets.asset_out, @@ -1244,6 +1275,19 @@ impl AMM> for Pallet::deposit_trade_event( + transfer.origin.clone(), + pallet_trade_event::PoolType::LBP, + pallet_trade_event::TradeOperation::Buy, + transfer.assets.asset_in, + transfer.assets.asset_out, + transfer.amount, + transfer.amount_b, + vec![transfer.fee], + None, + ); + Ok(()) } diff --git a/pallets/lbp/src/mock.rs b/pallets/lbp/src/mock.rs index c685d6dc8..6eb4f8cd6 100644 --- a/pallets/lbp/src/mock.rs +++ b/pallets/lbp/src/mock.rs @@ -75,6 +75,7 @@ frame_support::construct_runtime!( System: frame_system, LBPPallet: lbp, Currency: orml_tokens, + TradeEvent: pallet_trade_event, } ); @@ -170,6 +171,10 @@ impl LockedBalance for MultiLockedBalance { } } +impl pallet_trade_event::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type MultiCurrency = Currency; diff --git a/pallets/lbp/src/trade_execution.rs b/pallets/lbp/src/trade_execution.rs index 0846640cb..50ae89e48 100644 --- a/pallets/lbp/src/trade_execution.rs +++ b/pallets/lbp/src/trade_execution.rs @@ -2,10 +2,12 @@ use crate::*; use hydradx_traits::router::{ExecutorError, PoolType, TradeExecution}; use hydradx_traits::AMM; use orml_traits::MultiCurrency; +use pallet_trade_event::IncrementalIdType; use sp_runtime::traits::BlockNumberProvider; use sp_runtime::DispatchError::Corruption; use sp_runtime::{ArithmeticError, DispatchError, FixedPointNumber, FixedU128}; -impl TradeExecution for Pallet { + +impl TradeExecution for Pallet { type Error = DispatchError; fn calculate_sell( @@ -110,12 +112,26 @@ impl TradeExecution asset_out: AssetId, amount_in: Balance, min_limit: Balance, + batch_id: Option, ) -> Result<(), ExecutorError> { if pool_type != PoolType::LBP { return Err(ExecutorError::NotSupported); } - Self::sell(who, asset_in, asset_out, amount_in, min_limit).map_err(ExecutorError::Error) + let who = crate::ensure_signed(who).map_err(|e| ExecutorError::Error(e.into()))?; + + >::sell( + &who, + AssetPair { asset_in, asset_out }, + amount_in, + min_limit, + false, + batch_id, + ) + .map_err(ExecutorError::Error)?; + + Ok(()) + // Self::sell(who, asset_in, asset_out, amount_in, min_limit).map_err(ExecutorError::Error) } fn execute_buy( diff --git a/pallets/omnipool/Cargo.toml b/pallets/omnipool/Cargo.toml index ec37db74f..d960a91b2 100644 --- a/pallets/omnipool/Cargo.toml +++ b/pallets/omnipool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-omnipool" -version = "4.3.2" +version = "4.4.0" authors = ['GalacticCouncil'] edition = "2021" license = "Apache-2.0" @@ -17,6 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] scale-info = { version = "2.3.1", default-features = false, features = ["derive"] } codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.4.0" } +# HydraDX dependencies +hydradx-traits = { workspace = true } +hydra-dx-math = { workspace = true } +pallet-trade-event = { workspace = true } + # primitives sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -28,11 +33,6 @@ frame-system = { workspace = true } # ORML orml-traits = { workspace = true } -# Warehouse -hydradx-traits = { workspace = true } - -hydra-dx-math = { workspace = true } - # third party primitive-types = { version = "0.12.0", default-features = false } bitflags = "1.3.2" @@ -69,6 +69,10 @@ std = [ "pallet-balances/std", "orml-tokens/std", "frame-benchmarking/std", + "pallet-trade-event/std", + 'hydradx-traits/std', + 'hydra-dx-math/std', + 'orml-traits/std', ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/pallets/omnipool/src/lib.rs b/pallets/omnipool/src/lib.rs index 245e458dc..fb68f1fe0 100644 --- a/pallets/omnipool/src/lib.rs +++ b/pallets/omnipool/src/lib.rs @@ -114,6 +114,7 @@ pub mod weights; use crate::traits::{AssetInfo, OmnipoolHooks}; use crate::types::{AssetReserveState, AssetState, Balance, Position, SimpleImbalance, Tradability}; pub use pallet::*; +use pallet_trade_event::IncrementalIdType; pub use weights::WeightInfo; /// NFT class id type of provided nft implementation @@ -138,7 +139,7 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_trade_event::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -151,7 +152,8 @@ pub mod pallet { + HasCompact + MaybeSerializeDeserialize + MaxEncodedLen - + TypeInfo; + + TypeInfo + + Into; /// Multi currency mechanism type Currency: MultiCurrency; @@ -301,6 +303,7 @@ pub mod pallet { shares_removed: Balance, }, /// Sell trade executed. + /// Deprecated. Replaced by pallet_trade_event::Swapped SellExecuted { who: T::AccountId, asset_in: T::AssetId, @@ -313,6 +316,7 @@ pub mod pallet { protocol_fee_amount: Balance, }, /// Buy trade executed. + /// Deprecated. Replaced by pallet_trade_event::Swapped BuyExecuted { who: T::AccountId, asset_in: T::AssetId, @@ -359,6 +363,12 @@ pub mod pallet { /// Asset's weight cap has been updated. AssetWeightCapUpdated { asset_id: T::AssetId, cap: Permill }, + + /// Amount of the Hub asset has been updated. + HubAmountUpdated { + hub_amount_in: Balance, + hub_amount_out: Balance, + }, } #[pallet::error] @@ -1016,7 +1026,8 @@ pub mod pallet { /// - `amount`: Amount of asset sold /// - `min_buy_amount`: Minimum amount required to receive /// - /// Emits `SellExecuted` event when successful. + /// Emits `SellExecuted` event when successful. Deprecated. + /// Emits `pallet_trade_event::Swapped` event when successful. /// #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::sell() @@ -1031,185 +1042,7 @@ pub mod pallet { amount: Balance, min_buy_amount: Balance, ) -> DispatchResult { - let who = ensure_signed(origin.clone())?; - - ensure!(asset_in != asset_out, Error::::SameAssetTradeNotAllowed); - - ensure!( - amount >= T::MinimumTradingLimit::get(), - Error::::InsufficientTradingAmount - ); - - ensure!( - T::Currency::ensure_can_withdraw(asset_in, &who, amount).is_ok(), - Error::::InsufficientBalance - ); - - // Special handling when one of the asset is Hub Asset - // Math is simplified and asset_in is actually part of asset_out state in this case - if asset_in == T::HubAssetId::get() { - return Self::sell_hub_asset(origin, &who, asset_out, amount, min_buy_amount); - } - - if asset_out == T::HubAssetId::get() { - return Self::sell_asset_for_hub_asset(&who, asset_in, amount, min_buy_amount); - } - - let asset_in_state = Self::load_asset_state(asset_in)?; - let asset_out_state = Self::load_asset_state(asset_out)?; - - ensure!( - Self::allow_assets(&asset_in_state, &asset_out_state), - Error::::NotAllowed - ); - - ensure!( - amount - <= asset_in_state - .reserve - .checked_div(T::MaxInRatio::get()) - .ok_or(ArithmeticError::DivisionByZero)?, // Note: this can only fail if MaxInRatio is zero. - Error::::MaxInRatioExceeded - ); - - let current_imbalance = >::get(); - - let (asset_fee, _) = T::Fee::get(&asset_out); - let (_, protocol_fee) = T::Fee::get(&asset_in); - - let state_changes = hydra_dx_math::omnipool::calculate_sell_state_changes( - &(&asset_in_state).into(), - &(&asset_out_state).into(), - amount, - asset_fee, - protocol_fee, - current_imbalance.value, - ) - .ok_or(ArithmeticError::Overflow)?; - - ensure!( - *state_changes.asset_out.delta_reserve > Balance::zero(), - Error::::ZeroAmountOut - ); - - ensure!( - *state_changes.asset_out.delta_reserve >= min_buy_amount, - Error::::BuyLimitNotReached - ); - - ensure!( - *state_changes.asset_out.delta_reserve - <= asset_out_state - .reserve - .checked_div(T::MaxOutRatio::get()) - .ok_or(ArithmeticError::DivisionByZero)?, // Note: let's be safe. this can only fail if MaxOutRatio is zero. - Error::::MaxOutRatioExceeded - ); - - let new_asset_in_state = asset_in_state - .delta_update(&state_changes.asset_in) - .ok_or(ArithmeticError::Overflow)?; - let new_asset_out_state = asset_out_state - .delta_update(&state_changes.asset_out) - .ok_or(ArithmeticError::Overflow)?; - - debug_assert_eq!( - *state_changes.asset_in.delta_reserve, amount, - "delta_reserve_in is not equal to given amount in" - ); - - T::Currency::transfer( - asset_in, - &who, - &Self::protocol_account(), - *state_changes.asset_in.delta_reserve, - )?; - T::Currency::transfer( - asset_out, - &Self::protocol_account(), - &who, - *state_changes.asset_out.delta_reserve, - )?; - - // Hub liquidity update - work out difference between in and amount so only one update is needed. - let delta_hub_asset = state_changes - .asset_in - .delta_hub_reserve - .merge( - state_changes - .asset_out - .delta_hub_reserve - .merge(BalanceUpdate::Increase(state_changes.hdx_hub_amount)) - .ok_or(ArithmeticError::Overflow)?, - ) - .ok_or(ArithmeticError::Overflow)?; - - match delta_hub_asset { - BalanceUpdate::Increase(val) if val == Balance::zero() => { - // nothing to do if zero. - } - BalanceUpdate::Increase(_) => { - // trade can only burn some. This would be a bug. - return Err(Error::::HubAssetUpdateError.into()); - } - BalanceUpdate::Decrease(amount) => { - T::Currency::withdraw(T::HubAssetId::get(), &Self::protocol_account(), amount)?; - } - }; - - // Callback hook info - let info_in: AssetInfo = AssetInfo::new( - asset_in, - &asset_in_state, - &new_asset_in_state, - &state_changes.asset_in, - false, - ); - - let info_out: AssetInfo = AssetInfo::new( - asset_out, - &asset_out_state, - &new_asset_out_state, - &state_changes.asset_out, - false, - ); - - Self::update_imbalance(state_changes.delta_imbalance)?; - - Self::set_asset_state(asset_in, new_asset_in_state); - Self::set_asset_state(asset_out, new_asset_out_state); - - T::OmnipoolHooks::on_trade(origin.clone(), info_in, info_out)?; - - Self::update_hdx_subpool_hub_asset(origin, state_changes.hdx_hub_amount)?; - - Self::process_trade_fee(&who, asset_out, state_changes.fee.asset_fee)?; - - debug_assert!(*state_changes.asset_in.delta_hub_reserve >= *state_changes.asset_out.delta_hub_reserve); - debug_assert_eq!( - *state_changes.asset_in.delta_hub_reserve - *state_changes.asset_out.delta_hub_reserve, - state_changes.fee.protocol_fee - ); - - Self::deposit_event(Event::SellExecuted { - who, - asset_in, - asset_out, - amount_in: amount, - amount_out: *state_changes.asset_out.delta_reserve, - hub_amount_in: *state_changes.asset_in.delta_hub_reserve, - hub_amount_out: *state_changes.asset_out.delta_hub_reserve, - asset_fee_amount: state_changes.fee.asset_fee, - protocol_fee_amount: state_changes.fee.protocol_fee, - }); - - #[cfg(feature = "try-runtime")] - Self::ensure_trade_invariant( - (asset_in, asset_in_state, new_asset_in_state), - (asset_out, asset_out_state, new_asset_out_state), - ); - - Ok(()) + Self::do_sell(origin, asset_in, asset_out, amount, min_buy_amount, None) } /// Execute a swap of `asset_out` for `asset_in`. @@ -1226,7 +1059,8 @@ pub mod pallet { /// - `amount`: Amount of asset sold /// - `max_sell_amount`: Maximum amount to be sold. /// - /// Emits `BuyExecuted` event when successful. + /// Emits `BuyExecuted` event when successful. Deprecated. + /// Emits `pallet_trade_event::Swapped` event when successful. /// #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::buy() @@ -1395,8 +1229,9 @@ pub mod pallet { state_changes.fee.protocol_fee ); + // TODO: Deprecated, remove when ready Self::deposit_event(Event::BuyExecuted { - who, + who: who.clone(), asset_in, asset_out, amount_in: *state_changes.asset_in.delta_reserve, @@ -1407,6 +1242,23 @@ pub mod pallet { protocol_fee_amount: state_changes.fee.protocol_fee, }); + Self::deposit_event(Event::HubAmountUpdated { + hub_amount_in: *state_changes.asset_in.delta_hub_reserve, + hub_amount_out: *state_changes.asset_out.delta_hub_reserve, + }); + + pallet_trade_event::Pallet::::deposit_trade_event( + who.clone(), + pallet_trade_event::PoolType::Omnipool, + pallet_trade_event::TradeOperation::Buy, + asset_in.into(), + asset_out.into(), + *state_changes.asset_in.delta_reserve, + *state_changes.asset_out.delta_reserve, + vec![], // TODO + None, + ); + #[cfg(feature = "try-runtime")] Self::ensure_trade_invariant( (asset_in, asset_in_state, new_asset_in_state), @@ -1822,6 +1674,213 @@ impl Pallet { asset_in.tradable.contains(Tradability::SELL) && asset_out.tradable.contains(Tradability::BUY) } + fn do_sell( + origin: frame_system::pallet_prelude::OriginFor, + asset_in: T::AssetId, + asset_out: T::AssetId, + amount: Balance, + min_buy_amount: Balance, + batch_id: Option, + ) -> DispatchResult { + let who = frame_system::ensure_signed(origin.clone())?; + + ensure!(asset_in != asset_out, Error::::SameAssetTradeNotAllowed); + + ensure!( + amount >= T::MinimumTradingLimit::get(), + Error::::InsufficientTradingAmount + ); + + ensure!( + T::Currency::ensure_can_withdraw(asset_in, &who, amount).is_ok(), + Error::::InsufficientBalance + ); + + // Special handling when one of the asset is Hub Asset + // Math is simplified and asset_in is actually part of asset_out state in this case + if asset_in == T::HubAssetId::get() { + return Self::sell_hub_asset(origin, &who, asset_out, amount, min_buy_amount); + } + + if asset_out == T::HubAssetId::get() { + return Self::sell_asset_for_hub_asset(&who, asset_in, amount, min_buy_amount); + } + + let asset_in_state = Self::load_asset_state(asset_in)?; + let asset_out_state = Self::load_asset_state(asset_out)?; + + ensure!( + Self::allow_assets(&asset_in_state, &asset_out_state), + Error::::NotAllowed + ); + + ensure!( + amount + <= asset_in_state + .reserve + .checked_div(T::MaxInRatio::get()) + .ok_or(ArithmeticError::DivisionByZero)?, // Note: this can only fail if MaxInRatio is zero. + Error::::MaxInRatioExceeded + ); + + let current_imbalance = >::get(); + + let (asset_fee, _) = T::Fee::get(&asset_out); + let (_, protocol_fee) = T::Fee::get(&asset_in); + + let state_changes = hydra_dx_math::omnipool::calculate_sell_state_changes( + &(&asset_in_state).into(), + &(&asset_out_state).into(), + amount, + asset_fee, + protocol_fee, + current_imbalance.value, + ) + .ok_or(ArithmeticError::Overflow)?; + + ensure!( + *state_changes.asset_out.delta_reserve > Balance::zero(), + Error::::ZeroAmountOut + ); + + ensure!( + *state_changes.asset_out.delta_reserve >= min_buy_amount, + Error::::BuyLimitNotReached + ); + + ensure!( + *state_changes.asset_out.delta_reserve + <= asset_out_state + .reserve + .checked_div(T::MaxOutRatio::get()) + .ok_or(ArithmeticError::DivisionByZero)?, // Note: let's be safe. this can only fail if MaxOutRatio is zero. + Error::::MaxOutRatioExceeded + ); + + let new_asset_in_state = asset_in_state + .delta_update(&state_changes.asset_in) + .ok_or(ArithmeticError::Overflow)?; + let new_asset_out_state = asset_out_state + .delta_update(&state_changes.asset_out) + .ok_or(ArithmeticError::Overflow)?; + + debug_assert_eq!( + *state_changes.asset_in.delta_reserve, amount, + "delta_reserve_in is not equal to given amount in" + ); + + T::Currency::transfer( + asset_in, + &who, + &Self::protocol_account(), + *state_changes.asset_in.delta_reserve, + )?; + T::Currency::transfer( + asset_out, + &Self::protocol_account(), + &who, + *state_changes.asset_out.delta_reserve, + )?; + + // Hub liquidity update - work out difference between in and amount so only one update is needed. + let delta_hub_asset = state_changes + .asset_in + .delta_hub_reserve + .merge( + state_changes + .asset_out + .delta_hub_reserve + .merge(BalanceUpdate::Increase(state_changes.hdx_hub_amount)) + .ok_or(ArithmeticError::Overflow)?, + ) + .ok_or(ArithmeticError::Overflow)?; + + match delta_hub_asset { + BalanceUpdate::Increase(val) if val == Balance::zero() => { + // nothing to do if zero. + } + BalanceUpdate::Increase(_) => { + // trade can only burn some. This would be a bug. + return Err(Error::::HubAssetUpdateError.into()); + } + BalanceUpdate::Decrease(amount) => { + T::Currency::withdraw(T::HubAssetId::get(), &Self::protocol_account(), amount)?; + } + }; + + // Callback hook info + let info_in: AssetInfo = AssetInfo::new( + asset_in, + &asset_in_state, + &new_asset_in_state, + &state_changes.asset_in, + false, + ); + + let info_out: AssetInfo = AssetInfo::new( + asset_out, + &asset_out_state, + &new_asset_out_state, + &state_changes.asset_out, + false, + ); + + Self::update_imbalance(state_changes.delta_imbalance)?; + + Self::set_asset_state(asset_in, new_asset_in_state); + Self::set_asset_state(asset_out, new_asset_out_state); + + T::OmnipoolHooks::on_trade(origin.clone(), info_in, info_out)?; + + Self::update_hdx_subpool_hub_asset(origin, state_changes.hdx_hub_amount)?; + + Self::process_trade_fee(&who, asset_out, state_changes.fee.asset_fee)?; + + debug_assert!(*state_changes.asset_in.delta_hub_reserve >= *state_changes.asset_out.delta_hub_reserve); + debug_assert_eq!( + *state_changes.asset_in.delta_hub_reserve - *state_changes.asset_out.delta_hub_reserve, + state_changes.fee.protocol_fee + ); + + // TODO: Deprecated, remove when ready + Self::deposit_event(Event::SellExecuted { + who: who.clone(), + asset_in, + asset_out, + amount_in: amount, + amount_out: *state_changes.asset_out.delta_reserve, + hub_amount_in: *state_changes.asset_in.delta_hub_reserve, + hub_amount_out: *state_changes.asset_out.delta_hub_reserve, + asset_fee_amount: state_changes.fee.asset_fee, + protocol_fee_amount: state_changes.fee.protocol_fee, + }); + + Self::deposit_event(Event::HubAmountUpdated { + hub_amount_in: *state_changes.asset_in.delta_hub_reserve, + hub_amount_out: *state_changes.asset_out.delta_hub_reserve, + }); + + pallet_trade_event::Pallet::::deposit_trade_event( + who.clone(), + pallet_trade_event::PoolType::Omnipool, + pallet_trade_event::TradeOperation::Sell, + asset_in.into(), + asset_out.into(), + amount, + *state_changes.asset_out.delta_reserve, + vec![], // TODO + batch_id, + ); + + #[cfg(feature = "try-runtime")] + Self::ensure_trade_invariant( + (asset_in, asset_in_state, new_asset_in_state), + (asset_out, asset_out_state, new_asset_out_state), + ); + + Ok(()) + } + /// Swap hub asset for asset_out. /// Special handling of sell trade where asset in is Hub Asset. fn sell_hub_asset( @@ -1912,6 +1971,7 @@ impl Pallet { Self::process_trade_fee(who, asset_out, state_changes.fee.asset_fee)?; + // TODO: Deprecated, remove when ready Self::deposit_event(Event::SellExecuted { who: who.clone(), asset_in: T::HubAssetId::get(), @@ -1924,6 +1984,18 @@ impl Pallet { protocol_fee_amount: state_changes.fee.protocol_fee, }); + pallet_trade_event::Pallet::::deposit_trade_event( + who.clone(), + pallet_trade_event::PoolType::Omnipool, + pallet_trade_event::TradeOperation::Sell, + T::HubAssetId::get().into(), + asset_out.into(), + *state_changes.asset.delta_hub_reserve, + *state_changes.asset.delta_reserve, + vec![], // TODO + None, + ); + T::OmnipoolHooks::on_hub_asset_trade(origin, info)?; Ok(()) @@ -2019,6 +2091,7 @@ impl Pallet { Self::process_trade_fee(who, asset_out, state_changes.fee.asset_fee)?; + // TODO: Deprecated, remove when ready Self::deposit_event(Event::BuyExecuted { who: who.clone(), asset_in: T::HubAssetId::get(), @@ -2031,6 +2104,18 @@ impl Pallet { protocol_fee_amount: state_changes.fee.protocol_fee, }); + pallet_trade_event::Pallet::::deposit_trade_event( + who.clone(), + pallet_trade_event::PoolType::Omnipool, + pallet_trade_event::TradeOperation::Buy, + T::HubAssetId::get().into(), + asset_out.into(), + *state_changes.asset.delta_hub_reserve, + *state_changes.asset.delta_reserve, + vec![], // TODO + None, + ); + T::OmnipoolHooks::on_hub_asset_trade(origin, info)?; Ok(()) diff --git a/pallets/omnipool/src/router_execution.rs b/pallets/omnipool/src/router_execution.rs index 7ff3e9df0..356249a37 100644 --- a/pallets/omnipool/src/router_execution.rs +++ b/pallets/omnipool/src/router_execution.rs @@ -5,13 +5,14 @@ use hydra_dx_math::omnipool::types::I129; use hydradx_traits::router::{ExecutorError, PoolType, TradeExecution}; use orml_traits::{GetByKey, MultiCurrency}; +use pallet_trade_event::IncrementalIdType; use sp_runtime::traits::Get; use sp_runtime::DispatchError::Corruption; use sp_runtime::{ArithmeticError, DispatchError, FixedPointNumber, FixedU128}; // dev note: The code is calculate sell and buy is copied from the corresponding functions. // This is not ideal and should be refactored to avoid code duplication. -impl TradeExecution, T::AccountId, T::AssetId, Balance> for Pallet { +impl TradeExecution, T::AccountId, T::AssetId, Balance, IncrementalIdType> for Pallet { type Error = DispatchError; fn calculate_sell( @@ -131,12 +132,13 @@ impl TradeExecution, T::AccountId, T::AssetId, Balance> asset_out: T::AssetId, amount_in: Balance, min_limit: Balance, + batch_id: Option, ) -> Result<(), ExecutorError> { if pool_type != PoolType::Omnipool { return Err(ExecutorError::NotSupported); } - Self::sell(who, asset_in, asset_out, amount_in, min_limit).map_err(ExecutorError::Error) + Self::do_sell(who, asset_in, asset_out, amount_in, min_limit, batch_id).map_err(ExecutorError::Error) } fn execute_buy( diff --git a/pallets/omnipool/src/tests/mock.rs b/pallets/omnipool/src/tests/mock.rs index 6d7dfe5a8..b4b7aa195 100644 --- a/pallets/omnipool/src/tests/mock.rs +++ b/pallets/omnipool/src/tests/mock.rs @@ -86,6 +86,7 @@ construct_runtime!( Balances: pallet_balances, Omnipool: pallet_omnipool, Tokens: orml_tokens, + TradeEvent: pallet_trade_event, } ); @@ -175,6 +176,10 @@ parameter_types! { pub MinWithdrawFee: Permill = WITHDRAWAL_FEE.with(|v| *v.borrow()); } +impl pallet_trade_event::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type AssetId = AssetId; diff --git a/pallets/otc/Cargo.toml b/pallets/otc/Cargo.toml index 97633fbdf..da91daea7 100644 --- a/pallets/otc/Cargo.toml +++ b/pallets/otc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'pallet-otc' -version = '2.0.1' +version = '2.1.0' description = 'A pallet for trustless over-the-counter trading' authors = ['GalacticCouncil'] edition = '2021' @@ -12,6 +12,10 @@ repository = "https://github.com/galacticcouncil/Hydradx-node" codec = { package = "parity-scale-codec", version = "3.4.0", features = ["derive", "max-encoded-len"], default-features = false } scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +# HydraDX dependencies +hydradx-traits = { workspace = true } +pallet-trade-event = { workspace = true } + # primitives sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -21,9 +25,6 @@ sp-core = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -# HydraDX dependencies -hydradx-traits = { workspace = true } - # ORML dependencies orml-traits = { workspace = true } @@ -52,7 +53,8 @@ std = [ 'orml-tokens/std', 'orml-traits/std', 'hydradx-traits/std', - 'frame-benchmarking/std' + 'frame-benchmarking/std', + "pallet-trade-event/std", ] runtime-benchmarks = [ diff --git a/pallets/otc/src/lib.rs b/pallets/otc/src/lib.rs index a4604329d..113855905 100644 --- a/pallets/otc/src/lib.rs +++ b/pallets/otc/src/lib.rs @@ -81,9 +81,9 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_trade_event::Config { /// Identifier for the class of asset. - type AssetId: Member + Parameter + Copy + HasCompact + MaybeSerializeDeserialize + MaxEncodedLen; + type AssetId: Member + Parameter + Copy + HasCompact + MaybeSerializeDeserialize + MaxEncodedLen + Into; /// Asset Registry mechanism - used to check if asset is correctly registered in asset registry. type AssetRegistry: Inspect; @@ -124,6 +124,7 @@ pub mod pallet { /// An Order has been cancelled Cancelled { order_id: OrderId }, /// An Order has been completely filled + /// Deprecated. Replaced by pallet_trade_event::Swapped Filled { order_id: OrderId, who: T::AccountId, @@ -132,6 +133,7 @@ pub mod pallet { fee: Balance, }, /// An Order has been partially filled + /// Deprecated. Replaced by pallet_trade_event::Swapped PartiallyFilled { order_id: OrderId, who: T::AccountId, @@ -266,7 +268,8 @@ pub mod pallet { /// of asset_out multiplied by ExistentialDepositMultiplier /// /// Events: - /// `PartiallyFilled` event when successful. + /// `PartiallyFilled` event when successful. Deprecated. + /// `pallet_trade_event::Swapped` event when successful. #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::partial_fill_order())] pub fn partial_fill_order(origin: OriginFor, order_id: OrderId, amount_in: Balance) -> DispatchResult { @@ -296,13 +299,27 @@ pub mod pallet { Self::execute_order(order, &who, amount_in, amount_out, fee)?; + // TODO: Deprecated, remove when ready Self::deposit_event(Event::PartiallyFilled { order_id, - who, + who: who.clone(), amount_in, amount_out, fee, }); + + pallet_trade_event::Pallet::::deposit_trade_event( + who, + pallet_trade_event::PoolType::OTC(order_id), + pallet_trade_event::TradeOperation::Sell, + order.asset_in.into(), + order.asset_out.into(), + order.amount_in, + order.amount_out, + vec![(order.asset_out.into(), fee)], + None, + ); + Ok(()) }) } @@ -313,7 +330,8 @@ pub mod pallet { /// - `order_id`: ID of the order /// /// Events: - /// `Filled` event when successful. + /// `Filled` event when successful. Deprecated. + /// `pallet_trade_event::Swapped` event when successful. #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::fill_order())] pub fn fill_order(origin: OriginFor, order_id: OrderId) -> DispatchResult { @@ -325,13 +343,27 @@ pub mod pallet { Self::execute_order(&order, &who, order.amount_in, order.amount_out, fee)?; >::remove(order_id); + // TODO: Deprecated, remove when ready Self::deposit_event(Event::Filled { order_id, - who, + who: who.clone(), amount_in: order.amount_in, amount_out: order.amount_out, fee, }); + + pallet_trade_event::Pallet::::deposit_trade_event( + who, + pallet_trade_event::PoolType::OTC(order_id), + pallet_trade_event::TradeOperation::Sell, + order.asset_in.into(), + order.asset_out.into(), + order.amount_in, + order.amount_out, + vec![(order.asset_out.into(), fee)], + None, + ); + Ok(()) } diff --git a/pallets/otc/src/tests/mock.rs b/pallets/otc/src/tests/mock.rs index e314193d6..1438c14a3 100644 --- a/pallets/otc/src/tests/mock.rs +++ b/pallets/otc/src/tests/mock.rs @@ -57,6 +57,7 @@ frame_support::construct_runtime!( System: frame_system, OTC: otc, Tokens: orml_tokens, + TradeEvent: pallet_trade_event, } ); @@ -139,6 +140,10 @@ impl orml_tokens::Config for Test { type CurrencyHooks = (); } +impl pallet_trade_event::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + pub struct DummyRegistry(sp_std::marker::PhantomData); impl Inspect for DummyRegistry { diff --git a/pallets/route-executor/Cargo.toml b/pallets/route-executor/Cargo.toml index 16894edfd..2a6730ddf 100644 --- a/pallets/route-executor/Cargo.toml +++ b/pallets/route-executor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'pallet-route-executor' -version = '2.6.0' +version = '2.7.0' description = 'A pallet to execute a route containing a sequence of trades' authors = ['GalacticCouncil'] edition = '2021' @@ -15,6 +15,8 @@ serde = { features = ["derive"], optional = true, version = "1.0.137" } # HydraDX dependencies hydradx-traits = { workspace = true } hydra-dx-math = { workspace = true } +pallet-trade-event = { workspace = true } +primitives = { workspace = true } # ORML dependencies orml-traits = { workspace = true } @@ -46,11 +48,19 @@ runtime-benchmarks = [ std = [ 'serde/std', 'codec/std', + 'scale-info/std', 'sp-std/std', 'frame-support/std', 'frame-system/std', 'orml-tokens/std', + 'orml-traits/std', "hydradx-adapters/std", "pallet-balances/std", + "pallet-trade-event/std", + 'hydradx-traits/std', + 'hydra-dx-math/std', + 'frame-benchmarking/std', + 'sp-core/std', + 'sp-runtime/std', ] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/route-executor/src/lib.rs b/pallets/route-executor/src/lib.rs index ac64928dd..1bd07318b 100644 --- a/pallets/route-executor/src/lib.rs +++ b/pallets/route-executor/src/lib.rs @@ -37,7 +37,9 @@ use hydradx_traits::router::{inverse_route, AssetPair, RefundEdCalculator, Route pub use hydradx_traits::router::{ AmmTradeWeights, AmountInAndOut, ExecutorError, PoolType, RouterT, Trade, TradeExecution, }; +use hydradx_traits::IncrementalIdProvider; use orml_traits::arithmetic::{CheckedAdd, CheckedSub}; +use primitives::IncrementalId; use sp_core::U512; use sp_runtime::traits::{AccountIdConversion, CheckedDiv}; use sp_runtime::{ArithmeticError, DispatchError, FixedPointNumber, FixedU128, Saturating, TransactionOutcome}; @@ -107,6 +109,7 @@ pub mod pallet { Self::AccountId, Self::AssetId, Self::Balance, + IncrementalId, Error = DispatchError, >; @@ -126,6 +129,7 @@ pub mod pallet { /// Origin able to set route without validation type TechnicalOrigin: EnsureOrigin; + type BatchIdProvider: IncrementalIdProvider; /// Weight information for the extrinsics. type WeightInfo: AmmTradeWeights>; } @@ -490,6 +494,7 @@ impl Pallet { let user_balance_of_asset_in_before_trade = T::Currency::reducible_balance(trade.asset_in, &who, Preservation::Expendable, Fortitude::Polite); + let next_batch_id = T::BatchIdProvider::next_id(); let execution_result = T::AMM::execute_sell( origin.clone(), trade.pool, @@ -497,6 +502,7 @@ impl Pallet { trade.asset_out, trade_amount.amount_in, trade_amount.amount_out, + Some(next_batch_id), ); handle_execution_error!(execution_result); @@ -687,6 +693,7 @@ impl Pallet { PoolType::Stableswap(pool_id) => pool_id, PoolType::XYK => first_route.asset_out, PoolType::LBP => first_route.asset_out, + _ => return Err(Error::::PoolNotSupported.into()), }; let asset_in_liquidity = T::AMM::get_liquidity_depth(first_route.pool, first_route.asset_in, asset_b); diff --git a/pallets/stableswap/Cargo.toml b/pallets/stableswap/Cargo.toml index 462c072f5..c2cece27e 100644 --- a/pallets/stableswap/Cargo.toml +++ b/pallets/stableswap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'pallet-stableswap' -version = '3.6.4' +version = '3.7.0' description = 'AMM for correlated assets' authors = ['GalacticCouncil'] edition = '2021' @@ -19,6 +19,10 @@ scale-info = { version = "2.1.2", default-features = false, features = ["derive" codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.4.0" } serde = { features = ["derive"], optional = true, version = "1.0.137" } +# HydraDX dependencies +hydradx-traits = { workspace = true } +pallet-trade-event = { workspace = true } + # primitives sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -32,9 +36,6 @@ frame-system = { workspace = true } # Math hydra-dx-math = { workspace = true } -# HydraDX dependencies -hydradx-traits = { workspace = true } - # ORML dependencies orml-traits = { workspace = true } @@ -70,5 +71,6 @@ std = [ "frame-benchmarking/std", 'orml-traits/std', "hydra-dx-math/std", + "pallet-trade-event/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/stableswap/src/lib.rs b/pallets/stableswap/src/lib.rs index 13cf9caeb..c0bf70169 100644 --- a/pallets/stableswap/src/lib.rs +++ b/pallets/stableswap/src/lib.rs @@ -72,6 +72,7 @@ use crate::types::{AssetAmount, Balance, PoolInfo, PoolState, StableswapHooks, T use hydra_dx_math::stableswap::types::AssetReserve; use hydradx_traits::pools::DustRemovalAccountWhitelist; use orml_traits::MultiCurrency; +use pallet_trade_event::IncrementalIdType; use sp_std::collections::btree_map::BTreeMap; pub use weights::WeightInfo; @@ -110,7 +111,7 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_trade_event::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -126,7 +127,8 @@ pub mod pallet { + HasCompact + MaybeSerializeDeserialize + MaxEncodedLen - + TypeInfo; + + TypeInfo + + Into; /// Multi currency mechanism type Currency: MultiCurrency; @@ -204,6 +206,7 @@ pub mod pallet { fee: Balance, }, /// Sell trade executed. Trade fee paid in asset leaving the pool (already subtracted from amount_out). + /// Deprecated. Replaced by pallet_trade_event::Swapped SellExecuted { who: T::AccountId, pool_id: T::AssetId, @@ -214,6 +217,7 @@ pub mod pallet { fee: Balance, }, /// Buy trade executed. Trade fee paid in asset entering the pool (already included in amount_in). + /// Deprecated. Replaced by pallet_trade_event::Swapped BuyExecuted { who: T::AccountId, pool_id: T::AssetId, @@ -709,7 +713,8 @@ pub mod pallet { /// - `amount_in`: Amount of asset to be sold to the pool /// - `min_buy_amount`: Minimum amount required to receive /// - /// Emits `SellExecuted` event when successful. + /// Emits `SellExecuted` event when successful. Deprecated. + /// Emits `pallet_trade_event::Swapped` event when successful. /// #[pallet::call_index(7)] #[pallet::weight(::WeightInfo::sell() @@ -723,55 +728,7 @@ pub mod pallet { amount_in: Balance, min_buy_amount: Balance, ) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!(asset_in != asset_out, Error::::NotAllowed); - - ensure!( - Self::is_asset_allowed(pool_id, asset_in, Tradability::SELL) - && Self::is_asset_allowed(pool_id, asset_out, Tradability::BUY), - Error::::NotAllowed - ); - - ensure!( - amount_in >= T::MinTradingLimit::get(), - Error::::InsufficientTradingAmount - ); - - ensure!( - T::Currency::free_balance(asset_in, &who) >= amount_in, - Error::::InsufficientBalance - ); - - let pool = Pools::::get(pool_id).ok_or(Error::::PoolNotFound)?; - let pool_account = Self::pool_account(pool_id); - let initial_reserves = pool - .reserves_with_decimals::(&pool_account) - .ok_or(Error::::UnknownDecimals)?; - - let (amount_out, fee_amount) = Self::calculate_out_amount(pool_id, asset_in, asset_out, amount_in)?; - ensure!(amount_out >= min_buy_amount, Error::::BuyLimitNotReached); - - T::Currency::transfer(asset_in, &who, &pool_account, amount_in)?; - T::Currency::transfer(asset_out, &pool_account, &who, amount_out)?; - - //All done and updated. Let's call on_trade hook. - Self::call_on_trade_hook(pool_id, asset_in, asset_out, &initial_reserves)?; - - Self::deposit_event(Event::SellExecuted { - who, - pool_id, - asset_in, - asset_out, - amount_in, - amount_out, - fee: fee_amount, - }); - - #[cfg(feature = "try-runtime")] - Self::ensure_trade_invariant(pool_id, &initial_reserves, pool.fee); - - Ok(()) + Self::do_sell(origin, pool_id, asset_in, asset_out, amount_in, min_buy_amount, None) } /// Execute a swap of `asset_in` for `asset_out`. @@ -784,7 +741,8 @@ pub mod pallet { /// - `amount_out`: Amount of asset to receive from the pool /// - `max_sell_amount`: Maximum amount allowed to be sold /// - /// Emits `BuyExecuted` event when successful. + /// Emits `BuyExecuted` event when successful. Deprecated. + /// Emits `pallet_trade_event::Swapped` event when successful. /// #[pallet::call_index(8)] #[pallet::weight(::WeightInfo::buy() @@ -836,8 +794,9 @@ pub mod pallet { //All done and updated. Let's call on_trade_hook. Self::call_on_trade_hook(pool_id, asset_in, asset_out, &initial_reserves)?; + // TODO: Deprecated, remove when ready Self::deposit_event(Event::BuyExecuted { - who, + who: who.clone(), pool_id, asset_in, asset_out, @@ -846,6 +805,18 @@ pub mod pallet { fee: fee_amount, }); + pallet_trade_event::Pallet::::deposit_trade_event( + who, + pallet_trade_event::PoolType::Stableswap(pool_id.into()), + pallet_trade_event::TradeOperation::Buy, + asset_in.into(), + asset_out.into(), + amount_in, + amount_out, + vec![(asset_in.into(), fee_amount)], + None, + ); + #[cfg(feature = "try-runtime")] Self::ensure_trade_invariant(pool_id, &initial_reserves, pool.fee); @@ -890,6 +861,79 @@ impl Pallet { PalletId(*b"stblpool").into_account_truncating() } + fn do_sell( + origin: frame_system::pallet_prelude::OriginFor, + pool_id: T::AssetId, + asset_in: T::AssetId, + asset_out: T::AssetId, + amount_in: Balance, + min_buy_amount: Balance, + batch_id: Option, + ) -> DispatchResult { + let who = frame_system::ensure_signed(origin)?; + + ensure!(asset_in != asset_out, Error::::NotAllowed); + + ensure!( + Self::is_asset_allowed(pool_id, asset_in, Tradability::SELL) + && Self::is_asset_allowed(pool_id, asset_out, Tradability::BUY), + Error::::NotAllowed + ); + + ensure!( + amount_in >= T::MinTradingLimit::get(), + Error::::InsufficientTradingAmount + ); + + ensure!( + T::Currency::free_balance(asset_in, &who) >= amount_in, + Error::::InsufficientBalance + ); + + let pool = Pools::::get(pool_id).ok_or(Error::::PoolNotFound)?; + let pool_account = Self::pool_account(pool_id); + let initial_reserves = pool + .reserves_with_decimals::(&pool_account) + .ok_or(Error::::UnknownDecimals)?; + + let (amount_out, fee_amount) = Self::calculate_out_amount(pool_id, asset_in, asset_out, amount_in)?; + ensure!(amount_out >= min_buy_amount, Error::::BuyLimitNotReached); + + T::Currency::transfer(asset_in, &who, &pool_account, amount_in)?; + T::Currency::transfer(asset_out, &pool_account, &who, amount_out)?; + + //All done and updated. Let's call on_trade hook. + Self::call_on_trade_hook(pool_id, asset_in, asset_out, &initial_reserves)?; + + // TODO: Deprecated, remove when ready + Self::deposit_event(Event::SellExecuted { + who: who.clone(), + pool_id, + asset_in, + asset_out, + amount_in, + amount_out, + fee: fee_amount, + }); + + pallet_trade_event::Pallet::::deposit_trade_event( + who, + pallet_trade_event::PoolType::Stableswap(pool_id.into()), + pallet_trade_event::TradeOperation::Sell, + asset_in.into(), + asset_out.into(), + amount_in, + amount_out, + vec![(asset_out.into(), fee_amount)], + batch_id, + ); + + #[cfg(feature = "try-runtime")] + Self::ensure_trade_invariant(pool_id, &initial_reserves, pool.fee); + + Ok(()) + } + /// Calculates out amount given in amount. /// Returns (out_amount, fee_amount) on success. Note that fee amount is already subtracted from the out amount. fn calculate_out_amount( @@ -1178,9 +1222,7 @@ impl Pallet { pub(crate) fn retrieve_decimals(asset_id: T::AssetId) -> Option { T::AssetInspection::decimals(asset_id) } -} -impl Pallet { fn calculate_shares(pool_id: T::AssetId, assets: &[AssetAmount]) -> Result { let pool = Pools::::get(pool_id).ok_or(Error::::PoolNotFound)?; let pool_account = Self::pool_account(pool_id); diff --git a/pallets/stableswap/src/tests/mock.rs b/pallets/stableswap/src/tests/mock.rs index 079938ce3..b94ff1f63 100644 --- a/pallets/stableswap/src/tests/mock.rs +++ b/pallets/stableswap/src/tests/mock.rs @@ -81,6 +81,7 @@ construct_runtime!( System: frame_system, Tokens: orml_tokens, Stableswap: pallet_stableswap, + TradeEvent: pallet_trade_event, } ); @@ -167,6 +168,10 @@ impl DustRemovalAccountWhitelist for Whitelist { } } +impl pallet_trade_event::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type AssetId = AssetId; diff --git a/pallets/stableswap/src/trade_execution.rs b/pallets/stableswap/src/trade_execution.rs index 53d54e25a..4795efb42 100644 --- a/pallets/stableswap/src/trade_execution.rs +++ b/pallets/stableswap/src/trade_execution.rs @@ -3,11 +3,12 @@ use crate::{Balance, Config, Error, Pallet, Pools, D_ITERATIONS, Y_ITERATIONS}; use hydra_dx_math::stableswap::types::AssetReserve; use hydradx_traits::router::{ExecutorError, PoolType, TradeExecution}; use orml_traits::MultiCurrency; +use pallet_trade_event::IncrementalIdType; use sp_core::Get; use sp_runtime::{ArithmeticError, DispatchError, FixedU128}; use sp_std::vec; -impl TradeExecution for Pallet +impl TradeExecution for Pallet where u32: sp_std::convert::From, sp_std::vec::Vec<(u32, AssetReserve)>: FromIterator<(T::AssetId, AssetReserve)>, @@ -142,6 +143,7 @@ where asset_out: T::AssetId, amount_in: Balance, min_limit: Balance, + batch_id: Option, ) -> Result<(), ExecutorError> { match pool_type { PoolType::Stableswap(pool_id) => { @@ -159,7 +161,8 @@ where ) .map_err(ExecutorError::Error) } else { - Self::sell(who, pool_id, asset_in, asset_out, amount_in, min_limit).map_err(ExecutorError::Error) + Self::do_sell(who, pool_id, asset_in, asset_out, amount_in, min_limit, batch_id) + .map_err(ExecutorError::Error) } } _ => Err(ExecutorError::NotSupported), diff --git a/pallets/trade-event/Cargo.toml b/pallets/trade-event/Cargo.toml new file mode 100644 index 000000000..6e2d70549 --- /dev/null +++ b/pallets/trade-event/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "pallet-trade-event" +version = "1.0.0" +authors = ["GalacticCouncil"] +edition = "2021" +license = "Apache 2.0" +homepage = 'https://github.com/galacticcouncil/hydration-node' +repository = 'https://github.com/galacticcouncil/hydration-node' +description = "Trade event pallet provides unified event for AMMs" +readme = "README.md" + +[dependencies] +codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.4.0" } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } + +# HydraDX dependencies +hydradx-traits = { workspace = true } +primitives = { workspace = true } + +# Substrate dependencies +sp-std = { workspace = true } +sp-api = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-std/std", + "sp-api/std", + "primitives/std", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/trade-event/README.md b/pallets/trade-event/README.md new file mode 100644 index 000000000..625b66c14 --- /dev/null +++ b/pallets/trade-event/README.md @@ -0,0 +1,3 @@ +# Trade event pallet + +Support pallet for AMMs. Includes the unified event that is emitted by all AMM pallets. diff --git a/pallets/trade-event/src/lib.rs b/pallets/trade-event/src/lib.rs new file mode 100644 index 000000000..469285976 --- /dev/null +++ b/pallets/trade-event/src/lib.rs @@ -0,0 +1,111 @@ +// This file is part of hydration-node. + +// Copyright (C) 2020-2022 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +type AssetId = u32; +type Balance = u128; + +pub use hydradx_traits::{ + router::{PoolType, TradeOperation}, + IncrementalIdProvider, +}; +pub use primitives::IncrementalId as IncrementalIdType; + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::storage] + /// Incremental ID + #[pallet::getter(fn incremental_id)] + pub(super) type IncrementalId = StorageValue<_, IncrementalIdType, ValueQuery>; + + #[pallet::error] + pub enum Error {} + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Trade executed. + Swapped { + who: T::AccountId, + pool: PoolType, + operation: TradeOperation, + asset_in: AssetId, + asset_out: AssetId, + amount_in: Balance, + amount_out: Balance, + fees: Vec<(AssetId, Balance)>, + batch_id: Option, + }, + } + + #[pallet::call] + impl Pallet {} +} + +impl Pallet { + pub fn next_incremental_id() -> IncrementalIdType { + let next_incremental_id = Self::incremental_id(); + >::set(next_incremental_id + 1); // TODO: checked math + next_incremental_id + } + + #[allow(clippy::too_many_arguments)] + pub fn deposit_trade_event( + who: T::AccountId, + pool: PoolType, + operation: TradeOperation, + asset_in: AssetId, + asset_out: AssetId, + amount_in: Balance, + amount_out: Balance, + fees: Vec<(AssetId, Balance)>, + batch_id: Option, + ) { + Self::deposit_event(Event::::Swapped { + who, + pool, + operation, + asset_in, + asset_out, + amount_in, + amount_out, + fees, + batch_id, + }); + } +} + +impl IncrementalIdProvider for Pallet { + fn next_id() -> IncrementalIdType { + Self::next_incremental_id() + } +} diff --git a/pallets/xyk-liquidity-mining/src/lib.rs b/pallets/xyk-liquidity-mining/src/lib.rs index 4ae443483..5bc81871e 100644 --- a/pallets/xyk-liquidity-mining/src/lib.rs +++ b/pallets/xyk-liquidity-mining/src/lib.rs @@ -112,7 +112,7 @@ pub mod pallet { type Currencies: MultiCurrency; /// AMM helper functions. - type AMM: AMM + type AMM: AMM + AMMPosition; /// The origin account that can create new liquidity mining program. diff --git a/pallets/xyk/Cargo.toml b/pallets/xyk/Cargo.toml index 99c415ede..8289099e4 100644 --- a/pallets/xyk/Cargo.toml +++ b/pallets/xyk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'pallet-xyk' -version = "6.4.4" +version = "6.5.0" description = 'XYK automated market maker' authors = ['GalacticCouncil'] edition = '2021' @@ -29,6 +29,7 @@ orml-traits = { workspace = true } # HydraDX dependencies hydradx-traits = { workspace = true } +pallet-trade-event = { workspace = true } # Substrate dependencies frame-benchmarking = { workspace = true, optional = true } @@ -65,5 +66,6 @@ std = [ 'frame-benchmarking/std', "scale-info/std", "pallet-asset-registry/std", + "pallet-trade-event/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/xyk/src/lib.rs b/pallets/xyk/src/lib.rs index 0090e2c8c..dd31bf78e 100644 --- a/pallets/xyk/src/lib.rs +++ b/pallets/xyk/src/lib.rs @@ -40,6 +40,7 @@ use sp_std::{vec, vec::Vec}; use crate::types::{Amount, AssetId, AssetPair, Balance}; use hydra_dx_math::ratio::Ratio; +use pallet_trade_event::IncrementalIdType; use orml_traits::{MultiCurrency, MultiCurrencyExtended}; #[cfg(test)] @@ -75,7 +76,7 @@ pub mod pallet { impl Hooks> for Pallet {} #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_trade_event::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Registry support @@ -249,6 +250,7 @@ pub mod pallet { }, /// Asset sale executed. + /// Deprecated. Replaced by pallet_trade_event::Swapped SellExecuted { who: T::AccountId, asset_in: AssetId, @@ -261,6 +263,7 @@ pub mod pallet { }, /// Asset purchase executed. + /// Deprecated. Replaced by pallet_trade_event::Swapped BuyExecuted { who: T::AccountId, asset_out: AssetId, @@ -627,7 +630,8 @@ pub mod pallet { /// /// `max_limit` - minimum amount of `asset_out` / amount of asset_out to be obtained from the pool in exchange for `asset_in`. /// - /// Emits `SellExecuted` when successful. + /// Emits `SellExecuted` when successful. Deprecated. + /// Emits `pallet_trade_event::Swapped` when successful. #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::sell() + ::AMMHandler::on_trade_weight())] pub fn sell( @@ -640,7 +644,14 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - >::sell(&who, AssetPair { asset_in, asset_out }, amount, max_limit, discount)?; + >::sell( + &who, + AssetPair { asset_in, asset_out }, + amount, + max_limit, + discount, + None, + )?; Ok(()) } @@ -650,8 +661,8 @@ pub mod pallet { /// Executes a swap of `asset_in` for `asset_out`. Price is determined by the liquidity pool. /// /// `max_limit` - maximum amount of `asset_in` to be sold in exchange for `asset_out`. - /// - /// Emits `BuyExecuted` when successful. + /// Emits `BuyExecuted` when successful. Deprecated. + /// Emits `pallet_trade_event::Swapped` when successful. #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::buy() + ::AMMHandler::on_trade_weight())] pub fn buy( @@ -664,7 +675,7 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - >::buy(&who, AssetPair { asset_in, asset_out }, amount, max_limit, discount)?; + >::buy(&who, AssetPair { asset_in, asset_out }, amount, max_limit, discount)?; Ok(()) } @@ -705,7 +716,7 @@ impl Pallet { } // Implementation of AMM API which makes possible to plug the AMM pool into the exchange pallet. -impl AMM for Pallet { +impl AMM for Pallet { fn exists(assets: AssetPair) -> bool { >::contains_key(&Self::get_pair_id(assets)) } @@ -854,7 +865,10 @@ impl AMM for Pallet { /// Perform necessary storage/state changes. /// Note : the execution should not return error as everything was previously verified and validated. #[transactional] - fn execute_sell(transfer: &AMMTransfer) -> DispatchResult { + fn execute_sell( + transfer: &AMMTransfer, + batch_id: Option, + ) -> DispatchResult { let pair_account = Self::get_pair_id(transfer.assets); if transfer.discount && transfer.discount_amount > 0u128 { @@ -889,6 +903,7 @@ impl AMM for Pallet { ) .map_err(|(_w, e)| e)?; + // TODO: Deprecated, remove when ready Self::deposit_event(Event::::SellExecuted { who: transfer.origin.clone(), asset_in: transfer.assets.asset_in, @@ -900,6 +915,18 @@ impl AMM for Pallet { pool: pair_account, }); + pallet_trade_event::Pallet::::deposit_trade_event( + transfer.origin.clone(), + pallet_trade_event::PoolType::XYK, + pallet_trade_event::TradeOperation::Sell, + transfer.assets.asset_in, + transfer.assets.asset_out, + transfer.amount, + transfer.amount_b, + vec![transfer.fee], + batch_id, + ); + Ok(()) } @@ -1051,6 +1078,7 @@ impl AMM for Pallet { ) .map_err(|(_w, e)| e)?; + // TODO: Deprecated, remove when ready Self::deposit_event(Event::::BuyExecuted { who: transfer.origin.clone(), asset_out: transfer.assets.asset_out, @@ -1062,6 +1090,18 @@ impl AMM for Pallet { pool: pair_account, }); + pallet_trade_event::Pallet::::deposit_trade_event( + transfer.origin.clone(), + pallet_trade_event::PoolType::XYK, + pallet_trade_event::TradeOperation::Buy, + transfer.assets.asset_in, + transfer.assets.asset_out, + transfer.amount, + transfer.amount_b, + vec![transfer.fee], + None, + ); + Ok(()) } diff --git a/pallets/xyk/src/tests/mock.rs b/pallets/xyk/src/tests/mock.rs index 00aaa71bb..180c6bb14 100644 --- a/pallets/xyk/src/tests/mock.rs +++ b/pallets/xyk/src/tests/mock.rs @@ -61,6 +61,7 @@ frame_support::construct_runtime!( XYK: xyk, Currency: orml_tokens, AssetRegistry: pallet_asset_registry, + TradeEvent: pallet_trade_event, } ); @@ -197,6 +198,10 @@ impl CanCreatePool for Disallow10_10Pool { } } +impl pallet_trade_event::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + impl xyk::Config for Test { type RuntimeEvent = RuntimeEvent; type AssetRegistry = AssetRegistry; diff --git a/pallets/xyk/src/trade_execution.rs b/pallets/xyk/src/trade_execution.rs index 3cdb2f22b..3cbd7e955 100644 --- a/pallets/xyk/src/trade_execution.rs +++ b/pallets/xyk/src/trade_execution.rs @@ -5,10 +5,11 @@ use frame_support::traits::Get; use hydradx_traits::router::{ExecutorError, PoolType, TradeExecution}; use hydradx_traits::AMM; use orml_traits::MultiCurrency; +use pallet_trade_event::IncrementalIdType; use sp_runtime::DispatchError::Corruption; use sp_runtime::{ArithmeticError, DispatchError, FixedPointNumber, FixedU128}; -impl TradeExecution for Pallet { +impl TradeExecution for Pallet { type Error = DispatchError; fn calculate_sell( @@ -100,12 +101,26 @@ impl TradeExecution asset_out: AssetId, amount_in: Balance, min_limit: Balance, + batch_id: Option, ) -> Result<(), ExecutorError> { if pool_type != PoolType::XYK { return Err(ExecutorError::NotSupported); } - Self::sell(who, asset_in, asset_out, amount_in, min_limit, false).map_err(ExecutorError::Error) + let who = crate::ensure_signed(who).map_err(|e| ExecutorError::Error(e.into()))?; + + >::sell( + &who, + AssetPair { asset_in, asset_out }, + amount_in, + min_limit, + false, + batch_id, + ) + .map_err(ExecutorError::Error)?; + + Ok(()) + // Self::sell(who, asset_in, asset_out, amount_in, min_limit, false).map_err(ExecutorError::Error) } fn execute_buy( diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 9fe493c72..584cb775e 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -75,3 +75,6 @@ pub type Header = generic::Header; /// Block type. pub type Block = generic::Block; + +/// Incremental ID type +pub type IncrementalId = u32; diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 285bfed06..b0dc7c684 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "256.0.0" +version = "257.0.0" authors = ["GalacticCouncil"] edition = "2021" license = "Apache 2.0" @@ -38,6 +38,7 @@ pallet-bonds = { workspace = true } pallet-lbp = { workspace = true } pallet-xyk = { workspace = true } pallet-referrals = { workspace = true } +pallet-trade-event = { workspace = true } pallet-evm-accounts = { workspace = true } pallet-evm-accounts-rpc-runtime-api = { workspace = true } pallet-xyk-liquidity-mining = { workspace = true } @@ -323,6 +324,7 @@ std = [ "parachains-common/std", "polkadot-runtime-common/std", "pallet-state-trie-migration/std", + "pallet-trade-event/std", ] try-runtime = [ "frame-try-runtime", @@ -394,6 +396,7 @@ try-runtime = [ "pallet-xyk-liquidity-mining/try-runtime", "pallet-message-queue/try-runtime", "pallet-state-trie-migration/try-runtime", + "pallet-trade-event/try-runtime", ] metadata-hash = [ diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index 1aa98f746..566b8975e 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -920,6 +920,7 @@ impl AmmTradeWeights> for RouterWeightInfo { } PoolType::XYK => weights::pallet_xyk::HydraWeight::::router_execution_sell(c, e) .saturating_add(::AMMHandler::on_trade_weight()), + _ => Weight::MAX, }; weight.saturating_accrue(amm_weight); } @@ -968,6 +969,7 @@ impl AmmTradeWeights> for RouterWeightInfo { } PoolType::XYK => weights::pallet_xyk::HydraWeight::::router_execution_buy(c, e) .saturating_add(::AMMHandler::on_trade_weight()), + _ => Weight::MAX, }; weight.saturating_accrue(amm_weight); } @@ -1000,6 +1002,7 @@ impl AmmTradeWeights> for RouterWeightInfo { } PoolType::XYK => weights::pallet_xyk::HydraWeight::::router_execution_buy(c, e) .saturating_add(::AMMHandler::on_trade_weight()), + _ => Weight::MAX, }; weight.saturating_accrue(amm_weight); } @@ -1024,6 +1027,7 @@ impl AmmTradeWeights> for RouterWeightInfo { } PoolType::XYK => weights::pallet_xyk::HydraWeight::::router_execution_sell(c, e) .saturating_add(::AMMHandler::on_trade_weight()), + _ => Weight::MAX, }; weight.saturating_accrue(amm_weight); } @@ -1048,6 +1052,7 @@ impl AmmTradeWeights> for RouterWeightInfo { } PoolType::XYK => weights::pallet_xyk::HydraWeight::::router_execution_buy(c, e) .saturating_add(::AMMHandler::on_trade_weight()), + _ => Weight::MAX, }; weight.saturating_accrue(amm_weight); } @@ -1080,6 +1085,7 @@ impl AmmTradeWeights> for RouterWeightInfo { weights::pallet_stableswap::HydraWeight::::router_execution_sell(1, 0) } PoolType::XYK => weights::pallet_xyk::HydraWeight::::router_execution_sell(1, 0), + _ => Weight::MAX, }; weight.saturating_accrue(amm_weight); } @@ -1093,6 +1099,7 @@ impl AmmTradeWeights> for RouterWeightInfo { weights::pallet_stableswap::HydraWeight::::router_execution_sell(1, 0) } PoolType::XYK => weights::pallet_xyk::HydraWeight::::router_execution_sell(1, 0), + _ => Weight::MAX, }; weight.saturating_accrue(amm_weight); } @@ -1125,6 +1132,7 @@ impl AmmTradeWeights> for RouterWeightInfo { weights::pallet_stableswap::HydraWeight::::calculate_spot_price_with_fee() } PoolType::XYK => weights::pallet_xyk::HydraWeight::::calculate_spot_price_with_fee(), + _ => Weight::MAX, }; weight.saturating_accrue(amm_weight); } @@ -1156,6 +1164,7 @@ impl pallet_route_executor::Config for Runtime { type TechnicalOrigin = SuperMajorityTechCommittee; type EdToRefundCalculator = RefundAndLockedEdCalculator; type OraclePriceProvider = hydradx_adapters::OraclePriceProvider; + type BatchIdProvider = TradeEvent; type OraclePeriod = RouteValidationOraclePeriod; } @@ -1525,6 +1534,10 @@ impl pallet_referrals::Config for Runtime { type BenchmarkHelper = ReferralsBenchmarkHelper; } +impl pallet_trade_event::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + pub struct ConvertViaOmnipool(PhantomData); impl Convert for ConvertViaOmnipool where diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 8e5c13482..c0a200646 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -113,7 +113,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("hydradx"), impl_name: create_runtime_str!("hydradx"), authoring_version: 1, - spec_version: 256, + spec_version: 257, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -185,6 +185,7 @@ construct_runtime!( LBP: pallet_lbp = 73, XYK: pallet_xyk = 74, Referrals: pallet_referrals = 75, + TradeEvent: pallet_trade_event = 76, // ORML related modules Tokens: orml_tokens = 77, diff --git a/traits/src/lib.rs b/traits/src/lib.rs index 3078695ca..c67bb3c9b 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -53,7 +53,7 @@ pub struct AMMTransfer { } /// Traits for handling AMM Pool trades. -pub trait AMM { +pub trait AMM { /// Check if both assets exist in a pool. fn exists(assets: AssetPair) -> bool; @@ -80,7 +80,10 @@ pub trait AMM { ) -> Result, frame_support::sp_runtime::DispatchError>; /// Execute buy for given validated transfer. - fn execute_sell(transfer: &AMMTransfer) -> dispatch::DispatchResult; + fn execute_sell( + transfer: &AMMTransfer, + batch_id: Option, + ) -> dispatch::DispatchResult; /// Perform asset swap. /// Call execute following the validation. @@ -90,8 +93,13 @@ pub trait AMM { amount: Amount, min_bought: Amount, discount: bool, + batch_id: Option, ) -> dispatch::DispatchResult { - Self::execute_sell(&Self::validate_sell(origin, assets, amount, min_bought, discount)?)?; + Self::execute_sell( + &Self::validate_sell(origin, assets, amount, min_bought, discount)?, + batch_id, + )?; + Ok(()) } @@ -268,3 +276,7 @@ pub trait AccountFeeCurrencyBalanceInCurrency { type Output; fn get_balance_in_currency(to_currency: AssetId, account: &AccountId) -> Self::Output; } + +pub trait IncrementalIdProvider { + fn next_id() -> IncrementalId; +} diff --git a/traits/src/router.rs b/traits/src/router.rs index e8d6d615b..2ebab2aac 100644 --- a/traits/src/router.rs +++ b/traits/src/router.rs @@ -60,12 +60,19 @@ pub trait RouteProvider { } } +#[derive(Encode, Decode, Clone, Copy, Debug, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +pub enum TradeOperation { + Sell, + Buy, +} + #[derive(Encode, Decode, Clone, Copy, Debug, Eq, PartialEq, TypeInfo, MaxEncodedLen)] pub enum PoolType { XYK, LBP, Stableswap(AssetId), Omnipool, + OTC(u32), } #[derive(Debug, PartialEq, Eq)] @@ -143,7 +150,7 @@ pub trait RouterT { } /// All AMMs used in the router are required to implement this trait. -pub trait TradeExecution { +pub trait TradeExecution { type Error; fn calculate_sell( @@ -167,6 +174,7 @@ pub trait TradeExecution { asset_out: AssetId, amount_in: Balance, min_limit: Balance, + batch_id: Option, ) -> Result<(), ExecutorError>; fn execute_buy( @@ -193,10 +201,10 @@ pub trait TradeExecution { #[allow(clippy::redundant_clone)] //Needed as it complains about redundant clone, but clone is needed as Origin is moved and it is not copy type. #[impl_trait_for_tuples::impl_for_tuples(1, 5)] -impl - TradeExecution for Tuple +impl + TradeExecution for Tuple { - for_tuples!( where #(Tuple: TradeExecution)*); + for_tuples!( where #(Tuple: TradeExecution)*); type Error = E; fn calculate_sell( @@ -242,10 +250,11 @@ impl asset_out: AssetId, amount_in: Balance, min_limit: Balance, + batch_id: Option, ) -> Result<(), ExecutorError> { for_tuples!( #( - let value = match Tuple::execute_sell(who.clone(),pool_type, asset_in, asset_out, amount_in, min_limit) { + let value = match Tuple::execute_sell(who.clone(),pool_type, asset_in, asset_out, amount_in, min_limit, batch_id) { Ok(result) => return Ok(result), Err(v) if v == ExecutorError::NotSupported => v, Err(v) => return Err(v),