From c393706317526c8b71573629820ce3b971873fc4 Mon Sep 17 00:00:00 2001 From: zqh Date: Mon, 20 Jun 2022 23:25:52 +0800 Subject: [PATCH 01/20] triangle swap --- modules/dex/src/lib.rs | 68 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index cc3732e2e4..11db630a25 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -90,6 +90,7 @@ impl Default for TradingPairStatus { #[frame_support::pallet] pub mod module { use super::*; + use sp_runtime::traits::UniqueSaturatedInto; #[pallet::config] pub trait Config: frame_system::Config { @@ -247,6 +248,13 @@ pub mod module { accumulated_provision_0: Balance, accumulated_provision_1: Balance, }, + /// Triangle trading path and balance. + TriangleTrading { + currency_1: CurrencyId, + currency_2: CurrencyId, + currency_3: CurrencyId, + target_amount: Balance, + }, } /// Liquidity pool for TradingPair. @@ -281,6 +289,14 @@ pub mod module { pub type InitialShareExchangeRates = StorageMap<_, Twox64Concat, TradingPair, (ExchangeRate, ExchangeRate), ValueQuery>; + /// Triangle path of `A-B-C-A`. + /// + /// TriangleTradingPath: map CurrencyA => (CurrencyB, CurrencyC) + #[pallet::storage] + #[pallet::getter(fn triangle_trading_path)] + pub type TriangleTradingPath = + StorageMap<_, Twox64Concat, (CurrencyId, CurrencyId, CurrencyId), (), ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { pub initial_listing_trading_pairs: Vec<(TradingPair, (Balance, Balance), (Balance, Balance), T::BlockNumber)>, @@ -349,7 +365,20 @@ pub mod module { pub struct Pallet(_); #[pallet::hooks] - impl Hooks for Pallet {} + impl Hooks for Pallet { + // TODO: use Offchain worker + fn on_initialize(n: T::BlockNumber) -> Weight { + let keys: Vec<(CurrencyId, CurrencyId, CurrencyId)> = TriangleTradingPath::::iter_keys().collect(); + if keys.len() == 0 { + return 0; + } + let block: u64 = n.unique_saturated_into(); + let index: u64 = block % (keys.len() as u64); + let current: (CurrencyId, CurrencyId, CurrencyId) = keys[index as usize]; + Self::triangle_swap(current); + 0 + } + } #[pallet::call] impl Pallet { @@ -851,6 +880,43 @@ impl Pallet { T::PalletId::get().into_account_truncating() } + /// Triangle swap of `ABCA`, the final A should be large then original A. + fn triangle_swap(current: (CurrencyId, CurrencyId, CurrencyId)) { + // TODO: use configuration + let supply_amount: Balance = 100_000_000_000_000; + let minimum_amount: Balance = 110_000_000_000_000; + + let first_path: Vec = vec![current.0, current.1, current.2]; + if let Some((_, target)) = >::get_swap_amount( + &first_path, + SwapLimit::ExactSupply(supply_amount, 0), + ) { + let second_path: Vec = vec![current.2, current.0]; + if let Some((_, _)) = >::get_swap_amount( + &second_path, + SwapLimit::ExactSupply(target, minimum_amount), + ) { + if let Ok(actual_target) = + Self::do_swap_with_exact_supply(&Self::account_id(), &first_path, supply_amount, 0) + { + if let Ok(target_amount) = Self::do_swap_with_exact_supply( + &Self::account_id(), + &second_path, + actual_target, + minimum_amount, + ) { + Self::deposit_event(Event::TriangleTrading { + currency_1: current.0, + currency_2: current.1, + currency_3: current.2, + target_amount, + }); + } + } + } + } + } + fn try_mutate_liquidity_pool( trading_pair: &TradingPair, f: impl FnOnce((&mut Balance, &mut Balance)) -> sp_std::result::Result, From 87fdce0db63223dcce32c3f23e3613e95e59c541 Mon Sep 17 00:00:00 2001 From: zqh Date: Tue, 21 Jun 2022 00:14:29 +0800 Subject: [PATCH 02/20] offchain wroker --- Cargo.lock | 1 + modules/dex/Cargo.toml | 2 + modules/dex/src/lib.rs | 92 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 82 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df54ff98dc..32611392e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5807,6 +5807,7 @@ dependencies = [ "module-support", "orml-tokens", "orml-traits", + "orml-utilities", "pallet-balances", "parity-scale-codec", "scale-info", diff --git a/modules/dex/Cargo.toml b/modules/dex/Cargo.toml index 2a7611ab5c..132b9d6866 100644 --- a/modules/dex/Cargo.toml +++ b/modules/dex/Cargo.toml @@ -14,6 +14,7 @@ frame-support = { git = "https://github.com/paritytech/substrate", branch = "pol frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } orml-traits = { path = "../../orml/traits", default-features = false } +orml-utilities = { path = "../../orml/utilities", default-features = false } support = { package = "module-support", path = "../support", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } @@ -34,6 +35,7 @@ std = [ "frame-system/std", "sp-std/std", "orml-traits/std", + "orml-utilities/std", "support/std", "primitives/std", ] diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 11db630a25..254c5dd0c7 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -35,6 +35,7 @@ use codec::MaxEncodedLen; use frame_support::{log, pallet_prelude::*, transactional, PalletId}; +use frame_system::offchain::{SendTransactionTypes, SubmitTransaction}; use frame_system::pallet_prelude::*; use orml_traits::{Happened, MultiCurrency, MultiCurrencyExtended}; use primitives::{Balance, CurrencyId, TradingPair}; @@ -52,6 +53,7 @@ mod tests; pub mod weights; pub use module::*; +use orml_utilities::OffchainErr; pub use weights::WeightInfo; /// Parameters of TradingPair in Provisioning status @@ -90,10 +92,9 @@ impl Default for TradingPairStatus { #[frame_support::pallet] pub mod module { use super::*; - use sp_runtime::traits::UniqueSaturatedInto; #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + SendTransactionTypes> { type Event: From> + IsType<::Event>; /// Currency for transfer currencies @@ -366,17 +367,20 @@ pub mod module { #[pallet::hooks] impl Hooks for Pallet { - // TODO: use Offchain worker - fn on_initialize(n: T::BlockNumber) -> Weight { - let keys: Vec<(CurrencyId, CurrencyId, CurrencyId)> = TriangleTradingPath::::iter_keys().collect(); - if keys.len() == 0 { - return 0; + fn offchain_worker(now: T::BlockNumber) { + if let Err(e) = Self::_offchain_worker() { + log::info!( + target: "dex-bot", + "offchain worker: cannot run offchain worker at {:?}: {:?}", + now, e, + ); + } else { + log::debug!( + target: "dex-bot", + "offchain worker: offchain worker start at block: {:?} already done!", + now, + ); } - let block: u64 = n.unique_saturated_into(); - let index: u64 = block % (keys.len() as u64); - let current: (CurrencyId, CurrencyId, CurrencyId) = keys[index as usize]; - Self::triangle_swap(current); - 0 } } @@ -872,6 +876,45 @@ pub mod module { Ok(()) } + + #[pallet::weight(1000)] + #[transactional] + pub fn triangle_swap( + origin: OriginFor, + currency_1: CurrencyId, + currency_2: CurrencyId, + currency_3: CurrencyId, + ) -> DispatchResult { + ensure_none(origin)?; + Self::_triangle_swap((currency_1, currency_2, currency_3)) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::triangle_swap { + currency_1, + currency_2, + currency_3, + } = call + { + ValidTransaction::with_tag_prefix("DexBotOffchainWorker") + // .priority(T::UnsignedPriority::get()) + .and_provides(( + >::block_number(), + currency_1, + currency_2, + currency_3, + )) + .longevity(64_u64) + .propagate(true) + .build() + } else { + InvalidTransaction::Call.into() + } + } } } @@ -880,8 +923,29 @@ impl Pallet { T::PalletId::get().into_account_truncating() } + fn _submit_triangle_swap_tx(currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId) { + let call = Call::::triangle_swap { + currency_1, + currency_2, + currency_3, + }; + if let Err(err) = SubmitTransaction::>::submit_unsigned_transaction(call.into()) { + log::info!( + target: "dex-bot", + "offchain worker: submit unsigned auction A={:?},B={:?},C={:?}, failed: {:?}", + currency_1, currency_2, currency_3, err, + ); + } + } + + fn _offchain_worker() -> Result<(), OffchainErr> { + // find triangle path + // Self::submit_triangle_swap_tx(...); + Ok(()) + } + /// Triangle swap of `ABCA`, the final A should be large then original A. - fn triangle_swap(current: (CurrencyId, CurrencyId, CurrencyId)) { + fn _triangle_swap(current: (CurrencyId, CurrencyId, CurrencyId)) -> DispatchResult { // TODO: use configuration let supply_amount: Balance = 100_000_000_000_000; let minimum_amount: Balance = 110_000_000_000_000; @@ -915,6 +979,8 @@ impl Pallet { } } } + + Ok(()) } fn try_mutate_liquidity_pool( From 30f1c5e018f7b2f1f36e70979cabd208b53e2f55 Mon Sep 17 00:00:00 2001 From: zqh Date: Tue, 21 Jun 2022 17:44:25 +0800 Subject: [PATCH 03/20] use manual triangle info to swap --- modules/aggregated-dex/src/mock.rs | 2 + modules/auction-manager/src/mock.rs | 2 + modules/cdp-engine/src/mock.rs | 2 + modules/cdp-treasury/src/mock.rs | 2 + modules/dex/src/lib.rs | 153 +++++++++++++++++------- modules/evm/src/bench/mock.rs | 2 + modules/transaction-payment/src/mock.rs | 2 + runtime/acala/src/lib.rs | 1 + runtime/common/src/precompile/mock.rs | 2 + runtime/karura/src/lib.rs | 1 + runtime/mandala/src/lib.rs | 1 + 11 files changed, 128 insertions(+), 42 deletions(-) diff --git a/modules/aggregated-dex/src/mock.rs b/modules/aggregated-dex/src/mock.rs index 7fc8e11e3f..11183bc656 100644 --- a/modules/aggregated-dex/src/mock.rs +++ b/modules/aggregated-dex/src/mock.rs @@ -105,6 +105,7 @@ ord_parameter_types! { parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); pub const GetExchangeFee: (u32, u32) = (0, 100); pub EnabledTradingPairs: Vec = vec![]; } @@ -115,6 +116,7 @@ impl module_dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); diff --git a/modules/auction-manager/src/mock.rs b/modules/auction-manager/src/mock.rs index c68d3e15e3..311f85272f 100644 --- a/modules/auction-manager/src/mock.rs +++ b/modules/auction-manager/src/mock.rs @@ -162,6 +162,7 @@ impl PriceProvider for MockPriceSource { parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); pub const GetExchangeFee: (u32, u32) = (0, 100); pub EnabledTradingPairs: Vec = vec![ TradingPair::from_currency_ids(AUSD, BTC).unwrap(), @@ -176,6 +177,7 @@ impl module_dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); diff --git a/modules/cdp-engine/src/mock.rs b/modules/cdp-engine/src/mock.rs index b634d4a486..e171fdb496 100644 --- a/modules/cdp-engine/src/mock.rs +++ b/modules/cdp-engine/src/mock.rs @@ -242,6 +242,7 @@ impl cdp_treasury::Config for Runtime { parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); pub const GetExchangeFee: (u32, u32) = (0, 100); pub EnabledTradingPairs: Vec = vec![ TradingPair::from_currency_ids(AUSD, BTC).unwrap(), @@ -258,6 +259,7 @@ impl dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); diff --git a/modules/cdp-treasury/src/mock.rs b/modules/cdp-treasury/src/mock.rs index 98aedd058e..7b385885ff 100644 --- a/modules/cdp-treasury/src/mock.rs +++ b/modules/cdp-treasury/src/mock.rs @@ -139,6 +139,7 @@ parameter_types! { TradingPair::from_currency_ids(BTC, DOT).unwrap(), ]; pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); } impl module_dex::Config for Runtime { @@ -147,6 +148,7 @@ impl module_dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 254c5dd0c7..7a45299cfa 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -42,7 +42,7 @@ use primitives::{Balance, CurrencyId, TradingPair}; use scale_info::TypeInfo; use sp_core::{H160, U256}; use sp_runtime::{ - traits::{AccountIdConversion, One, Saturating, Zero}, + traits::{AccountIdConversion, One, Saturating, UniqueSaturatedInto, Zero}, ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, SaturatedConversion, }; use sp_std::{prelude::*, vec}; @@ -116,6 +116,10 @@ pub mod module { #[pallet::constant] type PalletId: Get; + /// Treasury account participate in triangle swap. + #[pallet::constant] + type TreasuryPallet: Get; + /// Mapping between CurrencyId and ERC20 address so user can use Erc20 /// address as LP token. type Erc20InfoMapping: Erc20InfoMapping; @@ -254,8 +258,17 @@ pub mod module { currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId, + supply_amount: Balance, target_amount: Balance, }, + /// Add Triangle info. + AddTriangleInfo { + currency_1: CurrencyId, + currency_2: CurrencyId, + currency_3: CurrencyId, + supply_amount: Balance, + threshold: Balance, + }, } /// Liquidity pool for TradingPair. @@ -290,14 +303,19 @@ pub mod module { pub type InitialShareExchangeRates = StorageMap<_, Twox64Concat, TradingPair, (ExchangeRate, ExchangeRate), ValueQuery>; - /// Triangle path of `A-B-C-A`. + /// Triangle path used for offchain arbitrage. /// - /// TriangleTradingPath: map CurrencyA => (CurrencyB, CurrencyC) + /// TriangleTradingPath: map (CurrencyA, CurrencyB, CurrencyC) => () #[pallet::storage] #[pallet::getter(fn triangle_trading_path)] pub type TriangleTradingPath = StorageMap<_, Twox64Concat, (CurrencyId, CurrencyId, CurrencyId), (), ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn triangle_supply_threshold)] + pub type TriangleSupplyThreshold = + StorageMap<_, Twox64Concat, CurrencyId, (Balance, Balance), OptionQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { pub initial_listing_trading_pairs: Vec<(TradingPair, (Balance, Balance), (Balance, Balance), T::BlockNumber)>, @@ -368,16 +386,16 @@ pub mod module { #[pallet::hooks] impl Hooks for Pallet { fn offchain_worker(now: T::BlockNumber) { - if let Err(e) = Self::_offchain_worker() { + if let Err(e) = Self::_offchain_worker(now) { log::info!( target: "dex-bot", - "offchain worker: cannot run offchain worker at {:?}: {:?}", + "offchain worker: cannot run at {:?}: {:?}", now, e, ); } else { log::debug!( target: "dex-bot", - "offchain worker: offchain worker start at block: {:?} already done!", + "offchain worker: start at block: {:?} already done!", now, ); } @@ -886,7 +904,21 @@ pub mod module { currency_3: CurrencyId, ) -> DispatchResult { ensure_none(origin)?; - Self::_triangle_swap((currency_1, currency_2, currency_3)) + Self::do_triangle_swap((currency_1, currency_2, currency_3)) + } + + #[pallet::weight(1000)] + #[transactional] + pub fn set_triangle_swap_info( + origin: OriginFor, + currency_1: CurrencyId, + currency_2: CurrencyId, + currency_3: CurrencyId, + #[pallet::compact] supply_amount: Balance, + #[pallet::compact] threshold: Balance, + ) -> DispatchResult { + T::ListingOrigin::ensure_origin(origin)?; + Self::do_set_triangle_swap_info(currency_1, currency_2, currency_3, supply_amount, threshold) } } @@ -923,7 +955,11 @@ impl Pallet { T::PalletId::get().into_account_truncating() } - fn _submit_triangle_swap_tx(currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId) { + fn treasury_account() -> T::AccountId { + T::TreasuryPallet::get().into_account_truncating() + } + + fn submit_triangle_swap_tx(currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId) { let call = Call::::triangle_swap { currency_1, currency_2, @@ -938,48 +974,81 @@ impl Pallet { } } - fn _offchain_worker() -> Result<(), OffchainErr> { - // find triangle path - // Self::submit_triangle_swap_tx(...); + fn _offchain_worker(now: T::BlockNumber) -> Result<(), OffchainErr> { + let keys: Vec<(CurrencyId, CurrencyId, CurrencyId)> = TriangleTradingPath::::iter_keys().collect(); + if keys.len() == 0 { + // find and store triangle path, or we could manual add trading path by dispatch call. + return Ok(()); + } + + let block: u64 = now.unique_saturated_into(); + let index: u64 = block % (keys.len() as u64); + let current: (CurrencyId, CurrencyId, CurrencyId) = keys[index as usize]; + Self::submit_triangle_swap_tx(current.0, current.1, current.2); + Ok(()) + } + + fn do_set_triangle_swap_info( + currency_1: CurrencyId, + currency_2: CurrencyId, + currency_3: CurrencyId, + supply_amount: Balance, + threshold: Balance, + ) -> DispatchResult { + let currency_tuple = (currency_1, currency_2, currency_3); + TriangleTradingPath::::insert(currency_tuple, ()); + TriangleSupplyThreshold::::try_mutate(currency_1, |maybe_supply_threshold| -> DispatchResult { + *maybe_supply_threshold = Some((supply_amount, threshold)); + Ok(()) + })?; + Self::deposit_event(Event::AddTriangleInfo { + currency_1, + currency_2, + currency_3, + supply_amount, + threshold, + }); Ok(()) } - /// Triangle swap of `ABCA`, the final A should be large then original A. - fn _triangle_swap(current: (CurrencyId, CurrencyId, CurrencyId)) -> DispatchResult { - // TODO: use configuration - let supply_amount: Balance = 100_000_000_000_000; - let minimum_amount: Balance = 110_000_000_000_000; - - let first_path: Vec = vec![current.0, current.1, current.2]; - if let Some((_, target)) = >::get_swap_amount( - &first_path, - SwapLimit::ExactSupply(supply_amount, 0), - ) { + /// Triangle swap of path: `A->B->C->A`, the final A should be large than original A. + fn do_triangle_swap(current: (CurrencyId, CurrencyId, CurrencyId)) -> DispatchResult { + if let Some((supply_amount, minimum_amount)) = TriangleSupplyThreshold::::get(¤t.0) { + let minimum_amount = supply_amount.saturating_add(minimum_amount); + + // A-B-C-A has two kind of swap: A-B/B-C-A or A-B-C/C-A, either one is ok. + let first_path: Vec = vec![current.0, current.1, current.2]; let second_path: Vec = vec![current.2, current.0]; - if let Some((_, _)) = >::get_swap_amount( - &second_path, - SwapLimit::ExactSupply(target, minimum_amount), - ) { - if let Ok(actual_target) = - Self::do_swap_with_exact_supply(&Self::account_id(), &first_path, supply_amount, 0) - { - if let Ok(target_amount) = Self::do_swap_with_exact_supply( - &Self::account_id(), - &second_path, - actual_target, - minimum_amount, - ) { - Self::deposit_event(Event::TriangleTrading { - currency_1: current.0, - currency_2: current.1, - currency_3: current.2, - target_amount, - }); + let supply_1 = SwapLimit::ExactSupply(supply_amount, 0); + + if let Some((_, target_3)) = + >::get_swap_amount(&first_path, supply_1) + { + if let Some((_, _)) = >::get_swap_amount( + &second_path, + SwapLimit::ExactSupply(target_3, minimum_amount), + ) { + if let Ok(target_3) = + Self::do_swap_with_exact_supply(&Self::treasury_account(), &first_path, supply_amount, 0) + { + if let Ok(target_amount) = Self::do_swap_with_exact_supply( + &Self::treasury_account(), + &second_path, + target_3, + minimum_amount, + ) { + Self::deposit_event(Event::TriangleTrading { + currency_1: current.0, + currency_2: current.1, + currency_3: current.2, + supply_amount, + target_amount, + }); + } } } } } - Ok(()) } diff --git a/modules/evm/src/bench/mock.rs b/modules/evm/src/bench/mock.rs index 3bb9d0ef5b..d9bdc2b835 100644 --- a/modules/evm/src/bench/mock.rs +++ b/modules/evm/src/bench/mock.rs @@ -277,6 +277,7 @@ impl DEXIncentives for MockDEXIncentives { parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); } impl module_dex::Config for Runtime { @@ -285,6 +286,7 @@ impl module_dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = MockErc20InfoMapping; type WeightInfo = (); type DEXIncentives = MockDEXIncentives; diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index 4b46e21b89..1a28540ef6 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -167,6 +167,7 @@ ord_parameter_types! { parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); pub const GetExchangeFee: (u32, u32) = (0, 100); pub EnabledTradingPairs: Vec = vec![ TradingPair::from_currency_ids(AUSD, ACA).unwrap(), @@ -181,6 +182,7 @@ impl module_dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 4685ea1a54..60f6274911 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1093,6 +1093,7 @@ impl module_dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; type DEXIncentives = Incentives; type WeightInfo = weights::module_dex::WeightInfo; diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 40c021a538..01b3d74203 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -368,6 +368,7 @@ parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); pub const TradingPathLimit: u32 = 4; pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); } impl module_dex::Config for Test { @@ -376,6 +377,7 @@ impl module_dex::Config for Test { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; type WeightInfo = (); type DEXIncentives = MockDEXIncentives; diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index f8cf420c43..12cddb8d75 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1107,6 +1107,7 @@ impl module_dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; type DEXIncentives = Incentives; type WeightInfo = weights::module_dex::WeightInfo; diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 06400ba7af..7b43bbe4dc 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1152,6 +1152,7 @@ impl module_dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; type DEXIncentives = Incentives; type WeightInfo = weights::module_dex::WeightInfo; From 22f4ff1bde0da5219d5c5e6159e32d303a1ce7ce Mon Sep 17 00:00:00 2001 From: zqh Date: Tue, 21 Jun 2022 19:13:48 +0800 Subject: [PATCH 04/20] fix test --- modules/aggregated-dex/src/mock.rs | 10 ++++++++++ modules/cdp-treasury/src/mock.rs | 10 ++++++++++ modules/dex/src/lib.rs | 2 +- modules/dex/src/mock.rs | 12 ++++++++++++ modules/evm/src/bench/mock.rs | 1 - modules/transaction-payment/src/mock.rs | 11 ++++++++++- runtime/common/src/precompile/mock.rs | 1 - 7 files changed, 43 insertions(+), 4 deletions(-) diff --git a/modules/aggregated-dex/src/mock.rs b/modules/aggregated-dex/src/mock.rs index 11183bc656..8b3fbbff90 100644 --- a/modules/aggregated-dex/src/mock.rs +++ b/modules/aggregated-dex/src/mock.rs @@ -221,6 +221,16 @@ frame_support::construct_runtime!( } ); +pub type Extrinsic = sp_runtime::testing::TestXt; + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} + pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, } diff --git a/modules/cdp-treasury/src/mock.rs b/modules/cdp-treasury/src/mock.rs index 7b385885ff..a8eaa8a4b6 100644 --- a/modules/cdp-treasury/src/mock.rs +++ b/modules/cdp-treasury/src/mock.rs @@ -241,6 +241,16 @@ construct_runtime!( } ); +pub type Extrinsic = sp_runtime::testing::TestXt; + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} + pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, } diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 7a45299cfa..667cdd486c 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -976,7 +976,7 @@ impl Pallet { fn _offchain_worker(now: T::BlockNumber) -> Result<(), OffchainErr> { let keys: Vec<(CurrencyId, CurrencyId, CurrencyId)> = TriangleTradingPath::::iter_keys().collect(); - if keys.len() == 0 { + if keys.is_empty() { // find and store triangle path, or we could manual add trading path by dispatch call. return Ok(()); } diff --git a/modules/dex/src/mock.rs b/modules/dex/src/mock.rs index 69aee2a56d..be4e05d64c 100644 --- a/modules/dex/src/mock.rs +++ b/modules/dex/src/mock.rs @@ -122,6 +122,7 @@ ord_parameter_types! { parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const TreasuryPallet: PalletId = PalletId(*b"aca/trsy"); pub AlternativeSwapPathJointList: Vec> = vec![ vec![DOT], ]; @@ -147,6 +148,7 @@ impl Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<3>; type PalletId = DEXPalletId; + type TreasuryPallet = TreasuryPallet; type Erc20InfoMapping = MockErc20InfoMapping; type WeightInfo = (); type DEXIncentives = MockDEXIncentives; @@ -178,6 +180,16 @@ construct_runtime!( } ); +pub type Extrinsic = sp_runtime::testing::TestXt; + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} + pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, initial_listing_trading_pairs: Vec<(TradingPair, (Balance, Balance), (Balance, Balance), BlockNumber)>, diff --git a/modules/evm/src/bench/mock.rs b/modules/evm/src/bench/mock.rs index d9bdc2b835..67454b0273 100644 --- a/modules/evm/src/bench/mock.rs +++ b/modules/evm/src/bench/mock.rs @@ -277,7 +277,6 @@ impl DEXIncentives for MockDEXIncentives { parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); - pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); } impl module_dex::Config for Runtime { diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index 1a28540ef6..bf86fc676f 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -167,7 +167,6 @@ ord_parameter_types! { parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); - pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); pub const GetExchangeFee: (u32, u32) = (0, 100); pub EnabledTradingPairs: Vec = vec![ TradingPair::from_currency_ids(AUSD, ACA).unwrap(), @@ -316,6 +315,16 @@ construct_runtime!( } ); +pub type Extrinsic = sp_runtime::testing::TestXt; + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} + pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, base_weight: u64, diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 01b3d74203..8bf45afac4 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -368,7 +368,6 @@ parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); pub const TradingPathLimit: u32 = 4; pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); - pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); } impl module_dex::Config for Test { From 1693d7886ca8ff5936ceb410795a8fdfde4e83d9 Mon Sep 17 00:00:00 2001 From: zqh Date: Tue, 21 Jun 2022 20:29:46 +0800 Subject: [PATCH 05/20] fix bench --- modules/evm/src/bench/mock.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/evm/src/bench/mock.rs b/modules/evm/src/bench/mock.rs index 67454b0273..c73c13333f 100644 --- a/modules/evm/src/bench/mock.rs +++ b/modules/evm/src/bench/mock.rs @@ -313,3 +313,13 @@ construct_runtime!( TransactionPayment: module_transaction_payment::{Pallet, Call, Storage, Event}, } ); + +pub type Extrinsic = sp_runtime::testing::TestXt; + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} From a2f74dd6ca89636dab571637f951c4f0abf81333 Mon Sep 17 00:00:00 2001 From: zqh Date: Tue, 21 Jun 2022 20:48:19 +0800 Subject: [PATCH 06/20] add UnsignedPriority --- modules/aggregated-dex/src/mock.rs | 1 + modules/auction-manager/src/mock.rs | 1 + modules/cdp-treasury/src/mock.rs | 1 + modules/dex/src/lib.rs | 5 ++++- modules/evm/src/bench/mock.rs | 1 + modules/transaction-payment/src/mock.rs | 1 + runtime/acala/src/lib.rs | 1 + runtime/common/src/lib.rs | 1 + runtime/common/src/precompile/mock.rs | 1 + runtime/karura/src/lib.rs | 1 + runtime/mandala/src/lib.rs | 1 + 11 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/aggregated-dex/src/mock.rs b/modules/aggregated-dex/src/mock.rs index 8b3fbbff90..b30e4f9ddd 100644 --- a/modules/aggregated-dex/src/mock.rs +++ b/modules/aggregated-dex/src/mock.rs @@ -115,6 +115,7 @@ impl module_dex::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); diff --git a/modules/auction-manager/src/mock.rs b/modules/auction-manager/src/mock.rs index 311f85272f..a615aa9d44 100644 --- a/modules/auction-manager/src/mock.rs +++ b/modules/auction-manager/src/mock.rs @@ -176,6 +176,7 @@ impl module_dex::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); diff --git a/modules/cdp-treasury/src/mock.rs b/modules/cdp-treasury/src/mock.rs index a8eaa8a4b6..e2bb1677e0 100644 --- a/modules/cdp-treasury/src/mock.rs +++ b/modules/cdp-treasury/src/mock.rs @@ -147,6 +147,7 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 667cdd486c..2cd57d3bdd 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -120,6 +120,9 @@ pub mod module { #[pallet::constant] type TreasuryPallet: Get; + #[pallet::constant] + type UnsignedPriority: Get; + /// Mapping between CurrencyId and ERC20 address so user can use Erc20 /// address as LP token. type Erc20InfoMapping: Erc20InfoMapping; @@ -933,7 +936,7 @@ pub mod module { } = call { ValidTransaction::with_tag_prefix("DexBotOffchainWorker") - // .priority(T::UnsignedPriority::get()) + .priority(T::UnsignedPriority::get()) .and_provides(( >::block_number(), currency_1, diff --git a/modules/evm/src/bench/mock.rs b/modules/evm/src/bench/mock.rs index c73c13333f..073977fa0e 100644 --- a/modules/evm/src/bench/mock.rs +++ b/modules/evm/src/bench/mock.rs @@ -284,6 +284,7 @@ impl module_dex::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = MockErc20InfoMapping; diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index bf86fc676f..7e6865c7b3 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -180,6 +180,7 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 60f6274911..863a4659d6 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1092,6 +1092,7 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 655232c131..32c4017b4d 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -88,6 +88,7 @@ parameter_types! { pub CdpEngineUnsignedPriority: TransactionPriority = MinOperationalPriority::get() - 1000; pub AuctionManagerUnsignedPriority: TransactionPriority = MinOperationalPriority::get() - 2000; pub RenvmBridgeUnsignedPriority: TransactionPriority = MinOperationalPriority::get() - 3000; + pub DexTriangleSwapUnsignedPriority: TransactionPriority = MinOperationalPriority::get() - 4000; } /// The call is allowed only if caller is a system contract. diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 8bf45afac4..1087f9868d 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -375,6 +375,7 @@ impl module_dex::Config for Test { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 12cddb8d75..38bd6f2be6 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1108,6 +1108,7 @@ impl module_dex::Config for Runtime { type TradingPathLimit = TradingPathLimit; type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; + type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type Erc20InfoMapping = EvmErc20InfoMapping; type DEXIncentives = Incentives; type WeightInfo = weights::module_dex::WeightInfo; diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 7b43bbe4dc..9a2b26867f 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1151,6 +1151,7 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; From 464b04e69d6e79ea37962921c4c5781a305bd712 Mon Sep 17 00:00:00 2001 From: zqh Date: Wed, 22 Jun 2022 09:25:33 +0800 Subject: [PATCH 07/20] offchain compute path via two loop --- modules/dex/src/lib.rs | 60 +++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 2cd57d3bdd..3297ad0856 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -89,6 +89,12 @@ impl Default for TradingPairStatus { } } +impl TradingPairStatus { + pub fn enabled(&self) -> bool { + matches!(self, TradingPairStatus::<_, _>::Enabled) + } +} + #[frame_support::pallet] pub mod module { use super::*; @@ -265,7 +271,7 @@ pub mod module { target_amount: Balance, }, /// Add Triangle info. - AddTriangleInfo { + AddTriangleSwapInfo { currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId, @@ -794,10 +800,7 @@ pub mod module { let trading_pair = TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; ensure!( - matches!( - Self::trading_pair_statuses(trading_pair), - TradingPairStatus::<_, _>::Enabled - ), + Self::trading_pair_statuses(trading_pair).enabled(), Error::::MustBeEnabled ); @@ -1004,7 +1007,7 @@ impl Pallet { *maybe_supply_threshold = Some((supply_amount, threshold)); Ok(()) })?; - Self::deposit_event(Event::AddTriangleInfo { + Self::deposit_event(Event::AddTriangleSwapInfo { currency_1, currency_2, currency_3, @@ -1055,6 +1058,36 @@ impl Pallet { Ok(()) } + fn _compute_triangle_trading_path() { + use sp_std::collections::btree_map::BTreeMap; + let enabled_trading_pair: Vec = TradingPairStatuses::::iter() + .filter(|(_, status)| status.enabled()) + .map(|(pair, _)| pair) + .collect(); + // let uniq_currencies = enabled_trading_pair.iter().cloned().map(|pair| pair.first()) + // .collect::>(); + let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); + for pair in enabled_trading_pair.clone() { + trading_pair_values_map + .entry(pair.first()) + .or_insert_with(Vec::::new) + .push(pair.second()); + } + let mut final_triangle_path = Vec::<(CurrencyId, CurrencyId, CurrencyId)>::new(); + trading_pair_values_map.into_iter().for_each(|(start, v)| { + let len = v.len(); + for i in 0..(len - 1) { + for j in (i + 1)..len { + if let Some(pair) = TradingPair::from_currency_ids(v[i], v[j]) { + if enabled_trading_pair.contains(&pair) { + final_triangle_path.push((start, pair.first(), pair.second())); + } + } + } + } + }); + } + fn try_mutate_liquidity_pool( trading_pair: &TradingPair, f: impl FnOnce((&mut Balance, &mut Balance)) -> sp_std::result::Result, @@ -1205,10 +1238,7 @@ impl Pallet { let trading_pair = TradingPair::from_currency_ids(currency_id_a, currency_id_b).ok_or(Error::::InvalidCurrencyId)?; ensure!( - matches!( - Self::trading_pair_statuses(trading_pair), - TradingPairStatus::<_, _>::Enabled - ), + Self::trading_pair_statuses(trading_pair).enabled(), Error::::MustBeEnabled, ); @@ -1449,10 +1479,7 @@ impl Pallet { let trading_pair = TradingPair::from_currency_ids(path[i], path[i + 1]).ok_or(Error::::InvalidCurrencyId)?; ensure!( - matches!( - Self::trading_pair_statuses(trading_pair), - TradingPairStatus::<_, _>::Enabled - ), + Self::trading_pair_statuses(trading_pair).enabled(), Error::::MustBeEnabled ); let (supply_pool, target_pool) = Self::get_liquidity(path[i], path[i + 1]); @@ -1485,10 +1512,7 @@ impl Pallet { let trading_pair = TradingPair::from_currency_ids(path[i - 1], path[i]).ok_or(Error::::InvalidCurrencyId)?; ensure!( - matches!( - Self::trading_pair_statuses(trading_pair), - TradingPairStatus::<_, _>::Enabled - ), + Self::trading_pair_statuses(trading_pair).enabled(), Error::::MustBeEnabled ); let (supply_pool, target_pool) = Self::get_liquidity(path[i - 1], path[i]); From deb624825d4164406823482ace61edb7511373af Mon Sep 17 00:00:00 2001 From: zqh Date: Tue, 26 Jul 2022 10:56:56 +0800 Subject: [PATCH 08/20] ignore duplicate path --- modules/auction-manager/src/lib.rs | 1 - modules/dex/src/lib.rs | 74 ++++++++++++++++++++---------- modules/dex/src/mock.rs | 1 + 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/modules/auction-manager/src/lib.rs b/modules/auction-manager/src/lib.rs index a5f4914136..5c8d4e4526 100644 --- a/modules/auction-manager/src/lib.rs +++ b/modules/auction-manager/src/lib.rs @@ -350,7 +350,6 @@ impl Pallet { let lock_expiration = Duration::from_millis(LOCK_DURATION); let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); let mut guard = lock.try_lock().map_err(|_| OffchainErr::OffchainLock)?; - let mut to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); // get to_be_continue record, diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 3297ad0856..552bc868b7 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -198,6 +198,10 @@ pub mod module { NotAllowedRefund, /// Cannot swap CannotSwap, + /// Triangle swap info exist. + TriangleSwapInfoExist, + /// Triangle swap info is invalid. + TriangleSwapInfoInvalid, } #[pallet::event] @@ -1001,8 +1005,27 @@ impl Pallet { supply_amount: Balance, threshold: Balance, ) -> DispatchResult { - let currency_tuple = (currency_1, currency_2, currency_3); - TriangleTradingPath::::insert(currency_tuple, ()); + ensure!(supply_amount > threshold, Error::::TriangleSwapInfoInvalid); + let tuple = (currency_1, currency_2, currency_3); + ensure!( + !TriangleTradingPath::::contains_key(tuple), + Error::::TriangleSwapInfoExist + ); + // A-B-C-A has other variant, should ignore it if we already have A-B-C-A + let duplicate_currencies = vec![ + (currency_1, currency_3, currency_2), + (currency_2, currency_3, currency_1), + (currency_2, currency_1, currency_3), + (currency_3, currency_1, currency_2), + (currency_3, currency_2, currency_1), + ]; + // error if input is (A,B,C) variant. i.e. (A,C,B),(B,A,C) etc. + ensure!( + !duplicate_currencies.contains(&tuple), + Error::::TriangleSwapInfoInvalid + ); + TriangleTradingPath::::insert(tuple, ()); + TriangleSupplyThreshold::::try_mutate(currency_1, |maybe_supply_threshold| -> DispatchResult { *maybe_supply_threshold = Some((supply_amount, threshold)); Ok(()) @@ -1017,7 +1040,7 @@ impl Pallet { Ok(()) } - /// Triangle swap of path: `A->B->C->A`, the final A should be large than original A. + /// Triangle swap of path: `A->B->C->A`, the final output should be large than input. fn do_triangle_swap(current: (CurrencyId, CurrencyId, CurrencyId)) -> DispatchResult { if let Some((supply_amount, minimum_amount)) = TriangleSupplyThreshold::::get(¤t.0) { let minimum_amount = supply_amount.saturating_add(minimum_amount); @@ -1027,6 +1050,7 @@ impl Pallet { let second_path: Vec = vec![current.2, current.0]; let supply_1 = SwapLimit::ExactSupply(supply_amount, 0); + let mut valid_swap = false; if let Some((_, target_3)) = >::get_swap_amount(&first_path, supply_1) { @@ -1034,24 +1058,28 @@ impl Pallet { &second_path, SwapLimit::ExactSupply(target_3, minimum_amount), ) { - if let Ok(target_3) = - Self::do_swap_with_exact_supply(&Self::treasury_account(), &first_path, supply_amount, 0) - { - if let Ok(target_amount) = Self::do_swap_with_exact_supply( - &Self::treasury_account(), - &second_path, - target_3, - minimum_amount, - ) { - Self::deposit_event(Event::TriangleTrading { - currency_1: current.0, - currency_2: current.1, - currency_3: current.2, - supply_amount, - target_amount, - }); - } - } + valid_swap = true; + } + } + if !valid_swap { + return Ok(()); + } + + if let Ok((_, target_3)) = >::swap_with_specific_path( + &Self::treasury_account(), + &first_path, + supply_1, + ) { + if let Ok(target_amount) = + Self::do_swap_with_exact_supply(&Self::treasury_account(), &second_path, target_3, minimum_amount) + { + Self::deposit_event(Event::TriangleTrading { + currency_1: current.0, + currency_2: current.1, + currency_3: current.2, + supply_amount, + target_amount, + }); } } } @@ -1064,8 +1092,7 @@ impl Pallet { .filter(|(_, status)| status.enabled()) .map(|(pair, _)| pair) .collect(); - // let uniq_currencies = enabled_trading_pair.iter().cloned().map(|pair| pair.first()) - // .collect::>(); + // if (A,B),(A,C),(A,D) are trading pair, then (A, [B,C,D]) is put into map. let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); for pair in enabled_trading_pair.clone() { trading_pair_values_map @@ -1076,6 +1103,7 @@ impl Pallet { let mut final_triangle_path = Vec::<(CurrencyId, CurrencyId, CurrencyId)>::new(); trading_pair_values_map.into_iter().for_each(|(start, v)| { let len = v.len(); + // for each A, validate if two of [B,C,D] can be form triangle path. for i in 0..(len - 1) { for j in (i + 1)..len { if let Some(pair) = TradingPair::from_currency_ids(v[i], v[j]) { diff --git a/modules/dex/src/mock.rs b/modules/dex/src/mock.rs index be4e05d64c..2be5410dae 100644 --- a/modules/dex/src/mock.rs +++ b/modules/dex/src/mock.rs @@ -149,6 +149,7 @@ impl Config for Runtime { type TradingPathLimit = ConstU32<3>; type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPallet; + type UnsignedPriority = ConstU64<1048576>; type Erc20InfoMapping = MockErc20InfoMapping; type WeightInfo = (); type DEXIncentives = MockDEXIncentives; From 2f97b130ca8a30a5f0a5ec3beb4342056ca5f801 Mon Sep 17 00:00:00 2001 From: zqh Date: Mon, 1 Aug 2022 23:40:23 +0800 Subject: [PATCH 09/20] triangle offchain work --- Cargo.lock | 1 + modules/aggregated-dex/src/mock.rs | 2 + modules/auction-manager/src/mock.rs | 2 + modules/cdp-engine/src/mock.rs | 2 + modules/cdp-treasury/src/mock.rs | 2 + modules/dex/Cargo.toml | 13 +- modules/dex/src/lib.rs | 196 ++++++++++++++++++------ modules/dex/src/mock.rs | 2 + modules/dex/src/tests.rs | 179 +++++++++++++++++++++- modules/evm/src/bench/mock.rs | 2 + modules/transaction-payment/src/mock.rs | 2 + runtime/acala/src/lib.rs | 3 + runtime/common/src/precompile/mock.rs | 2 + runtime/karura/src/lib.rs | 3 + runtime/mandala/src/lib.rs | 3 + 15 files changed, 362 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f1422b42b..1a6dbed6ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5679,6 +5679,7 @@ dependencies = [ "orml-utilities", "pallet-balances", "parity-scale-codec", + "parking_lot 0.12.1", "scale-info", "serde", "sp-core", diff --git a/modules/aggregated-dex/src/mock.rs b/modules/aggregated-dex/src/mock.rs index 76e343e444..5f5d46f582 100644 --- a/modules/aggregated-dex/src/mock.rs +++ b/modules/aggregated-dex/src/mock.rs @@ -116,6 +116,8 @@ impl module_dex::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = ConstU64<1>; type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; diff --git a/modules/auction-manager/src/mock.rs b/modules/auction-manager/src/mock.rs index a615aa9d44..d42106385c 100644 --- a/modules/auction-manager/src/mock.rs +++ b/modules/auction-manager/src/mock.rs @@ -176,6 +176,8 @@ impl module_dex::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = ConstU64<1>; type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; diff --git a/modules/cdp-engine/src/mock.rs b/modules/cdp-engine/src/mock.rs index e171fdb496..834009ff2d 100644 --- a/modules/cdp-engine/src/mock.rs +++ b/modules/cdp-engine/src/mock.rs @@ -258,6 +258,8 @@ impl dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = ConstU64<1>; type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); diff --git a/modules/cdp-treasury/src/mock.rs b/modules/cdp-treasury/src/mock.rs index 9db85f451d..61d0a38687 100644 --- a/modules/cdp-treasury/src/mock.rs +++ b/modules/cdp-treasury/src/mock.rs @@ -147,6 +147,8 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = ConstU64<1>; type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; diff --git a/modules/dex/Cargo.toml b/modules/dex/Cargo.toml index 3a00b3414e..aac488fc0f 100644 --- a/modules/dex/Cargo.toml +++ b/modules/dex/Cargo.toml @@ -8,21 +8,24 @@ edition = "2021" serde = { version = "1.0.136", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } scale-info = { version = "2.1", default-features = false, features = ["derive"] } + +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } + orml-traits = { path = "../../orml/traits", default-features = false } orml-utilities = { path = "../../orml/utilities", default-features = false } + support = { package = "module-support", path = "../support", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } [dev-dependencies] orml-tokens = { path = "../../orml/tokens" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } +parking_lot = { version = "0.12.0" } [features] default = ["std"] @@ -30,10 +33,12 @@ std = [ "serde", "codec/std", "scale-info/std", + "sp-io/std", + "sp-core/std", "sp-runtime/std", + "sp-std/std", "frame-support/std", "frame-system/std", - "sp-std/std", "orml-traits/std", "orml-utilities/std", "support/std", diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index 552bc868b7..d784c8ddbb 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -35,17 +35,24 @@ use codec::MaxEncodedLen; use frame_support::{log, pallet_prelude::*, transactional, PalletId}; -use frame_system::offchain::{SendTransactionTypes, SubmitTransaction}; -use frame_system::pallet_prelude::*; +use frame_system::{ + offchain::{SendTransactionTypes, SubmitTransaction}, + pallet_prelude::*, +}; use orml_traits::{Happened, MultiCurrency, MultiCurrencyExtended}; use primitives::{Balance, CurrencyId, TradingPair}; use scale_info::TypeInfo; use sp_core::{H160, U256}; use sp_runtime::{ - traits::{AccountIdConversion, One, Saturating, UniqueSaturatedInto, Zero}, + offchain::{ + storage::StorageValueRef, + storage_lock::{StorageLock, Time}, + Duration, + }, + traits::{AccountIdConversion, One, Saturating, Zero}, ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, SaturatedConversion, }; -use sp_std::{prelude::*, vec}; +use sp_std::{collections::btree_map::BTreeMap, prelude::*, vec}; use support::{DEXIncentives, DEXManager, Erc20InfoMapping, ExchangeRate, Ratio, SwapLimit}; mod mock; @@ -56,6 +63,12 @@ pub use module::*; use orml_utilities::OffchainErr; pub use weights::WeightInfo; +pub const OFFCHAIN_WORKER_DATA: &[u8] = b"acala/dex-bot/data/"; +pub const OFFCHAIN_WORKER_LOCK: &[u8] = b"acala/dex-bot/lock/"; +pub const OFFCHAIN_WORKER_MAX_ITERATIONS: &[u8] = b"acala/dex-bot/max-iterations/"; +pub const LOCK_DURATION: u64 = 100; +pub const DEFAULT_MAX_ITERATIONS: u32 = 100; + /// Parameters of TradingPair in Provisioning status #[derive(Encode, Decode, Clone, Copy, RuntimeDebug, PartialEq, Eq, MaxEncodedLen, TypeInfo)] pub struct ProvisioningParameters { @@ -118,6 +131,14 @@ pub mod module { #[pallet::constant] type TradingPathLimit: Get; + /// The limit of one Currency to all other direct trading token. + #[pallet::constant] + type SingleTokenTradingLimit: Get; + + /// The frequency block number to update `TradingPairNodes`. + #[pallet::constant] + type TradingKeysUpdateFrequency: Get; + /// The DEX's module id, keep all assets in DEX. #[pallet::constant] type PalletId: Get; @@ -316,6 +337,14 @@ pub mod module { pub type InitialShareExchangeRates = StorageMap<_, Twox64Concat, TradingPair, (ExchangeRate, ExchangeRate), ValueQuery>; + /// Direct trading pair token list of specify CurrencyId. + /// + /// TradingPairNodes: map CurrencyId => vec![CurrencyId] + #[pallet::storage] + #[pallet::getter(fn trading_pair_nodes)] + pub type TradingPairNodes = + StorageMap<_, Twox64Concat, CurrencyId, BoundedVec, ValueQuery>; + /// Triangle path used for offchain arbitrage. /// /// TriangleTradingPath: map (CurrencyA, CurrencyB, CurrencyC) => () @@ -969,6 +998,10 @@ impl Pallet { T::TreasuryPallet::get().into_account_truncating() } + pub fn get_trading_pair_keys() -> Vec { + TradingPairNodes::::iter_keys().collect() + } + fn submit_triangle_swap_tx(currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId) { let call = Call::::triangle_swap { currency_1, @@ -978,23 +1011,126 @@ impl Pallet { if let Err(err) = SubmitTransaction::>::submit_unsigned_transaction(call.into()) { log::info!( target: "dex-bot", - "offchain worker: submit unsigned auction A={:?},B={:?},C={:?}, failed: {:?}", + "offchain worker: submit unsigned swap A={:?},B={:?},C={:?}, failed: {:?}", currency_1, currency_2, currency_3, err, ); } } fn _offchain_worker(now: T::BlockNumber) -> Result<(), OffchainErr> { - let keys: Vec<(CurrencyId, CurrencyId, CurrencyId)> = TriangleTradingPath::::iter_keys().collect(); - if keys.is_empty() { - // find and store triangle path, or we could manual add trading path by dispatch call. - return Ok(()); + // let trading_currency_ids = Self::get_trading_pair_keys(); + // if trading_currency_ids.len().is_zero() { + // return Ok(()); + // } + + // acquire offchain worker lock + let lock_expiration = Duration::from_millis(LOCK_DURATION); + let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); + let mut guard = lock.try_lock().map_err(|_| OffchainErr::OffchainLock)?; + // get the max iterations config + let max_iterations = StorageValueRef::persistent(OFFCHAIN_WORKER_MAX_ITERATIONS) + .get::() + .unwrap_or(Some(DEFAULT_MAX_ITERATIONS)) + .unwrap_or(DEFAULT_MAX_ITERATIONS); + let mut to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); + + // update `TradingPairNodes` every `TradingKeysUpdateFrequency` interval. + if now % T::TradingKeysUpdateFrequency::get() == Zero::zero() { + let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); + TradingPairStatuses::::iter() + .filter(|(_, status)| status.enabled()) + .for_each(|(pair, _)| { + trading_pair_values_map + .entry(pair.first()) + .or_insert_with(Vec::::new) + .push(pair.second()); + }); + for (currency_id, trading_tokens) in trading_pair_values_map { + let _ = TradingPairNodes::::try_mutate(currency_id, |maybe_trading_tokens| -> DispatchResult { + let trading_tokens: Result, _> = + trading_tokens.try_into(); + match trading_tokens { + Ok(trading_tokens) => { + *maybe_trading_tokens = trading_tokens; + } + _ => { + log::debug!("Too many trading pair for token:{:?}.", currency_id); + } + } + Ok(()) + }); + } + } + + let start_key = to_be_continue.get::>().unwrap_or_default(); + let mut iterator = match start_key.clone() { + Some(key) => TradingPairNodes::::iter_from(key), + None => TradingPairNodes::::iter(), + }; + + let mut finished = true; + let mut iteration_count = 0; + let iteration_start_time = sp_io::offchain::timestamp(); + + let enabled_trading_pair: Vec = TradingPairStatuses::::iter() + .filter(|(_, status)| status.enabled()) + .map(|(pair, _)| pair) + .collect(); + + #[allow(clippy::while_let_on_iterator)] + 'outer: while let Some((currency_id, trading_tokens)) = iterator.next() { + let len = trading_tokens.len(); + if len < 2 { + continue; + } + for i in 0..(len - 1) { + for j in (i + 1)..len { + iteration_count += 1; + + if let Some(pair) = TradingPair::from_currency_ids(trading_tokens[i], trading_tokens[j]) { + if !enabled_trading_pair.contains(&pair) { + continue; + } + + Self::submit_triangle_swap_tx(currency_id, pair.first(), pair.second()); + } + + // inner iterator consider as iterations too. + if iteration_count == max_iterations { + finished = false; + break 'outer; + } + + // extend offchain worker lock + guard.extend_lock().map_err(|_| OffchainErr::OffchainLock)?; + } + } + } + + let iteration_end_time = sp_io::offchain::timestamp(); + log::debug!( + target: "dex offchain worker", + "iteration info:\n max iterations is {:?}\n start key: {:?}, iterate count: {:?}\n iteration start at: {:?}, end at: {:?}, execution time: {:?}\n", + max_iterations, + // currency_id, + start_key, + iteration_count, + iteration_start_time, + iteration_end_time, + iteration_end_time.diff(&iteration_start_time) + ); + + // if iteration for map storage finished, clear to be continue record + // otherwise, update to be continue record + if finished { + to_be_continue.clear(); + } else { + to_be_continue.set(&iterator.last_raw_key()); } - let block: u64 = now.unique_saturated_into(); - let index: u64 = block % (keys.len() as u64); - let current: (CurrencyId, CurrencyId, CurrencyId) = keys[index as usize]; - Self::submit_triangle_swap_tx(current.0, current.1, current.2); + // Consume the guard but **do not** unlock the underlying lock. + guard.forget(); + Ok(()) } @@ -1005,7 +1141,7 @@ impl Pallet { supply_amount: Balance, threshold: Balance, ) -> DispatchResult { - ensure!(supply_amount > threshold, Error::::TriangleSwapInfoInvalid); + ensure!(threshold > supply_amount, Error::::TriangleSwapInfoInvalid); let tuple = (currency_1, currency_2, currency_3); ensure!( !TriangleTradingPath::::contains_key(tuple), @@ -1043,7 +1179,7 @@ impl Pallet { /// Triangle swap of path: `A->B->C->A`, the final output should be large than input. fn do_triangle_swap(current: (CurrencyId, CurrencyId, CurrencyId)) -> DispatchResult { if let Some((supply_amount, minimum_amount)) = TriangleSupplyThreshold::::get(¤t.0) { - let minimum_amount = supply_amount.saturating_add(minimum_amount); + // let minimum_amount = supply_amount.saturating_add(minimum_amount); // A-B-C-A has two kind of swap: A-B/B-C-A or A-B-C/C-A, either one is ok. let first_path: Vec = vec![current.0, current.1, current.2]; @@ -1086,36 +1222,6 @@ impl Pallet { Ok(()) } - fn _compute_triangle_trading_path() { - use sp_std::collections::btree_map::BTreeMap; - let enabled_trading_pair: Vec = TradingPairStatuses::::iter() - .filter(|(_, status)| status.enabled()) - .map(|(pair, _)| pair) - .collect(); - // if (A,B),(A,C),(A,D) are trading pair, then (A, [B,C,D]) is put into map. - let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); - for pair in enabled_trading_pair.clone() { - trading_pair_values_map - .entry(pair.first()) - .or_insert_with(Vec::::new) - .push(pair.second()); - } - let mut final_triangle_path = Vec::<(CurrencyId, CurrencyId, CurrencyId)>::new(); - trading_pair_values_map.into_iter().for_each(|(start, v)| { - let len = v.len(); - // for each A, validate if two of [B,C,D] can be form triangle path. - for i in 0..(len - 1) { - for j in (i + 1)..len { - if let Some(pair) = TradingPair::from_currency_ids(v[i], v[j]) { - if enabled_trading_pair.contains(&pair) { - final_triangle_path.push((start, pair.first(), pair.second())); - } - } - } - } - }); - } - fn try_mutate_liquidity_pool( trading_pair: &TradingPair, f: impl FnOnce((&mut Balance, &mut Balance)) -> sp_std::result::Result, diff --git a/modules/dex/src/mock.rs b/modules/dex/src/mock.rs index 2be5410dae..2b24fe532a 100644 --- a/modules/dex/src/mock.rs +++ b/modules/dex/src/mock.rs @@ -147,6 +147,8 @@ impl Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<3>; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = ConstU64<1>; type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPallet; type UnsignedPriority = ConstU64<1048576>; diff --git a/modules/dex/src/tests.rs b/modules/dex/src/tests.rs index 7cec53b86b..a78dd7a60d 100644 --- a/modules/dex/src/tests.rs +++ b/modules/dex/src/tests.rs @@ -23,15 +23,32 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use mock::{ - ACAJointSwap, AUSDBTCPair, AUSDDOTPair, AUSDJointSwap, DOTBTCPair, DexModule, Event, ExtBuilder, ListingOrigin, - Origin, Runtime, System, Tokens, ACA, ALICE, AUSD, AUSD_DOT_POOL_RECORD, BOB, BTC, CAROL, DOT, + ACAJointSwap, AUSDBTCPair, AUSDDOTPair, AUSDJointSwap, Call as MockCall, DOTBTCPair, DexModule, Event, ExtBuilder, + ListingOrigin, Origin, Runtime, System, Tokens, ACA, ALICE, AUSD, AUSD_DOT_POOL_RECORD, BOB, BTC, CAROL, DOT, *, }; use orml_traits::MultiReservableCurrency; -use sp_core::H160; +use parking_lot::RwLock; +use sp_core::{ + offchain::{ + testing, testing::PoolState, DbExternalities, OffchainDbExt, OffchainWorkerExt, StorageKind, TransactionPoolExt, + }, + H160, +}; +use sp_io::offchain; use sp_runtime::traits::BadOrigin; use std::str::FromStr; +use std::sync::Arc; use support::{Swap, SwapError}; +fn run_to_block_offchain(n: u64) { + while System::block_number() < n { + System::set_block_number(System::block_number() + 1); + DexModule::offchain_worker(System::block_number()); + // this unlocks the concurrency storage lock so offchain_worker will fire next block + offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(LOCK_DURATION + 200))); + } +} + #[test] fn list_provisioning_work() { ExtBuilder::default().build().execute_with(|| { @@ -1970,3 +1987,159 @@ fn specific_joint_swap_work() { ); }); } + +#[test] +fn offchain_worker_max_iteration_works() { + let (mut offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::default() + .initialize_enabled_trading_pairs() + .initialize_added_liquidity_pools(ALICE) + .build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + + ext.execute_with(|| { + System::set_block_number(1); + let keys: Vec = TradingPairNodes::::iter_keys().collect(); + assert_eq!(keys, vec![]); + + run_to_block_offchain(2); + // initialize `TradingPairNodes` + let keys: Vec = TradingPairNodes::::iter_keys().collect(); + assert_eq!(keys, vec![DOT, AUSD]); + + // trigger unsigned tx + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::DexModule(crate::Call::triangle_swap { + currency_1, + currency_2, + currency_3, + }) = tx.call + { + assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + assert_ok!(DexModule::triangle_swap( + Origin::none(), + currency_1, + currency_2, + currency_3 + )); + } + assert!(pool_state.write().transactions.pop().is_none()); + + let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); + let start_key = to_be_continue.get::>().unwrap_or_default(); + assert_eq!(start_key, None); + + // sets max iterations value to 1 + offchain.local_storage_set(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS, &1u32.encode()); + run_to_block_offchain(3); + let keys: Vec = TradingPairNodes::::iter_keys().collect(); + assert_eq!(keys, vec![DOT, AUSD]); + + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::DexModule(crate::Call::triangle_swap { + currency_1, + currency_2, + currency_3, + }) = tx.call + { + assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + assert_ok!(DexModule::triangle_swap( + Origin::none(), + currency_1, + currency_2, + currency_3 + )); + } + assert!(pool_state.write().transactions.pop().is_none()); + + // iterator last_saw_key + let mut iter = TradingPairNodes::::iter(); + let _ = iter.next(); // first currency is DOT + let _ = iter.next(); // second one is AUSD + let last_saw_key = iter.last_raw_key(); + + let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); + let start_key = to_be_continue.get::>().unwrap_or_default(); + assert_eq!(start_key, Some(last_saw_key.to_vec())); + }); +} + +#[test] +fn offchain_worker_trigger_unsigned_triangle_swap() { + let (offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::default() + .initialize_enabled_trading_pairs() + .initialize_added_liquidity_pools(ALICE) + .build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + + ext.execute_with(|| { + System::set_block_number(1); + + // set swap supply and threshold + assert_ok!(DexModule::set_triangle_swap_info( + Origin::signed(ListingOrigin::get()), + AUSD, + DOT, + BTC, + 1000, + 1900, + )); + let supply_threshold = TriangleSupplyThreshold::::get(AUSD).unwrap(); + assert_eq!(supply_threshold, (1000, 1900)); + assert_ok!(Tokens::update_balance( + AUSD, + &Pallet::::treasury_account(), + 1_000_000_000_000_000i128 + )); + + trigger_unsigned_triangle_swap(2, pool_state.clone(), Some(1930)); + trigger_unsigned_triangle_swap(3, pool_state.clone(), Some(1911)); + trigger_unsigned_triangle_swap(4, pool_state.clone(), None); + }); + + fn trigger_unsigned_triangle_swap(n: u64, pool_state: Arc>, actual_target_amount: Option) { + System::reset_events(); + run_to_block_offchain(n); + let keys: Vec = TradingPairNodes::::iter_keys().collect(); + assert_eq!(keys, vec![DOT, AUSD]); + + // trigger unsigned tx + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::DexModule(crate::Call::triangle_swap { + currency_1, + currency_2, + currency_3, + }) = tx.call + { + assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + assert_ok!(DexModule::triangle_swap( + Origin::none(), + currency_1, + currency_2, + currency_3 + )); + } + assert!(pool_state.write().transactions.pop().is_none()); + + // if target amount is less than threshold, then triangle swap not triggered. + if let Some(target_amount) = actual_target_amount { + System::assert_last_event(Event::DexModule(crate::Event::TriangleTrading { + currency_1: AUSD, + currency_2: DOT, + currency_3: BTC, + supply_amount: 1000, + target_amount, + })); + } + } +} diff --git a/modules/evm/src/bench/mock.rs b/modules/evm/src/bench/mock.rs index e68d9b031d..e5ba0fbaf0 100644 --- a/modules/evm/src/bench/mock.rs +++ b/modules/evm/src/bench/mock.rs @@ -287,6 +287,8 @@ impl module_dex::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = ConstU64<1>; type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index e2efa6238c..70b09d174b 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -185,6 +185,8 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = ConstU64<1>; type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 3914b7d1e9..0cd48f127d 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1087,6 +1087,7 @@ impl module_emergency_shutdown::Config for Runtime { parameter_types! { pub const GetExchangeFee: (u32, u32) = (3, 1000); // 0.3% pub const ExtendedProvisioningBlocks: BlockNumber = 2 * DAYS; + pub const TradingKeysUpdateFrequency: BlockNumber = 2 * DAYS; pub const TradingPathLimit: u32 = 4; } @@ -1095,6 +1096,8 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index fa4270f792..6b96ee5a46 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -375,6 +375,8 @@ impl module_dex::Config for Test { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = ConstU64<1>; type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 7ec717ddb8..1c476308b7 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1093,6 +1093,7 @@ impl module_emergency_shutdown::Config for Runtime { parameter_types! { pub const GetExchangeFee: (u32, u32) = (3, 1000); // 0.3% pub const ExtendedProvisioningBlocks: BlockNumber = 2 * DAYS; + pub const TradingKeysUpdateFrequency: BlockNumber = 2 * DAYS; pub const TradingPathLimit: u32 = 4; } @@ -1101,6 +1102,8 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index b044282d0a..da5b1780be 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1146,6 +1146,7 @@ parameter_types! { TradingPair::from_currency_ids(DOT, ACA).unwrap(), ]; pub const ExtendedProvisioningBlocks: BlockNumber = 2 * DAYS; + pub const TradingKeysUpdateFrequency: BlockNumber = 2 * DAYS; pub const TradingPathLimit: u32 = 4; pub AlternativeSwapPathJointList: Vec> = vec![ vec![GetStakingCurrencyId::get()], @@ -1159,6 +1160,8 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; From da9d095ec91a5a9a3e6a8d27cf4b62b3c3c36118 Mon Sep 17 00:00:00 2001 From: zqh Date: Mon, 1 Aug 2022 23:58:07 +0800 Subject: [PATCH 10/20] clean and fix type --- modules/dex/src/lib.rs | 72 ++++----------------------- modules/dex/src/tests.rs | 8 ++- modules/evm/src/bench/mock.rs | 2 +- runtime/common/src/precompile/mock.rs | 2 +- 4 files changed, 19 insertions(+), 65 deletions(-) diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index d784c8ddbb..a5f55a6abf 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -219,8 +219,6 @@ pub mod module { NotAllowedRefund, /// Cannot swap CannotSwap, - /// Triangle swap info exist. - TriangleSwapInfoExist, /// Triangle swap info is invalid. TriangleSwapInfoInvalid, } @@ -296,10 +294,8 @@ pub mod module { target_amount: Balance, }, /// Add Triangle info. - AddTriangleSwapInfo { - currency_1: CurrencyId, - currency_2: CurrencyId, - currency_3: CurrencyId, + SetupTriangleSwapInfo { + currency_id: CurrencyId, supply_amount: Balance, threshold: Balance, }, @@ -345,14 +341,6 @@ pub mod module { pub type TradingPairNodes = StorageMap<_, Twox64Concat, CurrencyId, BoundedVec, ValueQuery>; - /// Triangle path used for offchain arbitrage. - /// - /// TriangleTradingPath: map (CurrencyA, CurrencyB, CurrencyC) => () - #[pallet::storage] - #[pallet::getter(fn triangle_trading_path)] - pub type TriangleTradingPath = - StorageMap<_, Twox64Concat, (CurrencyId, CurrencyId, CurrencyId), (), ValueQuery>; - #[pallet::storage] #[pallet::getter(fn triangle_supply_threshold)] pub type TriangleSupplyThreshold = @@ -950,14 +938,12 @@ pub mod module { #[transactional] pub fn set_triangle_swap_info( origin: OriginFor, - currency_1: CurrencyId, - currency_2: CurrencyId, - currency_3: CurrencyId, + currency_id: CurrencyId, #[pallet::compact] supply_amount: Balance, #[pallet::compact] threshold: Balance, ) -> DispatchResult { T::ListingOrigin::ensure_origin(origin)?; - Self::do_set_triangle_swap_info(currency_1, currency_2, currency_3, supply_amount, threshold) + Self::do_set_triangle_swap_info(currency_id, supply_amount, threshold) } } @@ -998,10 +984,6 @@ impl Pallet { T::TreasuryPallet::get().into_account_truncating() } - pub fn get_trading_pair_keys() -> Vec { - TradingPairNodes::::iter_keys().collect() - } - fn submit_triangle_swap_tx(currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId) { let call = Call::::triangle_swap { currency_1, @@ -1018,11 +1000,6 @@ impl Pallet { } fn _offchain_worker(now: T::BlockNumber) -> Result<(), OffchainErr> { - // let trading_currency_ids = Self::get_trading_pair_keys(); - // if trading_currency_ids.len().is_zero() { - // return Ok(()); - // } - // acquire offchain worker lock let lock_expiration = Duration::from_millis(LOCK_DURATION); let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); @@ -1054,7 +1031,7 @@ impl Pallet { *maybe_trading_tokens = trading_tokens; } _ => { - log::debug!("Too many trading pair for token:{:?}.", currency_id); + log::debug!(target: "dex-bot", "Too many trading pair for token:{:?}.", currency_id); } } Ok(()) @@ -1109,10 +1086,9 @@ impl Pallet { let iteration_end_time = sp_io::offchain::timestamp(); log::debug!( - target: "dex offchain worker", - "iteration info:\n max iterations is {:?}\n start key: {:?}, iterate count: {:?}\n iteration start at: {:?}, end at: {:?}, execution time: {:?}\n", + target: "dex-bot", + "max iterations: {:?} start key: {:?}, count: {:?} start: {:?}, end: {:?}, cost: {:?}", max_iterations, - // currency_id, start_key, iteration_count, iteration_start_time, @@ -1135,41 +1111,17 @@ impl Pallet { } fn do_set_triangle_swap_info( - currency_1: CurrencyId, - currency_2: CurrencyId, - currency_3: CurrencyId, + currency_id: CurrencyId, supply_amount: Balance, threshold: Balance, ) -> DispatchResult { ensure!(threshold > supply_amount, Error::::TriangleSwapInfoInvalid); - let tuple = (currency_1, currency_2, currency_3); - ensure!( - !TriangleTradingPath::::contains_key(tuple), - Error::::TriangleSwapInfoExist - ); - // A-B-C-A has other variant, should ignore it if we already have A-B-C-A - let duplicate_currencies = vec![ - (currency_1, currency_3, currency_2), - (currency_2, currency_3, currency_1), - (currency_2, currency_1, currency_3), - (currency_3, currency_1, currency_2), - (currency_3, currency_2, currency_1), - ]; - // error if input is (A,B,C) variant. i.e. (A,C,B),(B,A,C) etc. - ensure!( - !duplicate_currencies.contains(&tuple), - Error::::TriangleSwapInfoInvalid - ); - TriangleTradingPath::::insert(tuple, ()); - - TriangleSupplyThreshold::::try_mutate(currency_1, |maybe_supply_threshold| -> DispatchResult { + TriangleSupplyThreshold::::try_mutate(currency_id, |maybe_supply_threshold| -> DispatchResult { *maybe_supply_threshold = Some((supply_amount, threshold)); Ok(()) })?; - Self::deposit_event(Event::AddTriangleSwapInfo { - currency_1, - currency_2, - currency_3, + Self::deposit_event(Event::SetupTriangleSwapInfo { + currency_id, supply_amount, threshold, }); @@ -1179,8 +1131,6 @@ impl Pallet { /// Triangle swap of path: `A->B->C->A`, the final output should be large than input. fn do_triangle_swap(current: (CurrencyId, CurrencyId, CurrencyId)) -> DispatchResult { if let Some((supply_amount, minimum_amount)) = TriangleSupplyThreshold::::get(¤t.0) { - // let minimum_amount = supply_amount.saturating_add(minimum_amount); - // A-B-C-A has two kind of swap: A-B/B-C-A or A-B-C/C-A, either one is ok. let first_path: Vec = vec![current.0, current.1, current.2]; let second_path: Vec = vec![current.2, current.0]; diff --git a/modules/dex/src/tests.rs b/modules/dex/src/tests.rs index a78dd7a60d..2b73bd642d 100644 --- a/modules/dex/src/tests.rs +++ b/modules/dex/src/tests.rs @@ -2088,11 +2088,15 @@ fn offchain_worker_trigger_unsigned_triangle_swap() { assert_ok!(DexModule::set_triangle_swap_info( Origin::signed(ListingOrigin::get()), AUSD, - DOT, - BTC, 1000, 1900, )); + System::assert_last_event(Event::DexModule(crate::Event::SetupTriangleSwapInfo { + currency_id: AUSD, + supply_amount: 1000, + threshold: 1900, + })); + let supply_threshold = TriangleSupplyThreshold::::get(AUSD).unwrap(); assert_eq!(supply_threshold, (1000, 1900)); assert_ok!(Tokens::update_balance( diff --git a/modules/evm/src/bench/mock.rs b/modules/evm/src/bench/mock.rs index e5ba0fbaf0..f645df2034 100644 --- a/modules/evm/src/bench/mock.rs +++ b/modules/evm/src/bench/mock.rs @@ -288,7 +288,7 @@ impl module_dex::Config for Runtime { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU64<1>; + type TradingKeysUpdateFrequency = ConstU32<1>; type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 6b96ee5a46..81e8ae069b 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -376,7 +376,7 @@ impl module_dex::Config for Test { type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU64<1>; + type TradingKeysUpdateFrequency = ConstU32<1>; type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; type TreasuryPallet = TreasuryPalletId; From 616863b55bfbb1ef39bc108688c91c4d2f0a98b3 Mon Sep 17 00:00:00 2001 From: zqh Date: Tue, 2 Aug 2022 17:49:22 +0800 Subject: [PATCH 11/20] move offchain to aggregated-dex --- Cargo.lock | 4 +- modules/aggregated-dex/Cargo.toml | 27 +- modules/aggregated-dex/src/lib.rs | 334 +++++++++++++++++++++++- modules/aggregated-dex/src/mock.rs | 20 +- modules/aggregated-dex/src/tests.rs | 212 ++++++++++++++- modules/auction-manager/src/mock.rs | 5 - modules/cdp-engine/src/mock.rs | 5 - modules/cdp-treasury/src/mock.rs | 15 -- modules/dex/Cargo.toml | 4 +- modules/dex/src/lib.rs | 326 +---------------------- modules/dex/src/mock.rs | 14 - modules/dex/src/tests.rs | 183 +------------ modules/evm/src/bench/mock.rs | 14 - modules/transaction-payment/src/mock.rs | 15 +- runtime/acala/src/lib.rs | 9 +- runtime/common/src/precompile/mock.rs | 4 - runtime/karura/src/lib.rs | 9 +- runtime/mandala/src/lib.rs | 9 +- 18 files changed, 597 insertions(+), 612 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a6dbed6ee..e8c65fb810 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5495,13 +5495,16 @@ dependencies = [ "acala-primitives", "frame-support", "frame-system", + "log", "module-dex", "module-support", "nutsfinance-stable-asset", "orml-tokens", "orml-traits", + "orml-utilities", "pallet-balances", "parity-scale-codec", + "parking_lot 0.12.1", "scale-info", "serde", "sp-core", @@ -5679,7 +5682,6 @@ dependencies = [ "orml-utilities", "pallet-balances", "parity-scale-codec", - "parking_lot 0.12.1", "scale-info", "serde", "sp-core", diff --git a/modules/aggregated-dex/Cargo.toml b/modules/aggregated-dex/Cargo.toml index 5938cfc57e..94104282dd 100644 --- a/modules/aggregated-dex/Cargo.toml +++ b/modules/aggregated-dex/Cargo.toml @@ -8,36 +8,45 @@ edition = "2021" serde = { version = "1.0.136", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } scale-info = { version = "2.1", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } + +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } -orml-traits = { path = "../../orml/traits", default-features = false } -support = { package = "module-support", path = "../support", default-features = false } -primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } -nutsfinance-stable-asset = { path = "../../ecosystem-modules/stable-asset/lib/stable-asset", version = "0.1.0", default-features = false } + module-dex = { package = "module-dex", path = "../dex", default-features = false } +primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } +support = { package = "module-support", path = "../support", default-features = false } + orml-tokens = { path = "../../orml/tokens", default-features = false } +orml-traits = { path = "../../orml/traits", default-features = false } +orml-utilities = { path = "../../orml/utilities", default-features = false } + +nutsfinance-stable-asset = { path = "../../ecosystem-modules/stable-asset/lib/stable-asset", version = "0.1.0", default-features = false } [dev-dependencies] -orml-tokens = { path = "../../orml/tokens" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } +parking_lot = { version = "0.12.0" } [features] default = ["std"] std = [ "serde", + "log/std", "codec/std", "scale-info/std", + "sp-io/std", + "sp-core/std", "sp-runtime/std", + "sp-std/std", "frame-support/std", "frame-system/std", - "sp-std/std", "orml-traits/std", "orml-tokens/std", + "orml-utilities/std", "support/std", "primitives/std", "nutsfinance-stable-asset/std", diff --git a/modules/aggregated-dex/src/lib.rs b/modules/aggregated-dex/src/lib.rs index 5cfed06e88..62be9664a1 100644 --- a/modules/aggregated-dex/src/lib.rs +++ b/modules/aggregated-dex/src/lib.rs @@ -22,12 +22,23 @@ #![allow(clippy::unused_unit)] #![allow(clippy::type_complexity)] -use frame_support::{pallet_prelude::*, transactional}; -use frame_system::pallet_prelude::*; +use frame_support::{pallet_prelude::*, transactional, PalletId}; +use frame_system::{ + offchain::{SendTransactionTypes, SubmitTransaction}, + pallet_prelude::*, +}; use nutsfinance_stable_asset::traits::StableAsset as StableAssetT; -use primitives::{Balance, CurrencyId}; -use sp_runtime::traits::{Convert, Zero}; -use sp_std::{marker::PhantomData, vec::Vec}; +use orml_utilities::OffchainErr; +use primitives::{Balance, CurrencyId, TradingPair}; +use sp_runtime::{ + offchain::{ + storage::StorageValueRef, + storage_lock::{StorageLock, Time}, + Duration, + }, + traits::{AccountIdConversion, Convert, Zero}, +}; +use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*, vec::Vec}; use support::{AggregatedSwapPath, DEXManager, RebasedStableAssetError, Swap, SwapLimit}; mod mock; @@ -35,8 +46,15 @@ mod tests; pub mod weights; pub use module::*; +use module_dex::TradingPairStatuses; pub use weights::WeightInfo; +pub const OFFCHAIN_WORKER_DATA: &[u8] = b"acala/dex-bot/data/"; +pub const OFFCHAIN_WORKER_LOCK: &[u8] = b"acala/dex-bot/lock/"; +pub const OFFCHAIN_WORKER_MAX_ITERATIONS: &[u8] = b"acala/dex-bot/max-iterations/"; +pub const LOCK_DURATION: u64 = 100; +pub const DEFAULT_MAX_ITERATIONS: u32 = 100; + pub type SwapPath = AggregatedSwapPath; #[frame_support::pallet] @@ -44,7 +62,9 @@ pub mod module { use super::*; #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + SendTransactionTypes> + module_dex::Config { + type Event: From> + IsType<::Event>; + /// DEX type DEX: DEXManager; @@ -68,6 +88,21 @@ pub mod module { #[pallet::constant] type SwapPathLimit: Get; + /// The limit of one Currency to all other direct trading token. + #[pallet::constant] + type SingleTokenTradingLimit: Get; + + /// The frequency block number to update `TradingPairNodes`. + #[pallet::constant] + type TradingKeysUpdateFrequency: Get; + + /// Treasury account participate in Rebalance swap. + #[pallet::constant] + type TreasuryPallet: Get; + + #[pallet::constant] + type UnsignedPriority: Get; + type WeightInfo: WeightInfo; } @@ -81,6 +116,27 @@ pub mod module { InvalidTokenIndex, /// The SwapPath is invalid. InvalidSwapPath, + /// Rebalance swap info is invalid. + RebalanceSwapInfoInvalid, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Rebalance trading path and balance. + RebalanceTrading { + currency_1: CurrencyId, + currency_2: CurrencyId, + currency_3: CurrencyId, + supply_amount: Balance, + target_amount: Balance, + }, + /// Add rebalance info. + SetupRebalanceSwapInfo { + currency_id: CurrencyId, + supply_amount: Balance, + threshold: Balance, + }, } /// The specific swap paths for AggregatedSwap do aggreated_swap to swap TokenA to TokenB @@ -91,13 +147,42 @@ pub mod module { pub type AggregatedSwapPaths = StorageMap<_, Twox64Concat, (CurrencyId, CurrencyId), BoundedVec, OptionQuery>; + /// Direct trading pair token list of specify CurrencyId. + /// + /// TradingPairNodes: map CurrencyId => vec![CurrencyId] + #[pallet::storage] + #[pallet::getter(fn trading_pair_nodes)] + pub type TradingPairNodes = + StorageMap<_, Twox64Concat, CurrencyId, BoundedVec, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn rebalance_supply_threshold)] + pub type RebalanceSupplyThreshold = + StorageMap<_, Twox64Concat, CurrencyId, (Balance, Balance), OptionQuery>; + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::hooks] - impl Hooks for Pallet {} + impl Hooks for Pallet { + fn offchain_worker(now: T::BlockNumber) { + if let Err(e) = Self::_offchain_worker(now) { + log::info!( + target: "dex-bot", + "offchain worker: cannot run at {:?}: {:?}", + now, e, + ); + } else { + log::debug!( + target: "dex-bot", + "offchain worker: start at block: {:?} already done!", + now, + ); + } + } + } #[pallet::call] impl Pallet { @@ -177,10 +262,65 @@ pub mod module { Ok(()) } + + #[pallet::weight(1000)] + #[transactional] + pub fn rebalance_swap( + origin: OriginFor, + currency_1: CurrencyId, + currency_2: CurrencyId, + currency_3: CurrencyId, + ) -> DispatchResult { + ensure_none(origin)?; + Self::do_rebalance_swap((currency_1, currency_2, currency_3)) + } + + #[pallet::weight(1000)] + #[transactional] + pub fn set_rebalance_swap_info( + origin: OriginFor, + currency_id: CurrencyId, + #[pallet::compact] supply_amount: Balance, + #[pallet::compact] threshold: Balance, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + Self::do_set_rebalance_swap_info(currency_id, supply_amount, threshold) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::rebalance_swap { + currency_1, + currency_2, + currency_3, + } = call + { + ValidTransaction::with_tag_prefix("DexBotOffchainWorker") + .priority(T::UnsignedPriority::get()) + .and_provides(( + >::block_number(), + currency_1, + currency_2, + currency_3, + )) + .longevity(64_u64) + .propagate(true) + .build() + } else { + InvalidTransaction::Call.into() + } + } } } impl Pallet { + fn treasury_account() -> T::AccountId { + T::TreasuryPallet::get().into_account_truncating() + } + fn check_swap_paths(paths: &[SwapPath]) -> sp_std::result::Result<(CurrencyId, CurrencyId), DispatchError> { ensure!(!paths.is_empty(), Error::::InvalidSwapPath); let mut supply_currency_id: Option = None; @@ -379,6 +519,186 @@ impl Pallet { } } } + + fn submit_rebalance_swap_tx(currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId) { + let call = Call::::rebalance_swap { + currency_1, + currency_2, + currency_3, + }; + if let Err(err) = SubmitTransaction::>::submit_unsigned_transaction(call.into()) { + log::info!( + target: "dex-bot", + "offchain worker: submit unsigned swap A={:?},B={:?},C={:?}, failed: {:?}", + currency_1, currency_2, currency_3, err, + ); + } + } + + fn _offchain_worker(now: T::BlockNumber) -> Result<(), OffchainErr> { + // acquire offchain worker lock + let lock_expiration = Duration::from_millis(LOCK_DURATION); + let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); + let mut guard = lock.try_lock().map_err(|_| OffchainErr::OffchainLock)?; + // get the max iterations config + let max_iterations = StorageValueRef::persistent(OFFCHAIN_WORKER_MAX_ITERATIONS) + .get::() + .unwrap_or(Some(DEFAULT_MAX_ITERATIONS)) + .unwrap_or(DEFAULT_MAX_ITERATIONS); + let mut to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); + + // update `TradingPairNodes` every `TradingKeysUpdateFrequency` interval. + if now % T::TradingKeysUpdateFrequency::get() == Zero::zero() { + let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); + TradingPairStatuses::::iter() + .filter(|(_, status)| status.enabled()) + .for_each(|(pair, _)| { + trading_pair_values_map + .entry(pair.first()) + .or_insert_with(Vec::::new) + .push(pair.second()); + }); + for (currency_id, trading_tokens) in trading_pair_values_map { + let _ = TradingPairNodes::::try_mutate(currency_id, |maybe_trading_tokens| -> DispatchResult { + let trading_tokens: Result, _> = + trading_tokens.try_into(); + match trading_tokens { + Ok(trading_tokens) => { + *maybe_trading_tokens = trading_tokens; + } + _ => { + log::debug!(target: "dex-bot", "Too many trading pair for token:{:?}.", currency_id); + } + } + Ok(()) + }); + } + } + + let start_key = to_be_continue.get::>().unwrap_or_default(); + let mut iterator = match start_key.clone() { + Some(key) => TradingPairNodes::::iter_from(key), + None => TradingPairNodes::::iter(), + }; + + let mut finished = true; + let mut iteration_count = 0; + let iteration_start_time = sp_io::offchain::timestamp(); + + #[allow(clippy::while_let_on_iterator)] + 'outer: while let Some((currency_id, trading_tokens)) = iterator.next() { + let len = trading_tokens.len(); + if len < 2 { + continue; + } + for i in 0..(len - 1) { + for j in (i + 1)..len { + iteration_count += 1; + + if let Some(pair) = TradingPair::from_currency_ids(trading_tokens[i], trading_tokens[j]) { + if TradingPairStatuses::::contains_key(&pair) { + let pair_status = TradingPairStatuses::::get(&pair); + if pair_status.enabled() { + Self::submit_rebalance_swap_tx(currency_id, pair.first(), pair.second()); + } + } + } + + // inner iterator consider as iterations too. + if iteration_count == max_iterations { + finished = false; + break 'outer; + } + + // extend offchain worker lock + guard.extend_lock().map_err(|_| OffchainErr::OffchainLock)?; + } + } + } + + let iteration_end_time = sp_io::offchain::timestamp(); + log::debug!( + target: "dex-bot", + "max iterations: {:?} start key: {:?}, count: {:?} start: {:?}, end: {:?}, cost: {:?}", + max_iterations, + start_key, + iteration_count, + iteration_start_time, + iteration_end_time, + iteration_end_time.diff(&iteration_start_time) + ); + + // if iteration for map storage finished, clear to be continue record + // otherwise, update to be continue record + if finished { + to_be_continue.clear(); + } else { + to_be_continue.set(&iterator.last_raw_key()); + } + + // Consume the guard but **do not** unlock the underlying lock. + guard.forget(); + + Ok(()) + } + + fn do_set_rebalance_swap_info( + currency_id: CurrencyId, + supply_amount: Balance, + threshold: Balance, + ) -> DispatchResult { + ensure!(threshold > supply_amount, Error::::RebalanceSwapInfoInvalid); + RebalanceSupplyThreshold::::try_mutate(currency_id, |maybe_supply_threshold| -> DispatchResult { + *maybe_supply_threshold = Some((supply_amount, threshold)); + Ok(()) + })?; + Self::deposit_event(Event::SetupRebalanceSwapInfo { + currency_id, + supply_amount, + threshold, + }); + Ok(()) + } + + /// Rebalance swap of path: `A->B->C->A`, the final output should be large than input. + fn do_rebalance_swap(current: (CurrencyId, CurrencyId, CurrencyId)) -> DispatchResult { + if let Some((supply_amount, minimum_amount)) = RebalanceSupplyThreshold::::get(¤t.0) { + // A-B-C-A has two kind of swap: A-B/B-C-A or A-B-C/C-A, either one is ok. + let first_path: Vec = vec![current.0, current.1, current.2]; + let second_path: Vec = vec![current.2, current.0]; + let supply_1 = SwapLimit::ExactSupply(supply_amount, 0); + + let mut valid_swap = false; + if let Some((_, target_3)) = T::DEX::get_swap_amount(&first_path, supply_1) { + if let Some((_, _)) = + T::DEX::get_swap_amount(&second_path, SwapLimit::ExactSupply(target_3, minimum_amount)) + { + valid_swap = true; + } + } + if !valid_swap { + return Ok(()); + } + + if let Ok((_, target_3)) = T::DEX::swap_with_specific_path(&Self::treasury_account(), &first_path, supply_1) + { + if let Ok((_, target_amount)) = T::DEX::swap_with_specific_path( + &Self::treasury_account(), + &second_path, + SwapLimit::ExactSupply(target_3, minimum_amount), + ) { + Self::deposit_event(Event::RebalanceTrading { + currency_1: current.0, + currency_2: current.1, + currency_3: current.2, + supply_amount, + target_amount, + }); + } + } + } + Ok(()) + } } /// Swap by Acala DEX which has specific joints. diff --git a/modules/aggregated-dex/src/mock.rs b/modules/aggregated-dex/src/mock.rs index 5f5d46f582..ad992dcb3d 100644 --- a/modules/aggregated-dex/src/mock.rs +++ b/modules/aggregated-dex/src/mock.rs @@ -49,8 +49,15 @@ pub const BOB: AccountId = AccountId32::new([2u8; 32]); pub const AUSD: CurrencyId = CurrencyId::Token(TokenSymbol::AUSD); pub const DOT: CurrencyId = CurrencyId::Token(TokenSymbol::DOT); pub const LDOT: CurrencyId = CurrencyId::Token(TokenSymbol::LDOT); +pub const BTC: CurrencyId = CurrencyId::Token(TokenSymbol::RENBTC); pub const STABLE_ASSET: CurrencyId = CurrencyId::StableAssetPoolToken(0); +parameter_types! { + pub static AUSDBTCPair: TradingPair = TradingPair::from_currency_ids(AUSD, BTC).unwrap(); + pub static AUSDDOTPair: TradingPair = TradingPair::from_currency_ids(AUSD, DOT).unwrap(); + pub static DOTBTCPair: TradingPair = TradingPair::from_currency_ids(DOT, BTC).unwrap(); +} + impl frame_system::Config for Runtime { type BaseCallFilter = Everything; type BlockWeights = (); @@ -106,7 +113,6 @@ ord_parameter_types! { parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); - pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); pub const GetExchangeFee: (u32, u32) = (0, 100); pub EnabledTradingPairs: Vec = vec![]; } @@ -116,11 +122,7 @@ impl module_dex::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU64<1>; - type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); @@ -196,14 +198,20 @@ impl nutsfinance_stable_asset::Config for Runtime { parameter_types! { pub static DexSwapJointList: Vec> = vec![]; pub const GetLiquidCurrencyId: CurrencyId = LDOT; + pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); } impl Config for Runtime { + type Event = Event; type DEX = Dex; type StableAsset = StableAssetWrapper; type GovernanceOrigin = EnsureSignedBy; type DexSwapJointList = DexSwapJointList; type SwapPathLimit = ConstU32<3>; + type TreasuryPallet = TreasuryPalletId; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = ConstU64<1>; + type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type WeightInfo = (); } @@ -220,7 +228,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic { System: frame_system::{Pallet, Call, Config, Storage, Event}, - AggregatedDex: aggregated_dex::{Pallet, Call, Storage}, + AggregatedDex: aggregated_dex::{Pallet, Call, Storage, Event}, Dex: module_dex::{Pallet, Call, Storage, Config, Event}, Tokens: orml_tokens::{Pallet, Storage, Event, Config}, StableAsset: nutsfinance_stable_asset::{Pallet, Call, Storage, Event}, diff --git a/modules/aggregated-dex/src/tests.rs b/modules/aggregated-dex/src/tests.rs index 4f3379ca87..d0310c654c 100644 --- a/modules/aggregated-dex/src/tests.rs +++ b/modules/aggregated-dex/src/tests.rs @@ -22,9 +22,24 @@ use super::*; use frame_support::{assert_noop, assert_ok}; -use mock::*; +use mock::{Call as MockCall, Event, Origin, System, *}; use nutsfinance_stable_asset::traits::StableAsset as StableAssetT; +use parking_lot::RwLock; +use sp_core::offchain::{ + testing, testing::PoolState, DbExternalities, OffchainDbExt, OffchainWorkerExt, StorageKind, TransactionPoolExt, +}; +use sp_io::offchain; use sp_runtime::traits::BadOrigin; +use std::sync::Arc; + +fn run_to_block_offchain(n: u64) { + while System::block_number() < n { + System::set_block_number(System::block_number() + 1); + AggregatedDex::offchain_worker(System::block_number()); + // this unlocks the concurrency storage lock so offchain_worker will fire next block + offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(LOCK_DURATION + 200))); + } +} fn set_dex_swap_joint_list(joints: Vec>) { DexSwapJointList::set(joints); @@ -1301,3 +1316,198 @@ fn aggregated_swap_swap_work() { ); }); } + +// fn inject_liquidity( +// account: AccountId, +// currency_id_a: CurrencyId, +// currency_id_b: CurrencyId, +// max_amount_a: Balance, +// max_amount_b: Balance, +// ) -> Result<(), &'static str> { +// let _ = Dex::enable_trading_pair(Origin::root(), currency_id_a, currency_id_b); +// assert_ok!(Currencies::update_balance( +// Origin::root(), +// MultiAddress::Id(account.clone()), +// currency_id_a.clone(), +// max_amount_a, +// )); +// assert_ok!(Currencies::update_balance( +// Origin::root(), +// MultiAddress::Id(account.clone()), +// currency_id_b.clone(), +// max_amount_b, +// )); +// Dex::add_liquidity( +// Origin::signed(account), +// currency_id_a, +// currency_id_b, +// max_amount_a, +// max_amount_b, +// Default::default(), +// false, +// )?; +// Ok(()) +// } + +fn inject_liquidity_default_pairs() { + assert_ok!(inject_liquidity(AUSD, DOT, 1_000_000u128, 2_000_000u128)); + assert_ok!(inject_liquidity(AUSD, BTC, 1_000_000u128, 2_000_000u128)); + assert_ok!(inject_liquidity(DOT, BTC, 1_000_000u128, 2_000_000u128)); +} + +#[test] +fn offchain_worker_max_iteration_works() { + let (mut offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::default().build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + + ext.execute_with(|| { + System::set_block_number(1); + inject_liquidity_default_pairs(); + + let keys: Vec = TradingPairNodes::::iter_keys().collect(); + assert_eq!(keys, vec![]); + + run_to_block_offchain(2); + // initialize `TradingPairNodes` + let keys: Vec = TradingPairNodes::::iter_keys().collect(); + assert_eq!(keys, vec![DOT, AUSD]); + + // trigger unsigned tx + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::AggregatedDex(crate::Call::rebalance_swap { + currency_1, + currency_2, + currency_3, + }) = tx.call + { + assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + assert_ok!(AggregatedDex::rebalance_swap( + Origin::none(), + currency_1, + currency_2, + currency_3 + )); + } + assert!(pool_state.write().transactions.pop().is_none()); + + let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); + let start_key = to_be_continue.get::>().unwrap_or_default(); + assert_eq!(start_key, None); + + // sets max iterations value to 1 + offchain.local_storage_set(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS, &1u32.encode()); + run_to_block_offchain(3); + let keys: Vec = TradingPairNodes::::iter_keys().collect(); + assert_eq!(keys, vec![DOT, AUSD]); + + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::AggregatedDex(crate::Call::rebalance_swap { + currency_1, + currency_2, + currency_3, + }) = tx.call + { + assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + assert_ok!(AggregatedDex::rebalance_swap( + Origin::none(), + currency_1, + currency_2, + currency_3 + )); + } + assert!(pool_state.write().transactions.pop().is_none()); + + // iterator last_saw_key + let mut iter = TradingPairNodes::::iter(); + let _ = iter.next(); // first currency is DOT + let _ = iter.next(); // second one is AUSD + let last_saw_key = iter.last_raw_key(); + + let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); + let start_key = to_be_continue.get::>().unwrap_or_default(); + assert_eq!(start_key, Some(last_saw_key.to_vec())); + }); +} + +#[test] +fn offchain_worker_trigger_unsigned_rebalance_swap() { + let (offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::default().build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + + ext.execute_with(|| { + System::set_block_number(1); + inject_liquidity_default_pairs(); + + // set swap supply and threshold + assert_ok!(AggregatedDex::set_rebalance_swap_info( + Origin::signed(BOB), + AUSD, + 1000, + 1960, + )); + System::assert_last_event(Event::AggregatedDex(crate::Event::SetupRebalanceSwapInfo { + currency_id: AUSD, + supply_amount: 1000, + threshold: 1960, + })); + + let supply_threshold = RebalanceSupplyThreshold::::get(AUSD).unwrap(); + assert_eq!(supply_threshold, (1000, 1960)); + assert_ok!(Tokens::deposit( + AUSD, + &Pallet::::treasury_account(), + 1_000_000_000_000_000u128 + )); + + trigger_unsigned_rebalance_swap(2, pool_state.clone(), Some(1990)); + trigger_unsigned_rebalance_swap(3, pool_state.clone(), Some(1970)); + trigger_unsigned_rebalance_swap(4, pool_state.clone(), None); + }); + + fn trigger_unsigned_rebalance_swap(n: u64, pool_state: Arc>, actual_target_amount: Option) { + System::reset_events(); + run_to_block_offchain(n); + let keys: Vec = TradingPairNodes::::iter_keys().collect(); + assert_eq!(keys, vec![DOT, AUSD]); + + // trigger unsigned tx + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::AggregatedDex(crate::Call::rebalance_swap { + currency_1, + currency_2, + currency_3, + }) = tx.call + { + assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + assert_ok!(AggregatedDex::rebalance_swap( + Origin::none(), + currency_1, + currency_2, + currency_3 + )); + } + assert!(pool_state.write().transactions.pop().is_none()); + + // if target amount is less than threshold, then rebalance swap not triggered. + if let Some(target_amount) = actual_target_amount { + System::assert_last_event(Event::AggregatedDex(crate::Event::RebalanceTrading { + currency_1: AUSD, + currency_2: DOT, + currency_3: BTC, + supply_amount: 1000, + target_amount, + })); + } + } +} diff --git a/modules/auction-manager/src/mock.rs b/modules/auction-manager/src/mock.rs index d42106385c..c68d3e15e3 100644 --- a/modules/auction-manager/src/mock.rs +++ b/modules/auction-manager/src/mock.rs @@ -162,7 +162,6 @@ impl PriceProvider for MockPriceSource { parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); - pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); pub const GetExchangeFee: (u32, u32) = (0, 100); pub EnabledTradingPairs: Vec = vec![ TradingPair::from_currency_ids(AUSD, BTC).unwrap(), @@ -176,11 +175,7 @@ impl module_dex::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU64<1>; - type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); diff --git a/modules/cdp-engine/src/mock.rs b/modules/cdp-engine/src/mock.rs index 834009ff2d..80b74a6452 100644 --- a/modules/cdp-engine/src/mock.rs +++ b/modules/cdp-engine/src/mock.rs @@ -242,7 +242,6 @@ impl cdp_treasury::Config for Runtime { parameter_types! { pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); - pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); pub const GetExchangeFee: (u32, u32) = (0, 100); pub EnabledTradingPairs: Vec = vec![ TradingPair::from_currency_ids(AUSD, BTC).unwrap(), @@ -257,11 +256,7 @@ impl dex::Config for Runtime { type Event = Event; type Currency = Currencies; type GetExchangeFee = GetExchangeFee; - type TradingPathLimit = ConstU32<4>; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU64<1>; type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); diff --git a/modules/cdp-treasury/src/mock.rs b/modules/cdp-treasury/src/mock.rs index 61d0a38687..cd2d8914ea 100644 --- a/modules/cdp-treasury/src/mock.rs +++ b/modules/cdp-treasury/src/mock.rs @@ -139,7 +139,6 @@ parameter_types! { TradingPair::from_currency_ids(BTC, DOT).unwrap(), ]; pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); - pub const TreasuryPalletId: PalletId = PalletId(*b"aca/trea"); } impl module_dex::Config for Runtime { @@ -147,11 +146,7 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<4>; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU64<1>; - type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); @@ -244,16 +239,6 @@ construct_runtime!( } ); -pub type Extrinsic = sp_runtime::testing::TestXt; - -impl frame_system::offchain::SendTransactionTypes for Runtime -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = Extrinsic; -} - pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, } diff --git a/modules/dex/Cargo.toml b/modules/dex/Cargo.toml index aac488fc0f..958c7ab0df 100644 --- a/modules/dex/Cargo.toml +++ b/modules/dex/Cargo.toml @@ -9,7 +9,6 @@ serde = { version = "1.0.136", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } scale-info = { version = "2.1", default-features = false, features = ["derive"] } -sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } @@ -23,9 +22,9 @@ support = { package = "module-support", path = "../support", default-features = primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } [dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } orml-tokens = { path = "../../orml/tokens" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } -parking_lot = { version = "0.12.0" } [features] default = ["std"] @@ -33,7 +32,6 @@ std = [ "serde", "codec/std", "scale-info/std", - "sp-io/std", "sp-core/std", "sp-runtime/std", "sp-std/std", diff --git a/modules/dex/src/lib.rs b/modules/dex/src/lib.rs index a5f55a6abf..11384588d8 100644 --- a/modules/dex/src/lib.rs +++ b/modules/dex/src/lib.rs @@ -35,24 +35,16 @@ use codec::MaxEncodedLen; use frame_support::{log, pallet_prelude::*, transactional, PalletId}; -use frame_system::{ - offchain::{SendTransactionTypes, SubmitTransaction}, - pallet_prelude::*, -}; +use frame_system::pallet_prelude::*; use orml_traits::{Happened, MultiCurrency, MultiCurrencyExtended}; use primitives::{Balance, CurrencyId, TradingPair}; use scale_info::TypeInfo; use sp_core::{H160, U256}; use sp_runtime::{ - offchain::{ - storage::StorageValueRef, - storage_lock::{StorageLock, Time}, - Duration, - }, traits::{AccountIdConversion, One, Saturating, Zero}, ArithmeticError, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, SaturatedConversion, }; -use sp_std::{collections::btree_map::BTreeMap, prelude::*, vec}; +use sp_std::{prelude::*, vec}; use support::{DEXIncentives, DEXManager, Erc20InfoMapping, ExchangeRate, Ratio, SwapLimit}; mod mock; @@ -60,15 +52,8 @@ mod tests; pub mod weights; pub use module::*; -use orml_utilities::OffchainErr; pub use weights::WeightInfo; -pub const OFFCHAIN_WORKER_DATA: &[u8] = b"acala/dex-bot/data/"; -pub const OFFCHAIN_WORKER_LOCK: &[u8] = b"acala/dex-bot/lock/"; -pub const OFFCHAIN_WORKER_MAX_ITERATIONS: &[u8] = b"acala/dex-bot/max-iterations/"; -pub const LOCK_DURATION: u64 = 100; -pub const DEFAULT_MAX_ITERATIONS: u32 = 100; - /// Parameters of TradingPair in Provisioning status #[derive(Encode, Decode, Clone, Copy, RuntimeDebug, PartialEq, Eq, MaxEncodedLen, TypeInfo)] pub struct ProvisioningParameters { @@ -113,7 +98,7 @@ pub mod module { use super::*; #[pallet::config] - pub trait Config: frame_system::Config + SendTransactionTypes> { + pub trait Config: frame_system::Config { type Event: From> + IsType<::Event>; /// Currency for transfer currencies @@ -131,25 +116,10 @@ pub mod module { #[pallet::constant] type TradingPathLimit: Get; - /// The limit of one Currency to all other direct trading token. - #[pallet::constant] - type SingleTokenTradingLimit: Get; - - /// The frequency block number to update `TradingPairNodes`. - #[pallet::constant] - type TradingKeysUpdateFrequency: Get; - /// The DEX's module id, keep all assets in DEX. #[pallet::constant] type PalletId: Get; - /// Treasury account participate in triangle swap. - #[pallet::constant] - type TreasuryPallet: Get; - - #[pallet::constant] - type UnsignedPriority: Get; - /// Mapping between CurrencyId and ERC20 address so user can use Erc20 /// address as LP token. type Erc20InfoMapping: Erc20InfoMapping; @@ -219,8 +189,6 @@ pub mod module { NotAllowedRefund, /// Cannot swap CannotSwap, - /// Triangle swap info is invalid. - TriangleSwapInfoInvalid, } #[pallet::event] @@ -285,20 +253,6 @@ pub mod module { accumulated_provision_0: Balance, accumulated_provision_1: Balance, }, - /// Triangle trading path and balance. - TriangleTrading { - currency_1: CurrencyId, - currency_2: CurrencyId, - currency_3: CurrencyId, - supply_amount: Balance, - target_amount: Balance, - }, - /// Add Triangle info. - SetupTriangleSwapInfo { - currency_id: CurrencyId, - supply_amount: Balance, - threshold: Balance, - }, } /// Liquidity pool for TradingPair. @@ -333,19 +287,6 @@ pub mod module { pub type InitialShareExchangeRates = StorageMap<_, Twox64Concat, TradingPair, (ExchangeRate, ExchangeRate), ValueQuery>; - /// Direct trading pair token list of specify CurrencyId. - /// - /// TradingPairNodes: map CurrencyId => vec![CurrencyId] - #[pallet::storage] - #[pallet::getter(fn trading_pair_nodes)] - pub type TradingPairNodes = - StorageMap<_, Twox64Concat, CurrencyId, BoundedVec, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn triangle_supply_threshold)] - pub type TriangleSupplyThreshold = - StorageMap<_, Twox64Concat, CurrencyId, (Balance, Balance), OptionQuery>; - #[pallet::genesis_config] pub struct GenesisConfig { pub initial_listing_trading_pairs: Vec<(TradingPair, (Balance, Balance), (Balance, Balance), T::BlockNumber)>, @@ -414,23 +355,7 @@ pub mod module { pub struct Pallet(_); #[pallet::hooks] - impl Hooks for Pallet { - fn offchain_worker(now: T::BlockNumber) { - if let Err(e) = Self::_offchain_worker(now) { - log::info!( - target: "dex-bot", - "offchain worker: cannot run at {:?}: {:?}", - now, e, - ); - } else { - log::debug!( - target: "dex-bot", - "offchain worker: start at block: {:?} already done!", - now, - ); - } - } - } + impl Hooks for Pallet {} #[pallet::call] impl Pallet { @@ -921,57 +846,6 @@ pub mod module { Ok(()) } - - #[pallet::weight(1000)] - #[transactional] - pub fn triangle_swap( - origin: OriginFor, - currency_1: CurrencyId, - currency_2: CurrencyId, - currency_3: CurrencyId, - ) -> DispatchResult { - ensure_none(origin)?; - Self::do_triangle_swap((currency_1, currency_2, currency_3)) - } - - #[pallet::weight(1000)] - #[transactional] - pub fn set_triangle_swap_info( - origin: OriginFor, - currency_id: CurrencyId, - #[pallet::compact] supply_amount: Balance, - #[pallet::compact] threshold: Balance, - ) -> DispatchResult { - T::ListingOrigin::ensure_origin(origin)?; - Self::do_set_triangle_swap_info(currency_id, supply_amount, threshold) - } - } - - #[pallet::validate_unsigned] - impl ValidateUnsigned for Pallet { - type Call = Call; - fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - if let Call::triangle_swap { - currency_1, - currency_2, - currency_3, - } = call - { - ValidTransaction::with_tag_prefix("DexBotOffchainWorker") - .priority(T::UnsignedPriority::get()) - .and_provides(( - >::block_number(), - currency_1, - currency_2, - currency_3, - )) - .longevity(64_u64) - .propagate(true) - .build() - } else { - InvalidTransaction::Call.into() - } - } } } @@ -980,198 +854,6 @@ impl Pallet { T::PalletId::get().into_account_truncating() } - fn treasury_account() -> T::AccountId { - T::TreasuryPallet::get().into_account_truncating() - } - - fn submit_triangle_swap_tx(currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId) { - let call = Call::::triangle_swap { - currency_1, - currency_2, - currency_3, - }; - if let Err(err) = SubmitTransaction::>::submit_unsigned_transaction(call.into()) { - log::info!( - target: "dex-bot", - "offchain worker: submit unsigned swap A={:?},B={:?},C={:?}, failed: {:?}", - currency_1, currency_2, currency_3, err, - ); - } - } - - fn _offchain_worker(now: T::BlockNumber) -> Result<(), OffchainErr> { - // acquire offchain worker lock - let lock_expiration = Duration::from_millis(LOCK_DURATION); - let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); - let mut guard = lock.try_lock().map_err(|_| OffchainErr::OffchainLock)?; - // get the max iterations config - let max_iterations = StorageValueRef::persistent(OFFCHAIN_WORKER_MAX_ITERATIONS) - .get::() - .unwrap_or(Some(DEFAULT_MAX_ITERATIONS)) - .unwrap_or(DEFAULT_MAX_ITERATIONS); - let mut to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); - - // update `TradingPairNodes` every `TradingKeysUpdateFrequency` interval. - if now % T::TradingKeysUpdateFrequency::get() == Zero::zero() { - let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); - TradingPairStatuses::::iter() - .filter(|(_, status)| status.enabled()) - .for_each(|(pair, _)| { - trading_pair_values_map - .entry(pair.first()) - .or_insert_with(Vec::::new) - .push(pair.second()); - }); - for (currency_id, trading_tokens) in trading_pair_values_map { - let _ = TradingPairNodes::::try_mutate(currency_id, |maybe_trading_tokens| -> DispatchResult { - let trading_tokens: Result, _> = - trading_tokens.try_into(); - match trading_tokens { - Ok(trading_tokens) => { - *maybe_trading_tokens = trading_tokens; - } - _ => { - log::debug!(target: "dex-bot", "Too many trading pair for token:{:?}.", currency_id); - } - } - Ok(()) - }); - } - } - - let start_key = to_be_continue.get::>().unwrap_or_default(); - let mut iterator = match start_key.clone() { - Some(key) => TradingPairNodes::::iter_from(key), - None => TradingPairNodes::::iter(), - }; - - let mut finished = true; - let mut iteration_count = 0; - let iteration_start_time = sp_io::offchain::timestamp(); - - let enabled_trading_pair: Vec = TradingPairStatuses::::iter() - .filter(|(_, status)| status.enabled()) - .map(|(pair, _)| pair) - .collect(); - - #[allow(clippy::while_let_on_iterator)] - 'outer: while let Some((currency_id, trading_tokens)) = iterator.next() { - let len = trading_tokens.len(); - if len < 2 { - continue; - } - for i in 0..(len - 1) { - for j in (i + 1)..len { - iteration_count += 1; - - if let Some(pair) = TradingPair::from_currency_ids(trading_tokens[i], trading_tokens[j]) { - if !enabled_trading_pair.contains(&pair) { - continue; - } - - Self::submit_triangle_swap_tx(currency_id, pair.first(), pair.second()); - } - - // inner iterator consider as iterations too. - if iteration_count == max_iterations { - finished = false; - break 'outer; - } - - // extend offchain worker lock - guard.extend_lock().map_err(|_| OffchainErr::OffchainLock)?; - } - } - } - - let iteration_end_time = sp_io::offchain::timestamp(); - log::debug!( - target: "dex-bot", - "max iterations: {:?} start key: {:?}, count: {:?} start: {:?}, end: {:?}, cost: {:?}", - max_iterations, - start_key, - iteration_count, - iteration_start_time, - iteration_end_time, - iteration_end_time.diff(&iteration_start_time) - ); - - // if iteration for map storage finished, clear to be continue record - // otherwise, update to be continue record - if finished { - to_be_continue.clear(); - } else { - to_be_continue.set(&iterator.last_raw_key()); - } - - // Consume the guard but **do not** unlock the underlying lock. - guard.forget(); - - Ok(()) - } - - fn do_set_triangle_swap_info( - currency_id: CurrencyId, - supply_amount: Balance, - threshold: Balance, - ) -> DispatchResult { - ensure!(threshold > supply_amount, Error::::TriangleSwapInfoInvalid); - TriangleSupplyThreshold::::try_mutate(currency_id, |maybe_supply_threshold| -> DispatchResult { - *maybe_supply_threshold = Some((supply_amount, threshold)); - Ok(()) - })?; - Self::deposit_event(Event::SetupTriangleSwapInfo { - currency_id, - supply_amount, - threshold, - }); - Ok(()) - } - - /// Triangle swap of path: `A->B->C->A`, the final output should be large than input. - fn do_triangle_swap(current: (CurrencyId, CurrencyId, CurrencyId)) -> DispatchResult { - if let Some((supply_amount, minimum_amount)) = TriangleSupplyThreshold::::get(¤t.0) { - // A-B-C-A has two kind of swap: A-B/B-C-A or A-B-C/C-A, either one is ok. - let first_path: Vec = vec![current.0, current.1, current.2]; - let second_path: Vec = vec![current.2, current.0]; - let supply_1 = SwapLimit::ExactSupply(supply_amount, 0); - - let mut valid_swap = false; - if let Some((_, target_3)) = - >::get_swap_amount(&first_path, supply_1) - { - if let Some((_, _)) = >::get_swap_amount( - &second_path, - SwapLimit::ExactSupply(target_3, minimum_amount), - ) { - valid_swap = true; - } - } - if !valid_swap { - return Ok(()); - } - - if let Ok((_, target_3)) = >::swap_with_specific_path( - &Self::treasury_account(), - &first_path, - supply_1, - ) { - if let Ok(target_amount) = - Self::do_swap_with_exact_supply(&Self::treasury_account(), &second_path, target_3, minimum_amount) - { - Self::deposit_event(Event::TriangleTrading { - currency_1: current.0, - currency_2: current.1, - currency_3: current.2, - supply_amount, - target_amount, - }); - } - } - } - Ok(()) - } - fn try_mutate_liquidity_pool( trading_pair: &TradingPair, f: impl FnOnce((&mut Balance, &mut Balance)) -> sp_std::result::Result, diff --git a/modules/dex/src/mock.rs b/modules/dex/src/mock.rs index 2b24fe532a..59c38f006f 100644 --- a/modules/dex/src/mock.rs +++ b/modules/dex/src/mock.rs @@ -147,11 +147,7 @@ impl Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = ConstU32<3>; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU64<1>; type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPallet; - type UnsignedPriority = ConstU64<1048576>; type Erc20InfoMapping = MockErc20InfoMapping; type WeightInfo = (); type DEXIncentives = MockDEXIncentives; @@ -183,16 +179,6 @@ construct_runtime!( } ); -pub type Extrinsic = sp_runtime::testing::TestXt; - -impl frame_system::offchain::SendTransactionTypes for Runtime -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = Extrinsic; -} - pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, initial_listing_trading_pairs: Vec<(TradingPair, (Balance, Balance), (Balance, Balance), BlockNumber)>, diff --git a/modules/dex/src/tests.rs b/modules/dex/src/tests.rs index 2b73bd642d..bfd9c05ebd 100644 --- a/modules/dex/src/tests.rs +++ b/modules/dex/src/tests.rs @@ -23,32 +23,15 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use mock::{ - ACAJointSwap, AUSDBTCPair, AUSDDOTPair, AUSDJointSwap, Call as MockCall, DOTBTCPair, DexModule, Event, ExtBuilder, - ListingOrigin, Origin, Runtime, System, Tokens, ACA, ALICE, AUSD, AUSD_DOT_POOL_RECORD, BOB, BTC, CAROL, DOT, *, + ACAJointSwap, AUSDBTCPair, AUSDDOTPair, AUSDJointSwap, DOTBTCPair, DexModule, Event, ExtBuilder, ListingOrigin, + Origin, Runtime, System, Tokens, ACA, ALICE, AUSD, AUSD_DOT_POOL_RECORD, BOB, BTC, CAROL, DOT, *, }; use orml_traits::MultiReservableCurrency; -use parking_lot::RwLock; -use sp_core::{ - offchain::{ - testing, testing::PoolState, DbExternalities, OffchainDbExt, OffchainWorkerExt, StorageKind, TransactionPoolExt, - }, - H160, -}; -use sp_io::offchain; +use sp_core::H160; use sp_runtime::traits::BadOrigin; use std::str::FromStr; -use std::sync::Arc; use support::{Swap, SwapError}; -fn run_to_block_offchain(n: u64) { - while System::block_number() < n { - System::set_block_number(System::block_number() + 1); - DexModule::offchain_worker(System::block_number()); - // this unlocks the concurrency storage lock so offchain_worker will fire next block - offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(LOCK_DURATION + 200))); - } -} - #[test] fn list_provisioning_work() { ExtBuilder::default().build().execute_with(|| { @@ -1987,163 +1970,3 @@ fn specific_joint_swap_work() { ); }); } - -#[test] -fn offchain_worker_max_iteration_works() { - let (mut offchain, _offchain_state) = testing::TestOffchainExt::new(); - let (pool, pool_state) = testing::TestTransactionPoolExt::new(); - let mut ext = ExtBuilder::default() - .initialize_enabled_trading_pairs() - .initialize_added_liquidity_pools(ALICE) - .build(); - ext.register_extension(OffchainWorkerExt::new(offchain.clone())); - ext.register_extension(TransactionPoolExt::new(pool)); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - - ext.execute_with(|| { - System::set_block_number(1); - let keys: Vec = TradingPairNodes::::iter_keys().collect(); - assert_eq!(keys, vec![]); - - run_to_block_offchain(2); - // initialize `TradingPairNodes` - let keys: Vec = TradingPairNodes::::iter_keys().collect(); - assert_eq!(keys, vec![DOT, AUSD]); - - // trigger unsigned tx - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); - if let MockCall::DexModule(crate::Call::triangle_swap { - currency_1, - currency_2, - currency_3, - }) = tx.call - { - assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); - assert_ok!(DexModule::triangle_swap( - Origin::none(), - currency_1, - currency_2, - currency_3 - )); - } - assert!(pool_state.write().transactions.pop().is_none()); - - let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); - let start_key = to_be_continue.get::>().unwrap_or_default(); - assert_eq!(start_key, None); - - // sets max iterations value to 1 - offchain.local_storage_set(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS, &1u32.encode()); - run_to_block_offchain(3); - let keys: Vec = TradingPairNodes::::iter_keys().collect(); - assert_eq!(keys, vec![DOT, AUSD]); - - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); - if let MockCall::DexModule(crate::Call::triangle_swap { - currency_1, - currency_2, - currency_3, - }) = tx.call - { - assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); - assert_ok!(DexModule::triangle_swap( - Origin::none(), - currency_1, - currency_2, - currency_3 - )); - } - assert!(pool_state.write().transactions.pop().is_none()); - - // iterator last_saw_key - let mut iter = TradingPairNodes::::iter(); - let _ = iter.next(); // first currency is DOT - let _ = iter.next(); // second one is AUSD - let last_saw_key = iter.last_raw_key(); - - let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); - let start_key = to_be_continue.get::>().unwrap_or_default(); - assert_eq!(start_key, Some(last_saw_key.to_vec())); - }); -} - -#[test] -fn offchain_worker_trigger_unsigned_triangle_swap() { - let (offchain, _offchain_state) = testing::TestOffchainExt::new(); - let (pool, pool_state) = testing::TestTransactionPoolExt::new(); - let mut ext = ExtBuilder::default() - .initialize_enabled_trading_pairs() - .initialize_added_liquidity_pools(ALICE) - .build(); - ext.register_extension(OffchainWorkerExt::new(offchain.clone())); - ext.register_extension(TransactionPoolExt::new(pool)); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - - ext.execute_with(|| { - System::set_block_number(1); - - // set swap supply and threshold - assert_ok!(DexModule::set_triangle_swap_info( - Origin::signed(ListingOrigin::get()), - AUSD, - 1000, - 1900, - )); - System::assert_last_event(Event::DexModule(crate::Event::SetupTriangleSwapInfo { - currency_id: AUSD, - supply_amount: 1000, - threshold: 1900, - })); - - let supply_threshold = TriangleSupplyThreshold::::get(AUSD).unwrap(); - assert_eq!(supply_threshold, (1000, 1900)); - assert_ok!(Tokens::update_balance( - AUSD, - &Pallet::::treasury_account(), - 1_000_000_000_000_000i128 - )); - - trigger_unsigned_triangle_swap(2, pool_state.clone(), Some(1930)); - trigger_unsigned_triangle_swap(3, pool_state.clone(), Some(1911)); - trigger_unsigned_triangle_swap(4, pool_state.clone(), None); - }); - - fn trigger_unsigned_triangle_swap(n: u64, pool_state: Arc>, actual_target_amount: Option) { - System::reset_events(); - run_to_block_offchain(n); - let keys: Vec = TradingPairNodes::::iter_keys().collect(); - assert_eq!(keys, vec![DOT, AUSD]); - - // trigger unsigned tx - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); - if let MockCall::DexModule(crate::Call::triangle_swap { - currency_1, - currency_2, - currency_3, - }) = tx.call - { - assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); - assert_ok!(DexModule::triangle_swap( - Origin::none(), - currency_1, - currency_2, - currency_3 - )); - } - assert!(pool_state.write().transactions.pop().is_none()); - - // if target amount is less than threshold, then triangle swap not triggered. - if let Some(target_amount) = actual_target_amount { - System::assert_last_event(Event::DexModule(crate::Event::TriangleTrading { - currency_1: AUSD, - currency_2: DOT, - currency_3: BTC, - supply_amount: 1000, - target_amount, - })); - } - } -} diff --git a/modules/evm/src/bench/mock.rs b/modules/evm/src/bench/mock.rs index f645df2034..9ba8029b3a 100644 --- a/modules/evm/src/bench/mock.rs +++ b/modules/evm/src/bench/mock.rs @@ -287,11 +287,7 @@ impl module_dex::Config for Runtime { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU32<1>; - type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = MockErc20InfoMapping; type WeightInfo = (); type DEXIncentives = MockDEXIncentives; @@ -319,13 +315,3 @@ construct_runtime!( TransactionPayment: module_transaction_payment::{Pallet, Call, Storage, Event}, } ); - -pub type Extrinsic = sp_runtime::testing::TestXt; - -impl frame_system::offchain::SendTransactionTypes for Runtime -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = Extrinsic; -} diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index 70b09d174b..1effc0018a 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -185,11 +185,7 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU64<1>; - type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); type WeightInfo = (); @@ -199,6 +195,7 @@ impl module_dex::Config for Runtime { } impl module_aggregated_dex::Config for Runtime { + type Event = Event; type DEX = DEXModule; type StableAsset = MockStableAsset; type GovernanceOrigin = EnsureSignedBy; @@ -335,16 +332,6 @@ construct_runtime!( } ); -pub type Extrinsic = sp_runtime::testing::TestXt; - -impl frame_system::offchain::SendTransactionTypes for Runtime -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = Extrinsic; -} - pub struct ExtBuilder { balances: Vec<(AccountId, CurrencyId, Balance)>, base_weight: u64, diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 0cd48f127d..16bda66994 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1096,11 +1096,7 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; - type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; type DEXIncentives = Incentives; type WeightInfo = weights::module_dex::WeightInfo; @@ -1110,11 +1106,16 @@ impl module_dex::Config for Runtime { } impl module_aggregated_dex::Config for Runtime { + type Event = Event; type DEX = Dex; type StableAsset = RebasedStableAsset; type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; type DexSwapJointList = AlternativeSwapPathJointList; type SwapPathLimit = ConstU32<3>; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; + type TreasuryPallet = TreasuryPalletId; + type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type WeightInfo = (); } diff --git a/runtime/common/src/precompile/mock.rs b/runtime/common/src/precompile/mock.rs index 81e8ae069b..d579c6c5fa 100644 --- a/runtime/common/src/precompile/mock.rs +++ b/runtime/common/src/precompile/mock.rs @@ -375,11 +375,7 @@ impl module_dex::Config for Test { type Currency = Tokens; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU32<1>; - type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; type WeightInfo = (); type DEXIncentives = MockDEXIncentives; diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 1c476308b7..a9cd23682f 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1102,11 +1102,7 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; - type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type Erc20InfoMapping = EvmErc20InfoMapping; type DEXIncentives = Incentives; type WeightInfo = weights::module_dex::WeightInfo; @@ -1116,11 +1112,16 @@ impl module_dex::Config for Runtime { } impl module_aggregated_dex::Config for Runtime { + type Event = Event; type DEX = Dex; type StableAsset = RebasedStableAsset; type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; type DexSwapJointList = AlternativeSwapPathJointList; type SwapPathLimit = ConstU32<3>; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; + type TreasuryPallet = TreasuryPalletId; + type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type WeightInfo = (); } diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index da5b1780be..8a99718dcf 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1160,11 +1160,7 @@ impl module_dex::Config for Runtime { type Currency = Currencies; type GetExchangeFee = GetExchangeFee; type TradingPathLimit = TradingPathLimit; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; - type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type PalletId = DEXPalletId; - type TreasuryPallet = TreasuryPalletId; type Erc20InfoMapping = EvmErc20InfoMapping; type DEXIncentives = Incentives; type WeightInfo = weights::module_dex::WeightInfo; @@ -1174,11 +1170,16 @@ impl module_dex::Config for Runtime { } impl module_aggregated_dex::Config for Runtime { + type Event = Event; type DEX = Dex; type StableAsset = RebasedStableAsset; type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; type DexSwapJointList = AlternativeSwapPathJointList; type SwapPathLimit = ConstU32<3>; + type SingleTokenTradingLimit = ConstU32<10>; + type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; + type TreasuryPallet = TreasuryPalletId; + type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type WeightInfo = (); } From cda66b3f639f3450cb071ce26f4ceb67771a90dd Mon Sep 17 00:00:00 2001 From: zqh Date: Wed, 3 Aug 2022 00:18:50 +0800 Subject: [PATCH 12/20] initialize on_idle and add benchmark --- Cargo.lock | 1 - modules/aggregated-dex/src/lib.rs | 101 +++++++---- modules/aggregated-dex/src/tests.rs | 164 +++++++++--------- modules/aggregated-dex/src/weights.rs | 27 +++ modules/cdp-engine/src/mock.rs | 1 + modules/dex/Cargo.toml | 12 +- modules/dex/src/mock.rs | 1 - modules/dex/src/tests.rs | 2 +- .../src/benchmarking/aggregated_dex.rs | 95 ++++++++++ runtime/mandala/src/benchmarking/mod.rs | 1 + runtime/mandala/src/lib.rs | 5 +- 11 files changed, 282 insertions(+), 128 deletions(-) create mode 100644 runtime/mandala/src/benchmarking/aggregated_dex.rs diff --git a/Cargo.lock b/Cargo.lock index e8c65fb810..c024973f9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5679,7 +5679,6 @@ dependencies = [ "module-support", "orml-tokens", "orml-traits", - "orml-utilities", "pallet-balances", "parity-scale-codec", "scale-info", diff --git a/modules/aggregated-dex/src/lib.rs b/modules/aggregated-dex/src/lib.rs index 62be9664a1..52c53588c7 100644 --- a/modules/aggregated-dex/src/lib.rs +++ b/modules/aggregated-dex/src/lib.rs @@ -182,6 +182,16 @@ pub mod module { ); } } + + fn on_idle(now: T::BlockNumber, mut remaining_weight: Weight) -> Weight { + // update `TradingPairNodes` every `TradingKeysUpdateFrequency` interval. + if now % T::TradingKeysUpdateFrequency::get() == Zero::zero() { + let _ = Self::do_set_trading_pair_nodes(); + let used = ::WeightInfo::set_trading_pair_nodes(); + remaining_weight = remaining_weight.saturating_sub(used); + } + remaining_weight + } } #[pallet::call] @@ -263,7 +273,19 @@ pub mod module { Ok(()) } - #[pallet::weight(1000)] + #[pallet::weight(::WeightInfo::set_rebalance_swap_info())] + #[transactional] + pub fn set_rebalance_swap_info( + origin: OriginFor, + currency_id: CurrencyId, + #[pallet::compact] supply_amount: Balance, + #[pallet::compact] threshold: Balance, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + Self::do_set_rebalance_swap_info(currency_id, supply_amount, threshold) + } + + #[pallet::weight(::WeightInfo::rebalance_swap())] #[transactional] pub fn rebalance_swap( origin: OriginFor, @@ -275,16 +297,11 @@ pub mod module { Self::do_rebalance_swap((currency_1, currency_2, currency_3)) } - #[pallet::weight(1000)] + #[pallet::weight(::WeightInfo::set_trading_pair_nodes())] #[transactional] - pub fn set_rebalance_swap_info( - origin: OriginFor, - currency_id: CurrencyId, - #[pallet::compact] supply_amount: Balance, - #[pallet::compact] threshold: Balance, - ) -> DispatchResult { + pub fn set_trading_pair_nodes(origin: OriginFor) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; - Self::do_set_rebalance_swap_info(currency_id, supply_amount, threshold) + Self::do_set_trading_pair_nodes() } } @@ -321,6 +338,10 @@ impl Pallet { T::TreasuryPallet::get().into_account_truncating() } + pub fn get_trading_pair_keys() -> Vec { + TradingPairNodes::::iter_keys().collect() + } + fn check_swap_paths(paths: &[SwapPath]) -> sp_std::result::Result<(CurrencyId, CurrencyId), DispatchError> { ensure!(!paths.is_empty(), Error::::InvalidSwapPath); let mut supply_currency_id: Option = None; @@ -535,7 +556,11 @@ impl Pallet { } } - fn _offchain_worker(now: T::BlockNumber) -> Result<(), OffchainErr> { + fn _offchain_worker(_now: T::BlockNumber) -> Result<(), OffchainErr> { + if Self::get_trading_pair_keys().len().is_zero() { + return Ok(()); + } + // acquire offchain worker lock let lock_expiration = Duration::from_millis(LOCK_DURATION); let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); @@ -547,34 +572,6 @@ impl Pallet { .unwrap_or(DEFAULT_MAX_ITERATIONS); let mut to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); - // update `TradingPairNodes` every `TradingKeysUpdateFrequency` interval. - if now % T::TradingKeysUpdateFrequency::get() == Zero::zero() { - let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); - TradingPairStatuses::::iter() - .filter(|(_, status)| status.enabled()) - .for_each(|(pair, _)| { - trading_pair_values_map - .entry(pair.first()) - .or_insert_with(Vec::::new) - .push(pair.second()); - }); - for (currency_id, trading_tokens) in trading_pair_values_map { - let _ = TradingPairNodes::::try_mutate(currency_id, |maybe_trading_tokens| -> DispatchResult { - let trading_tokens: Result, _> = - trading_tokens.try_into(); - match trading_tokens { - Ok(trading_tokens) => { - *maybe_trading_tokens = trading_tokens; - } - _ => { - log::debug!(target: "dex-bot", "Too many trading pair for token:{:?}.", currency_id); - } - } - Ok(()) - }); - } - } - let start_key = to_be_continue.get::>().unwrap_or_default(); let mut iterator = match start_key.clone() { Some(key) => TradingPairNodes::::iter_from(key), @@ -699,6 +696,34 @@ impl Pallet { } Ok(()) } + + fn do_set_trading_pair_nodes() -> DispatchResult { + let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); + TradingPairStatuses::::iter() + .filter(|(_, status)| status.enabled()) + .for_each(|(pair, _)| { + trading_pair_values_map + .entry(pair.first()) + .or_insert_with(Vec::::new) + .push(pair.second()); + }); + for (currency_id, trading_tokens) in trading_pair_values_map { + let _ = TradingPairNodes::::try_mutate(currency_id, |maybe_trading_tokens| -> DispatchResult { + let trading_tokens: Result, _> = + trading_tokens.try_into(); + match trading_tokens { + Ok(trading_tokens) => { + *maybe_trading_tokens = trading_tokens; + } + _ => { + log::debug!(target: "dex-bot", "Too many trading pair for token:{:?}.", currency_id); + } + } + Ok(()) + }); + } + Ok(()) + } } /// Swap by Acala DEX which has specific joints. diff --git a/modules/aggregated-dex/src/tests.rs b/modules/aggregated-dex/src/tests.rs index d0310c654c..3a36663879 100644 --- a/modules/aggregated-dex/src/tests.rs +++ b/modules/aggregated-dex/src/tests.rs @@ -32,8 +32,13 @@ use sp_io::offchain; use sp_runtime::traits::BadOrigin; use std::sync::Arc; -fn run_to_block_offchain(n: u64) { +fn run_to_block_offchain(n: u64, execute_on_idle: bool) { + let weight: Weight = 1000; while System::block_number() < n { + if execute_on_idle { + AggregatedDex::on_idle(n, weight); + } + System::set_block_number(System::block_number() + 1); AggregatedDex::offchain_worker(System::block_number()); // this unlocks the concurrency storage lock so offchain_worker will fire next block @@ -1371,29 +1376,30 @@ fn offchain_worker_max_iteration_works() { let keys: Vec = TradingPairNodes::::iter_keys().collect(); assert_eq!(keys, vec![]); - run_to_block_offchain(2); - // initialize `TradingPairNodes` - let keys: Vec = TradingPairNodes::::iter_keys().collect(); - assert_eq!(keys, vec![DOT, AUSD]); - - // trigger unsigned tx - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); - if let MockCall::AggregatedDex(crate::Call::rebalance_swap { - currency_1, - currency_2, - currency_3, - }) = tx.call - { - assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); - assert_ok!(AggregatedDex::rebalance_swap( - Origin::none(), - currency_1, - currency_2, - currency_3 - )); - } - assert!(pool_state.write().transactions.pop().is_none()); + trigger_unsigned_rebalance_swap(2, pool_state.clone(), None); + // run_to_block_offchain(2); + // // initialize `TradingPairNodes` + // let keys: Vec = TradingPairNodes::::iter_keys().collect(); + // assert_eq!(keys, vec![DOT, AUSD]); + // + // // trigger unsigned tx + // let tx = pool_state.write().transactions.pop().unwrap(); + // let tx = Extrinsic::decode(&mut &*tx).unwrap(); + // if let MockCall::AggregatedDex(crate::Call::rebalance_swap { + // currency_1, + // currency_2, + // currency_3, + // }) = tx.call + // { + // assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + // assert_ok!(AggregatedDex::rebalance_swap( + // Origin::none(), + // currency_1, + // currency_2, + // currency_3 + // )); + // } + // assert!(pool_state.write().transactions.pop().is_none()); let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); let start_key = to_be_continue.get::>().unwrap_or_default(); @@ -1401,27 +1407,29 @@ fn offchain_worker_max_iteration_works() { // sets max iterations value to 1 offchain.local_storage_set(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS, &1u32.encode()); - run_to_block_offchain(3); - let keys: Vec = TradingPairNodes::::iter_keys().collect(); - assert_eq!(keys, vec![DOT, AUSD]); - - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); - if let MockCall::AggregatedDex(crate::Call::rebalance_swap { - currency_1, - currency_2, - currency_3, - }) = tx.call - { - assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); - assert_ok!(AggregatedDex::rebalance_swap( - Origin::none(), - currency_1, - currency_2, - currency_3 - )); - } - assert!(pool_state.write().transactions.pop().is_none()); + trigger_unsigned_rebalance_swap(3, pool_state.clone(), None); + + // run_to_block_offchain(3); + // let keys: Vec = TradingPairNodes::::iter_keys().collect(); + // assert_eq!(keys, vec![DOT, AUSD]); + // + // let tx = pool_state.write().transactions.pop().unwrap(); + // let tx = Extrinsic::decode(&mut &*tx).unwrap(); + // if let MockCall::AggregatedDex(crate::Call::rebalance_swap { + // currency_1, + // currency_2, + // currency_3, + // }) = tx.call + // { + // assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + // assert_ok!(AggregatedDex::rebalance_swap( + // Origin::none(), + // currency_1, + // currency_2, + // currency_3 + // )); + // } + // assert!(pool_state.write().transactions.pop().is_none()); // iterator last_saw_key let mut iter = TradingPairNodes::::iter(); @@ -1473,41 +1481,41 @@ fn offchain_worker_trigger_unsigned_rebalance_swap() { trigger_unsigned_rebalance_swap(3, pool_state.clone(), Some(1970)); trigger_unsigned_rebalance_swap(4, pool_state.clone(), None); }); +} - fn trigger_unsigned_rebalance_swap(n: u64, pool_state: Arc>, actual_target_amount: Option) { - System::reset_events(); - run_to_block_offchain(n); - let keys: Vec = TradingPairNodes::::iter_keys().collect(); - assert_eq!(keys, vec![DOT, AUSD]); - - // trigger unsigned tx - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); - if let MockCall::AggregatedDex(crate::Call::rebalance_swap { +fn trigger_unsigned_rebalance_swap(n: u64, pool_state: Arc>, actual_target_amount: Option) { + System::reset_events(); + run_to_block_offchain(n, true); + let keys: Vec = TradingPairNodes::::iter_keys().collect(); + assert_eq!(keys, vec![DOT, AUSD]); + + // trigger unsigned tx + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + if let MockCall::AggregatedDex(crate::Call::rebalance_swap { + currency_1, + currency_2, + currency_3, + }) = tx.call + { + assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + assert_ok!(AggregatedDex::rebalance_swap( + Origin::none(), currency_1, currency_2, - currency_3, - }) = tx.call - { - assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); - assert_ok!(AggregatedDex::rebalance_swap( - Origin::none(), - currency_1, - currency_2, - currency_3 - )); - } - assert!(pool_state.write().transactions.pop().is_none()); - - // if target amount is less than threshold, then rebalance swap not triggered. - if let Some(target_amount) = actual_target_amount { - System::assert_last_event(Event::AggregatedDex(crate::Event::RebalanceTrading { - currency_1: AUSD, - currency_2: DOT, - currency_3: BTC, - supply_amount: 1000, - target_amount, - })); - } + currency_3 + )); + } + assert!(pool_state.write().transactions.pop().is_none()); + + // if target amount is less than threshold, then rebalance swap not triggered. + if let Some(target_amount) = actual_target_amount { + System::assert_last_event(Event::AggregatedDex(crate::Event::RebalanceTrading { + currency_1: AUSD, + currency_2: DOT, + currency_3: BTC, + supply_amount: 1000, + target_amount, + })); } } diff --git a/modules/aggregated-dex/src/weights.rs b/modules/aggregated-dex/src/weights.rs index 500b09be52..3cf1e67494 100644 --- a/modules/aggregated-dex/src/weights.rs +++ b/modules/aggregated-dex/src/weights.rs @@ -48,6 +48,9 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn swap_with_exact_supply(u: u32, ) -> Weight; fn update_aggregated_swap_paths(u: u32, ) -> Weight; + fn rebalance_swap() -> Weight; + fn set_rebalance_swap_info() -> Weight; + fn set_trading_pair_nodes() -> Weight; } /// Weights for module_aggregated_dex using the Acala node and recommended hardware. @@ -69,6 +72,18 @@ impl WeightInfo for AcalaWeight { .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(u as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(u as Weight))) } + + fn rebalance_swap() -> Weight { + 2_268_000 + } + + fn set_rebalance_swap_info() -> Weight { + 2_268_000 + } + + fn set_trading_pair_nodes() -> Weight { + 2_268_000 + } } // For backwards compatibility and tests @@ -89,4 +104,16 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(u as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(u as Weight))) } + + fn rebalance_swap() -> Weight { + 2_268_000 + } + + fn set_rebalance_swap_info() -> Weight { + 2_268_000 + } + + fn set_trading_pair_nodes() -> Weight { + 2_268_000 + } } diff --git a/modules/cdp-engine/src/mock.rs b/modules/cdp-engine/src/mock.rs index 80b74a6452..b634d4a486 100644 --- a/modules/cdp-engine/src/mock.rs +++ b/modules/cdp-engine/src/mock.rs @@ -256,6 +256,7 @@ impl dex::Config for Runtime { type Event = Event; type Currency = Currencies; type GetExchangeFee = GetExchangeFee; + type TradingPathLimit = ConstU32<4>; type PalletId = DEXPalletId; type Erc20InfoMapping = (); type DEXIncentives = (); diff --git a/modules/dex/Cargo.toml b/modules/dex/Cargo.toml index 958c7ab0df..e319a9710e 100644 --- a/modules/dex/Cargo.toml +++ b/modules/dex/Cargo.toml @@ -9,20 +9,19 @@ serde = { version = "1.0.136", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } scale-info = { version = "2.1", default-features = false, features = ["derive"] } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } -frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26", default-features = false } orml-traits = { path = "../../orml/traits", default-features = false } -orml-utilities = { path = "../../orml/utilities", default-features = false } - support = { package = "module-support", path = "../support", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } [dev-dependencies] sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } + orml-tokens = { path = "../../orml/tokens" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } @@ -32,13 +31,12 @@ std = [ "serde", "codec/std", "scale-info/std", + "frame-support/std", + "frame-system/std", "sp-core/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", "orml-traits/std", - "orml-utilities/std", "support/std", "primitives/std", ] diff --git a/modules/dex/src/mock.rs b/modules/dex/src/mock.rs index 59c38f006f..69aee2a56d 100644 --- a/modules/dex/src/mock.rs +++ b/modules/dex/src/mock.rs @@ -122,7 +122,6 @@ ord_parameter_types! { parameter_types! { pub const GetExchangeFee: (u32, u32) = (1, 100); pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); - pub const TreasuryPallet: PalletId = PalletId(*b"aca/trsy"); pub AlternativeSwapPathJointList: Vec> = vec![ vec![DOT], ]; diff --git a/modules/dex/src/tests.rs b/modules/dex/src/tests.rs index bfd9c05ebd..7cec53b86b 100644 --- a/modules/dex/src/tests.rs +++ b/modules/dex/src/tests.rs @@ -24,7 +24,7 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use mock::{ ACAJointSwap, AUSDBTCPair, AUSDDOTPair, AUSDJointSwap, DOTBTCPair, DexModule, Event, ExtBuilder, ListingOrigin, - Origin, Runtime, System, Tokens, ACA, ALICE, AUSD, AUSD_DOT_POOL_RECORD, BOB, BTC, CAROL, DOT, *, + Origin, Runtime, System, Tokens, ACA, ALICE, AUSD, AUSD_DOT_POOL_RECORD, BOB, BTC, CAROL, DOT, }; use orml_traits::MultiReservableCurrency; use sp_core::H160; diff --git a/runtime/mandala/src/benchmarking/aggregated_dex.rs b/runtime/mandala/src/benchmarking/aggregated_dex.rs new file mode 100644 index 0000000000..da5521ecee --- /dev/null +++ b/runtime/mandala/src/benchmarking/aggregated_dex.rs @@ -0,0 +1,95 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2022 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::utils::{dollar, set_balance}; +use crate::{ + AccountId, Balance, CurrencyId, Dex, GetLiquidCurrencyId, GetNativeCurrencyId, GetStableCurrencyId, + GetStakingCurrencyId, Runtime, +}; +use frame_benchmarking::account; +use frame_system::RawOrigin; +use primitives::TokenSymbol; +use sp_runtime::traits::UniqueSaturatedInto; + +const SEED: u32 = 0; + +const STABLECOIN: CurrencyId = GetStableCurrencyId::get(); +const STAKINGCOIN: CurrencyId = GetStakingCurrencyId::get(); +const NATIVECOIN: CurrencyId = GetNativeCurrencyId::get(); +const LIQUIDCOIN: CurrencyId = GetLiquidCurrencyId::get(); + +fn inject_liquidity( + maker: AccountId, + currency_id_a: CurrencyId, + currency_id_b: CurrencyId, + max_amount_a: Balance, + max_amount_b: Balance, +) -> Result<(), &'static str> { + // set balance + set_balance(currency_id_a, &maker, max_amount_a.unique_saturated_into()); + set_balance(currency_id_b, &maker, max_amount_b.unique_saturated_into()); + + let _ = Dex::enable_trading_pair(RawOrigin::Root.into(), currency_id_a, currency_id_b); + + Dex::add_liquidity( + RawOrigin::Signed(maker.clone()).into(), + currency_id_a, + currency_id_b, + max_amount_a, + max_amount_b, + Default::default(), + false, + )?; + + Ok(()) +} + +runtime_benchmarks! { + { Runtime, module_aggregated_dex } + + set_rebalance_swap_info { + let supply: Balance = 100_000_000_000_000; + let threshold: Balance = 110_000_000_000_000; + + }: _(RawOrigin::Root, STABLECOIN, supply, threshold) + + rebalance_swap { + let funder: AccountId = account("funder", 0, SEED); + + let _ = inject_liquidity(funder.clone(), STABLECOIN, STAKINGCOIN, 100 * dollar(STABLECOIN), 200 * dollar(STAKINGCOIN)); + let _ = inject_liquidity(funder.clone(), STAKINGCOIN, NATIVECOIN, 100 * dollar(STAKINGCOIN), 200 * dollar(NATIVECOIN)); + }: _(RawOrigin::None, STABLECOIN, STAKINGCOIN, STAKINGCOIN) + + set_trading_pair_nodes { + let funder: AccountId = account("funder", 0, SEED); + + let _ = inject_liquidity(funder.clone(), STABLECOIN, STAKINGCOIN, 100 * dollar(STABLECOIN), 200 * dollar(STAKINGCOIN)); + let _ = inject_liquidity(funder.clone(), STAKINGCOIN, NATIVECOIN, 100 * dollar(STAKINGCOIN), 200 * dollar(NATIVECOIN)); + let _ = inject_liquidity(funder.clone(), STABLECOIN, NATIVECOIN, 100 * dollar(STABLECOIN), 200 * dollar(NATIVECOIN)); + let _ = inject_liquidity(funder.clone(), STABLECOIN, LIQUIDCOIN, 100 * dollar(STABLECOIN), 200 * dollar(LIQUIDCOIN)); + }: _(RawOrigin::Root) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::benchmarking::utils::tests::new_test_ext; + use orml_benchmarking::impl_benchmark_test_suite; + + impl_benchmark_test_suite!(new_test_ext(),); +} diff --git a/runtime/mandala/src/benchmarking/mod.rs b/runtime/mandala/src/benchmarking/mod.rs index f7ffc34e99..49c9547410 100644 --- a/runtime/mandala/src/benchmarking/mod.rs +++ b/runtime/mandala/src/benchmarking/mod.rs @@ -26,6 +26,7 @@ use sp_std::prelude::*; pub mod utils; // module benchmarking +pub mod aggregated_dex; pub mod asset_registry; pub mod auction_manager; pub mod cdp_engine; diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 8a99718dcf..67e54105c9 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -2102,10 +2102,11 @@ extern crate orml_benchmarking; #[cfg(feature = "runtime-benchmarks")] mod benches { define_benchmarks!( - [module_dex, benchmarking::dex] - [module_dex_oracle, benchmarking::dex_oracle] [module_asset_registry, benchmarking::asset_registry] + [module_aggregated_dex, benchmarking::aggregated_dex] [module_auction_manager, benchmarking::auction_manager] + [module_dex, benchmarking::dex] + [module_dex_oracle, benchmarking::dex_oracle] [module_cdp_engine, benchmarking::cdp_engine] [module_earning, benchmarking::earning] [module_emergency_shutdown, benchmarking::emergency_shutdown] From bfc1685a0551e27b9318f802862caae0c1f7851c Mon Sep 17 00:00:00 2001 From: zqh Date: Wed, 3 Aug 2022 00:28:52 +0800 Subject: [PATCH 13/20] fix test --- modules/aggregated-dex/src/tests.rs | 32 ----------------------------- 1 file changed, 32 deletions(-) diff --git a/modules/aggregated-dex/src/tests.rs b/modules/aggregated-dex/src/tests.rs index 3a36663879..5bb000f143 100644 --- a/modules/aggregated-dex/src/tests.rs +++ b/modules/aggregated-dex/src/tests.rs @@ -1322,38 +1322,6 @@ fn aggregated_swap_swap_work() { }); } -// fn inject_liquidity( -// account: AccountId, -// currency_id_a: CurrencyId, -// currency_id_b: CurrencyId, -// max_amount_a: Balance, -// max_amount_b: Balance, -// ) -> Result<(), &'static str> { -// let _ = Dex::enable_trading_pair(Origin::root(), currency_id_a, currency_id_b); -// assert_ok!(Currencies::update_balance( -// Origin::root(), -// MultiAddress::Id(account.clone()), -// currency_id_a.clone(), -// max_amount_a, -// )); -// assert_ok!(Currencies::update_balance( -// Origin::root(), -// MultiAddress::Id(account.clone()), -// currency_id_b.clone(), -// max_amount_b, -// )); -// Dex::add_liquidity( -// Origin::signed(account), -// currency_id_a, -// currency_id_b, -// max_amount_a, -// max_amount_b, -// Default::default(), -// false, -// )?; -// Ok(()) -// } - fn inject_liquidity_default_pairs() { assert_ok!(inject_liquidity(AUSD, DOT, 1_000_000u128, 2_000_000u128)); assert_ok!(inject_liquidity(AUSD, BTC, 1_000_000u128, 2_000_000u128)); From c251695258a97cc187fa219117c21cb9977cabaa Mon Sep 17 00:00:00 2001 From: zqh Date: Mon, 8 Aug 2022 16:05:24 +0800 Subject: [PATCH 14/20] use treemap computation instead of on-chain storage --- modules/aggregated-dex/src/lib.rs | 111 +++++++---------------- modules/aggregated-dex/src/mock.rs | 2 - modules/aggregated-dex/src/tests.rs | 70 +------------- modules/transaction-payment/src/mock.rs | 15 +-- modules/transaction-payment/src/tests.rs | 30 ++++++ runtime/acala/src/lib.rs | 3 - runtime/karura/src/lib.rs | 3 - runtime/mandala/src/lib.rs | 3 - 8 files changed, 71 insertions(+), 166 deletions(-) diff --git a/modules/aggregated-dex/src/lib.rs b/modules/aggregated-dex/src/lib.rs index 52c53588c7..a5252604b6 100644 --- a/modules/aggregated-dex/src/lib.rs +++ b/modules/aggregated-dex/src/lib.rs @@ -22,6 +22,7 @@ #![allow(clippy::unused_unit)] #![allow(clippy::type_complexity)] +use codec::Decode; use frame_support::{pallet_prelude::*, transactional, PalletId}; use frame_system::{ offchain::{SendTransactionTypes, SubmitTransaction}, @@ -88,14 +89,6 @@ pub mod module { #[pallet::constant] type SwapPathLimit: Get; - /// The limit of one Currency to all other direct trading token. - #[pallet::constant] - type SingleTokenTradingLimit: Get; - - /// The frequency block number to update `TradingPairNodes`. - #[pallet::constant] - type TradingKeysUpdateFrequency: Get; - /// Treasury account participate in Rebalance swap. #[pallet::constant] type TreasuryPallet: Get; @@ -147,14 +140,6 @@ pub mod module { pub type AggregatedSwapPaths = StorageMap<_, Twox64Concat, (CurrencyId, CurrencyId), BoundedVec, OptionQuery>; - /// Direct trading pair token list of specify CurrencyId. - /// - /// TradingPairNodes: map CurrencyId => vec![CurrencyId] - #[pallet::storage] - #[pallet::getter(fn trading_pair_nodes)] - pub type TradingPairNodes = - StorageMap<_, Twox64Concat, CurrencyId, BoundedVec, ValueQuery>; - #[pallet::storage] #[pallet::getter(fn rebalance_supply_threshold)] pub type RebalanceSupplyThreshold = @@ -182,16 +167,6 @@ pub mod module { ); } } - - fn on_idle(now: T::BlockNumber, mut remaining_weight: Weight) -> Weight { - // update `TradingPairNodes` every `TradingKeysUpdateFrequency` interval. - if now % T::TradingKeysUpdateFrequency::get() == Zero::zero() { - let _ = Self::do_set_trading_pair_nodes(); - let used = ::WeightInfo::set_trading_pair_nodes(); - remaining_weight = remaining_weight.saturating_sub(used); - } - remaining_weight - } } #[pallet::call] @@ -296,13 +271,6 @@ pub mod module { ensure_none(origin)?; Self::do_rebalance_swap((currency_1, currency_2, currency_3)) } - - #[pallet::weight(::WeightInfo::set_trading_pair_nodes())] - #[transactional] - pub fn set_trading_pair_nodes(origin: OriginFor) -> DispatchResult { - T::GovernanceOrigin::ensure_origin(origin)?; - Self::do_set_trading_pair_nodes() - } } #[pallet::validate_unsigned] @@ -338,10 +306,6 @@ impl Pallet { T::TreasuryPallet::get().into_account_truncating() } - pub fn get_trading_pair_keys() -> Vec { - TradingPairNodes::::iter_keys().collect() - } - fn check_swap_paths(paths: &[SwapPath]) -> sp_std::result::Result<(CurrencyId, CurrencyId), DispatchError> { ensure!(!paths.is_empty(), Error::::InvalidSwapPath); let mut supply_currency_id: Option = None; @@ -557,10 +521,6 @@ impl Pallet { } fn _offchain_worker(_now: T::BlockNumber) -> Result<(), OffchainErr> { - if Self::get_trading_pair_keys().len().is_zero() { - return Ok(()); - } - // acquire offchain worker lock let lock_expiration = Duration::from_millis(LOCK_DURATION); let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); @@ -571,23 +531,47 @@ impl Pallet { .unwrap_or(Some(DEFAULT_MAX_ITERATIONS)) .unwrap_or(DEFAULT_MAX_ITERATIONS); let mut to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); - let start_key = to_be_continue.get::>().unwrap_or_default(); - let mut iterator = match start_key.clone() { - Some(key) => TradingPairNodes::::iter_from(key), - None => TradingPairNodes::::iter(), - }; + + let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); + let mut first_currency: Option = None; + TradingPairStatuses::::iter() + .filter(|(_, status)| status.enabled()) + .for_each(|(pair, _)| { + trading_pair_values_map + .entry(pair.first()) + .or_insert_with(Vec::::new) + .push(pair.second()); + if first_currency.is_none() { + first_currency = Some(pair.first()); + } + }); + let mut iterator = trading_pair_values_map.iter(); let mut finished = true; let mut iteration_count = 0; + let mut last_currency_id: Option = None; let iteration_start_time = sp_io::offchain::timestamp(); #[allow(clippy::while_let_on_iterator)] 'outer: while let Some((currency_id, trading_tokens)) = iterator.next() { + // BTreeMap don't have `iter_from(key)`, use compare here to ignore previous processed token. + match start_key.clone() { + Some(key) => { + let starter_key = CurrencyId::decode(&mut &*key).map_err(|_| OffchainErr::OffchainLock)?; + if *currency_id < starter_key { + continue; + } + } + None => {} + }; + let len = trading_tokens.len(); if len < 2 { continue; } + // update last processing CurrencyId + last_currency_id = Some(*currency_id); for i in 0..(len - 1) { for j in (i + 1)..len { iteration_count += 1; @@ -596,7 +580,7 @@ impl Pallet { if TradingPairStatuses::::contains_key(&pair) { let pair_status = TradingPairStatuses::::get(&pair); if pair_status.enabled() { - Self::submit_rebalance_swap_tx(currency_id, pair.first(), pair.second()); + Self::submit_rebalance_swap_tx(*currency_id, pair.first(), pair.second()); } } } @@ -630,7 +614,10 @@ impl Pallet { if finished { to_be_continue.clear(); } else { - to_be_continue.set(&iterator.last_raw_key()); + match last_currency_id { + Some(last_currency_id) => to_be_continue.set(&last_currency_id.encode()), + None => to_be_continue.clear(), + } } // Consume the guard but **do not** unlock the underlying lock. @@ -696,34 +683,6 @@ impl Pallet { } Ok(()) } - - fn do_set_trading_pair_nodes() -> DispatchResult { - let mut trading_pair_values_map: BTreeMap> = BTreeMap::new(); - TradingPairStatuses::::iter() - .filter(|(_, status)| status.enabled()) - .for_each(|(pair, _)| { - trading_pair_values_map - .entry(pair.first()) - .or_insert_with(Vec::::new) - .push(pair.second()); - }); - for (currency_id, trading_tokens) in trading_pair_values_map { - let _ = TradingPairNodes::::try_mutate(currency_id, |maybe_trading_tokens| -> DispatchResult { - let trading_tokens: Result, _> = - trading_tokens.try_into(); - match trading_tokens { - Ok(trading_tokens) => { - *maybe_trading_tokens = trading_tokens; - } - _ => { - log::debug!(target: "dex-bot", "Too many trading pair for token:{:?}.", currency_id); - } - } - Ok(()) - }); - } - Ok(()) - } } /// Swap by Acala DEX which has specific joints. diff --git a/modules/aggregated-dex/src/mock.rs b/modules/aggregated-dex/src/mock.rs index ad992dcb3d..b7f479d239 100644 --- a/modules/aggregated-dex/src/mock.rs +++ b/modules/aggregated-dex/src/mock.rs @@ -209,8 +209,6 @@ impl Config for Runtime { type DexSwapJointList = DexSwapJointList; type SwapPathLimit = ConstU32<3>; type TreasuryPallet = TreasuryPalletId; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = ConstU64<1>; type UnsignedPriority = ConstU64<1048576>; // 1 << 20 type WeightInfo = (); } diff --git a/modules/aggregated-dex/src/tests.rs b/modules/aggregated-dex/src/tests.rs index 5bb000f143..09da7ea9f3 100644 --- a/modules/aggregated-dex/src/tests.rs +++ b/modules/aggregated-dex/src/tests.rs @@ -32,13 +32,8 @@ use sp_io::offchain; use sp_runtime::traits::BadOrigin; use std::sync::Arc; -fn run_to_block_offchain(n: u64, execute_on_idle: bool) { - let weight: Weight = 1000; +fn run_to_block_offchain(n: u64) { while System::block_number() < n { - if execute_on_idle { - AggregatedDex::on_idle(n, weight); - } - System::set_block_number(System::block_number() + 1); AggregatedDex::offchain_worker(System::block_number()); // this unlocks the concurrency storage lock so offchain_worker will fire next block @@ -1341,33 +1336,7 @@ fn offchain_worker_max_iteration_works() { System::set_block_number(1); inject_liquidity_default_pairs(); - let keys: Vec = TradingPairNodes::::iter_keys().collect(); - assert_eq!(keys, vec![]); - trigger_unsigned_rebalance_swap(2, pool_state.clone(), None); - // run_to_block_offchain(2); - // // initialize `TradingPairNodes` - // let keys: Vec = TradingPairNodes::::iter_keys().collect(); - // assert_eq!(keys, vec![DOT, AUSD]); - // - // // trigger unsigned tx - // let tx = pool_state.write().transactions.pop().unwrap(); - // let tx = Extrinsic::decode(&mut &*tx).unwrap(); - // if let MockCall::AggregatedDex(crate::Call::rebalance_swap { - // currency_1, - // currency_2, - // currency_3, - // }) = tx.call - // { - // assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); - // assert_ok!(AggregatedDex::rebalance_swap( - // Origin::none(), - // currency_1, - // currency_2, - // currency_3 - // )); - // } - // assert!(pool_state.write().transactions.pop().is_none()); let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); let start_key = to_be_continue.get::>().unwrap_or_default(); @@ -1377,37 +1346,10 @@ fn offchain_worker_max_iteration_works() { offchain.local_storage_set(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS, &1u32.encode()); trigger_unsigned_rebalance_swap(3, pool_state.clone(), None); - // run_to_block_offchain(3); - // let keys: Vec = TradingPairNodes::::iter_keys().collect(); - // assert_eq!(keys, vec![DOT, AUSD]); - // - // let tx = pool_state.write().transactions.pop().unwrap(); - // let tx = Extrinsic::decode(&mut &*tx).unwrap(); - // if let MockCall::AggregatedDex(crate::Call::rebalance_swap { - // currency_1, - // currency_2, - // currency_3, - // }) = tx.call - // { - // assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); - // assert_ok!(AggregatedDex::rebalance_swap( - // Origin::none(), - // currency_1, - // currency_2, - // currency_3 - // )); - // } - // assert!(pool_state.write().transactions.pop().is_none()); - - // iterator last_saw_key - let mut iter = TradingPairNodes::::iter(); - let _ = iter.next(); // first currency is DOT - let _ = iter.next(); // second one is AUSD - let last_saw_key = iter.last_raw_key(); - let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); - let start_key = to_be_continue.get::>().unwrap_or_default(); - assert_eq!(start_key, Some(last_saw_key.to_vec())); + let start_key = to_be_continue.get::>().unwrap_or_default().unwrap(); + let to_be_continue_currency = CurrencyId::decode(&mut &*start_key).unwrap(); + assert_eq!(to_be_continue_currency, AUSD); }); } @@ -1453,9 +1395,7 @@ fn offchain_worker_trigger_unsigned_rebalance_swap() { fn trigger_unsigned_rebalance_swap(n: u64, pool_state: Arc>, actual_target_amount: Option) { System::reset_events(); - run_to_block_offchain(n, true); - let keys: Vec = TradingPairNodes::::iter_keys().collect(); - assert_eq!(keys, vec![DOT, AUSD]); + run_to_block_offchain(n); // trigger unsigned tx let tx = pool_state.write().transactions.pop().unwrap(); diff --git a/modules/transaction-payment/src/mock.rs b/modules/transaction-payment/src/mock.rs index 1effc0018a..d97b489d47 100644 --- a/modules/transaction-payment/src/mock.rs +++ b/modules/transaction-payment/src/mock.rs @@ -39,10 +39,7 @@ use sp_runtime::{ Perbill, }; use sp_std::cell::RefCell; -use support::{ - mocks::{MockAddressMapping, MockStableAsset}, - Price, SpecificJointsSwap, -}; +use support::{mocks::MockAddressMapping, Price, SpecificJointsSwap}; pub type AccountId = AccountId32; pub type BlockNumber = u64; @@ -194,16 +191,6 @@ impl module_dex::Config for Runtime { type OnLiquidityPoolUpdated = (); } -impl module_aggregated_dex::Config for Runtime { - type Event = Event; - type DEX = DEXModule; - type StableAsset = MockStableAsset; - type GovernanceOrigin = EnsureSignedBy; - type DexSwapJointList = AlternativeSwapPathJointList; - type SwapPathLimit = ConstU32<3>; - type WeightInfo = (); -} - parameter_types! { pub MaxSwapSlippageCompareToOracle: Ratio = Ratio::saturating_from_rational(1, 2); pub static TransactionByteFee: u128 = 1; diff --git a/modules/transaction-payment/src/tests.rs b/modules/transaction-payment/src/tests.rs index 8bbed11e61..6ac7296020 100644 --- a/modules/transaction-payment/src/tests.rs +++ b/modules/transaction-payment/src/tests.rs @@ -2242,3 +2242,33 @@ fn with_fee_call_validation_works() { ); }); } + +#[test] +fn compare_currency() { + use std::cmp::Ordering; + + let c1 = CurrencyId::Token(TokenSymbol::ACA); + let c2 = CurrencyId::Token(TokenSymbol::AUSD); + let c3 = CurrencyId::Token(TokenSymbol::DOT); + + cmp_cur(c1, c2); + cmp_cur(c2, c1); + cmp_cur(c1, c3); + cmp_cur(c3, c1); + cmp_cur(c2, c3); + cmp_cur(c2, c2); + + fn cmp_cur(c1: CurrencyId, c2: CurrencyId) { + match c1.cmp(&c2) { + Ordering::Equal => { + println!("==!"); + } + Ordering::Less => { + println!("<-!"); + } + Ordering::Greater => { + println!(">-!"); + } + } + } +} diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 16bda66994..3372181d05 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1087,7 +1087,6 @@ impl module_emergency_shutdown::Config for Runtime { parameter_types! { pub const GetExchangeFee: (u32, u32) = (3, 1000); // 0.3% pub const ExtendedProvisioningBlocks: BlockNumber = 2 * DAYS; - pub const TradingKeysUpdateFrequency: BlockNumber = 2 * DAYS; pub const TradingPathLimit: u32 = 4; } @@ -1112,8 +1111,6 @@ impl module_aggregated_dex::Config for Runtime { type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; type DexSwapJointList = AlternativeSwapPathJointList; type SwapPathLimit = ConstU32<3>; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; type TreasuryPallet = TreasuryPalletId; type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type WeightInfo = (); diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index a9cd23682f..5d23f864ee 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1093,7 +1093,6 @@ impl module_emergency_shutdown::Config for Runtime { parameter_types! { pub const GetExchangeFee: (u32, u32) = (3, 1000); // 0.3% pub const ExtendedProvisioningBlocks: BlockNumber = 2 * DAYS; - pub const TradingKeysUpdateFrequency: BlockNumber = 2 * DAYS; pub const TradingPathLimit: u32 = 4; } @@ -1118,8 +1117,6 @@ impl module_aggregated_dex::Config for Runtime { type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; type DexSwapJointList = AlternativeSwapPathJointList; type SwapPathLimit = ConstU32<3>; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; type TreasuryPallet = TreasuryPalletId; type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type WeightInfo = (); diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index 67e54105c9..ba8cec4198 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1146,7 +1146,6 @@ parameter_types! { TradingPair::from_currency_ids(DOT, ACA).unwrap(), ]; pub const ExtendedProvisioningBlocks: BlockNumber = 2 * DAYS; - pub const TradingKeysUpdateFrequency: BlockNumber = 2 * DAYS; pub const TradingPathLimit: u32 = 4; pub AlternativeSwapPathJointList: Vec> = vec![ vec![GetStakingCurrencyId::get()], @@ -1176,8 +1175,6 @@ impl module_aggregated_dex::Config for Runtime { type GovernanceOrigin = EnsureRootOrHalfGeneralCouncil; type DexSwapJointList = AlternativeSwapPathJointList; type SwapPathLimit = ConstU32<3>; - type SingleTokenTradingLimit = ConstU32<10>; - type TradingKeysUpdateFrequency = TradingKeysUpdateFrequency; type TreasuryPallet = TreasuryPalletId; type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; type WeightInfo = (); From 5ac9fe17efcf2be5100fbf137627ce347cecd540 Mon Sep 17 00:00:00 2001 From: zqh Date: Mon, 8 Aug 2022 17:40:12 +0800 Subject: [PATCH 15/20] update benchmark --- modules/aggregated-dex/src/lib.rs | 8 ++++---- modules/aggregated-dex/src/tests.rs | 4 ++-- modules/aggregated-dex/src/weights.rs | 15 +++------------ .../mandala/src/benchmarking/aggregated_dex.rs | 12 +----------- 4 files changed, 10 insertions(+), 29 deletions(-) diff --git a/modules/aggregated-dex/src/lib.rs b/modules/aggregated-dex/src/lib.rs index a5252604b6..6cd034fc23 100644 --- a/modules/aggregated-dex/src/lib.rs +++ b/modules/aggregated-dex/src/lib.rs @@ -260,9 +260,9 @@ pub mod module { Self::do_set_rebalance_swap_info(currency_id, supply_amount, threshold) } - #[pallet::weight(::WeightInfo::rebalance_swap())] + #[pallet::weight(::WeightInfo::force_rebalance_swap())] #[transactional] - pub fn rebalance_swap( + pub fn force_rebalance_swap( origin: OriginFor, currency_1: CurrencyId, currency_2: CurrencyId, @@ -277,7 +277,7 @@ pub mod module { impl ValidateUnsigned for Pallet { type Call = Call; fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - if let Call::rebalance_swap { + if let Call::force_rebalance_swap { currency_1, currency_2, currency_3, @@ -506,7 +506,7 @@ impl Pallet { } fn submit_rebalance_swap_tx(currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId) { - let call = Call::::rebalance_swap { + let call = Call::::force_rebalance_swap { currency_1, currency_2, currency_3, diff --git a/modules/aggregated-dex/src/tests.rs b/modules/aggregated-dex/src/tests.rs index 09da7ea9f3..ae7c8e5bc9 100644 --- a/modules/aggregated-dex/src/tests.rs +++ b/modules/aggregated-dex/src/tests.rs @@ -1400,14 +1400,14 @@ fn trigger_unsigned_rebalance_swap(n: u64, pool_state: Arc>, a // trigger unsigned tx let tx = pool_state.write().transactions.pop().unwrap(); let tx = Extrinsic::decode(&mut &*tx).unwrap(); - if let MockCall::AggregatedDex(crate::Call::rebalance_swap { + if let MockCall::AggregatedDex(crate::Call::force_rebalance_swap { currency_1, currency_2, currency_3, }) = tx.call { assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); - assert_ok!(AggregatedDex::rebalance_swap( + assert_ok!(AggregatedDex::force_rebalance_swap( Origin::none(), currency_1, currency_2, diff --git a/modules/aggregated-dex/src/weights.rs b/modules/aggregated-dex/src/weights.rs index 3cf1e67494..7a28b6ff01 100644 --- a/modules/aggregated-dex/src/weights.rs +++ b/modules/aggregated-dex/src/weights.rs @@ -48,9 +48,8 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn swap_with_exact_supply(u: u32, ) -> Weight; fn update_aggregated_swap_paths(u: u32, ) -> Weight; - fn rebalance_swap() -> Weight; + fn force_rebalance_swap() -> Weight; fn set_rebalance_swap_info() -> Weight; - fn set_trading_pair_nodes() -> Weight; } /// Weights for module_aggregated_dex using the Acala node and recommended hardware. @@ -73,17 +72,13 @@ impl WeightInfo for AcalaWeight { .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(u as Weight))) } - fn rebalance_swap() -> Weight { + fn force_rebalance_swap() -> Weight { 2_268_000 } fn set_rebalance_swap_info() -> Weight { 2_268_000 } - - fn set_trading_pair_nodes() -> Weight { - 2_268_000 - } } // For backwards compatibility and tests @@ -105,15 +100,11 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(u as Weight))) } - fn rebalance_swap() -> Weight { + fn force_rebalance_swap() -> Weight { 2_268_000 } fn set_rebalance_swap_info() -> Weight { 2_268_000 } - - fn set_trading_pair_nodes() -> Weight { - 2_268_000 - } } diff --git a/runtime/mandala/src/benchmarking/aggregated_dex.rs b/runtime/mandala/src/benchmarking/aggregated_dex.rs index da5521ecee..e053c2faa7 100644 --- a/runtime/mandala/src/benchmarking/aggregated_dex.rs +++ b/runtime/mandala/src/benchmarking/aggregated_dex.rs @@ -23,7 +23,6 @@ use crate::{ }; use frame_benchmarking::account; use frame_system::RawOrigin; -use primitives::TokenSymbol; use sp_runtime::traits::UniqueSaturatedInto; const SEED: u32 = 0; @@ -68,21 +67,12 @@ runtime_benchmarks! { }: _(RawOrigin::Root, STABLECOIN, supply, threshold) - rebalance_swap { + force_rebalance_swap { let funder: AccountId = account("funder", 0, SEED); let _ = inject_liquidity(funder.clone(), STABLECOIN, STAKINGCOIN, 100 * dollar(STABLECOIN), 200 * dollar(STAKINGCOIN)); let _ = inject_liquidity(funder.clone(), STAKINGCOIN, NATIVECOIN, 100 * dollar(STAKINGCOIN), 200 * dollar(NATIVECOIN)); }: _(RawOrigin::None, STABLECOIN, STAKINGCOIN, STAKINGCOIN) - - set_trading_pair_nodes { - let funder: AccountId = account("funder", 0, SEED); - - let _ = inject_liquidity(funder.clone(), STABLECOIN, STAKINGCOIN, 100 * dollar(STABLECOIN), 200 * dollar(STAKINGCOIN)); - let _ = inject_liquidity(funder.clone(), STAKINGCOIN, NATIVECOIN, 100 * dollar(STAKINGCOIN), 200 * dollar(NATIVECOIN)); - let _ = inject_liquidity(funder.clone(), STABLECOIN, NATIVECOIN, 100 * dollar(STABLECOIN), 200 * dollar(NATIVECOIN)); - let _ = inject_liquidity(funder.clone(), STABLECOIN, LIQUIDCOIN, 100 * dollar(STABLECOIN), 200 * dollar(LIQUIDCOIN)); - }: _(RawOrigin::Root) } #[cfg(test)] From 46b7f8a7097a3481ef8b0194106ae816c6c922d7 Mon Sep 17 00:00:00 2001 From: zqh Date: Fri, 12 Aug 2022 22:15:10 +0800 Subject: [PATCH 16/20] add aggregated dex rebalance swap --- modules/aggregated-dex/src/lib.rs | 140 ++++++++++------ modules/aggregated-dex/src/tests.rs | 245 ++++++++++++++++++++++++---- 2 files changed, 298 insertions(+), 87 deletions(-) diff --git a/modules/aggregated-dex/src/lib.rs b/modules/aggregated-dex/src/lib.rs index 6cd034fc23..4bed13a514 100644 --- a/modules/aggregated-dex/src/lib.rs +++ b/modules/aggregated-dex/src/lib.rs @@ -118,11 +118,10 @@ pub mod module { pub enum Event { /// Rebalance trading path and balance. RebalanceTrading { - currency_1: CurrencyId, - currency_2: CurrencyId, - currency_3: CurrencyId, + currency_id: CurrencyId, supply_amount: Balance, target_amount: Balance, + swap_path: Vec, }, /// Add rebalance info. SetupRebalanceSwapInfo { @@ -132,7 +131,7 @@ pub mod module { }, } - /// The specific swap paths for AggregatedSwap do aggreated_swap to swap TokenA to TokenB + /// The specific swap paths for AggregatedSwap do aggregated_swap to swap TokenA to TokenB /// /// AggregatedSwapPaths: Map: (token_a: CurrencyId, token_b: CurrencyId) => paths: Vec #[pallet::storage] @@ -140,6 +139,14 @@ pub mod module { pub type AggregatedSwapPaths = StorageMap<_, Twox64Concat, (CurrencyId, CurrencyId), BoundedVec, OptionQuery>; + /// The specific rebalance swap paths doing aggregated_swap from TokenA to TokenA + /// + /// AggregatedSwapPaths: Map: CurrencyId => paths: Vec + #[pallet::storage] + #[pallet::getter(fn rebalance_swap_paths)] + pub type RebalanceSwapPaths = + StorageMap<_, Twox64Concat, CurrencyId, BoundedVec, OptionQuery>; + #[pallet::storage] #[pallet::getter(fn rebalance_supply_threshold)] pub type RebalanceSupplyThreshold = @@ -248,6 +255,38 @@ pub mod module { Ok(()) } + /// Update the rebalance swap paths for AggregatedSwap to swap TokenA to TokenA. + /// + /// Requires `GovernanceOrigin` + /// + /// Parameters: + /// - `updates`: Vec<(Token, Option>)> + #[pallet::weight(::WeightInfo::update_aggregated_swap_paths(updates.len() as u32))] + #[transactional] + pub fn update_rebalance_swap_paths( + origin: OriginFor, + updates: Vec<(CurrencyId, Option>)>, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + for (key, maybe_paths) in updates { + if let Some(paths) = maybe_paths { + let paths: BoundedVec = + paths.try_into().map_err(|_| Error::::InvalidSwapPath)?; + let (supply_currency_id, target_currency_id) = Self::check_swap_paths(&paths)?; + ensure!( + key == supply_currency_id && supply_currency_id == target_currency_id, + Error::::InvalidSwapPath + ); + RebalanceSwapPaths::::insert(key, paths); + } else { + RebalanceSwapPaths::::remove(key); + } + } + + Ok(()) + } + #[pallet::weight(::WeightInfo::set_rebalance_swap_info())] #[transactional] pub fn set_rebalance_swap_info( @@ -264,12 +303,11 @@ pub mod module { #[transactional] pub fn force_rebalance_swap( origin: OriginFor, - currency_1: CurrencyId, - currency_2: CurrencyId, - currency_3: CurrencyId, + currency_id: CurrencyId, + swap_path: Vec, ) -> DispatchResult { ensure_none(origin)?; - Self::do_rebalance_swap((currency_1, currency_2, currency_3)) + Self::do_rebalance_swap(currency_id, swap_path) } } @@ -278,19 +316,13 @@ pub mod module { type Call = Call; fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { if let Call::force_rebalance_swap { - currency_1, - currency_2, - currency_3, + currency_id, + swap_path: _, } = call { ValidTransaction::with_tag_prefix("DexBotOffchainWorker") .priority(T::UnsignedPriority::get()) - .and_provides(( - >::block_number(), - currency_1, - currency_2, - currency_3, - )) + .and_provides((>::block_number(), currency_id)) .longevity(64_u64) .propagate(true) .build() @@ -505,17 +537,13 @@ impl Pallet { } } - fn submit_rebalance_swap_tx(currency_1: CurrencyId, currency_2: CurrencyId, currency_3: CurrencyId) { - let call = Call::::force_rebalance_swap { - currency_1, - currency_2, - currency_3, - }; + fn submit_rebalance_swap_tx(currency_id: CurrencyId, swap_path: Vec) { + let call = Call::::force_rebalance_swap { currency_id, swap_path }; if let Err(err) = SubmitTransaction::>::submit_unsigned_transaction(call.into()) { log::info!( target: "dex-bot", - "offchain worker: submit unsigned swap A={:?},B={:?},C={:?}, failed: {:?}", - currency_1, currency_2, currency_3, err, + "offchain worker: submit unsigned swap from currency:{:?}, failed: {:?}", + currency_id, err, ); } } @@ -553,6 +581,7 @@ impl Pallet { let mut last_currency_id: Option = None; let iteration_start_time = sp_io::offchain::timestamp(); + // processing pure dex rebalance swap #[allow(clippy::while_let_on_iterator)] 'outer: while let Some((currency_id, trading_tokens)) = iterator.next() { // BTreeMap don't have `iter_from(key)`, use compare here to ignore previous processed token. @@ -580,7 +609,12 @@ impl Pallet { if TradingPairStatuses::::contains_key(&pair) { let pair_status = TradingPairStatuses::::get(&pair); if pair_status.enabled() { - Self::submit_rebalance_swap_tx(*currency_id, pair.first(), pair.second()); + let first_path: Vec = vec![*currency_id, pair.first(), pair.second()]; + let second_path: Vec = vec![pair.second(), *currency_id]; + let swap_path = vec![SwapPath::Dex(first_path), SwapPath::Dex(second_path)]; + // TODO: do we need check `get_aggregated_swap_amount` large than supply amount before + // submit unsigned tx? + Self::submit_rebalance_swap_tx(*currency_id, swap_path); } } } @@ -597,6 +631,24 @@ impl Pallet { } } + // processing aggregated dex rebalance swap + if iteration_count < max_iterations { + for (currency_id, swap_path) in RebalanceSwapPaths::::iter() { + iteration_count += 1; + + Self::submit_rebalance_swap_tx(currency_id, swap_path.into_inner()); + + // inner iterator consider as iterations too. + if iteration_count == max_iterations { + finished = false; + break; + } + + // extend offchain worker lock + guard.extend_lock().map_err(|_| OffchainErr::OffchainLock)?; + } + } + let iteration_end_time = sp_io::offchain::timestamp(); log::debug!( target: "dex-bot", @@ -645,38 +697,18 @@ impl Pallet { } /// Rebalance swap of path: `A->B->C->A`, the final output should be large than input. - fn do_rebalance_swap(current: (CurrencyId, CurrencyId, CurrencyId)) -> DispatchResult { - if let Some((supply_amount, minimum_amount)) = RebalanceSupplyThreshold::::get(¤t.0) { - // A-B-C-A has two kind of swap: A-B/B-C-A or A-B-C/C-A, either one is ok. - let first_path: Vec = vec![current.0, current.1, current.2]; - let second_path: Vec = vec![current.2, current.0]; - let supply_1 = SwapLimit::ExactSupply(supply_amount, 0); - - let mut valid_swap = false; - if let Some((_, target_3)) = T::DEX::get_swap_amount(&first_path, supply_1) { - if let Some((_, _)) = - T::DEX::get_swap_amount(&second_path, SwapLimit::ExactSupply(target_3, minimum_amount)) - { - valid_swap = true; - } - } - if !valid_swap { - return Ok(()); - } + fn do_rebalance_swap(currency_id: CurrencyId, swap_path: Vec) -> DispatchResult { + if let Some((supply_amount, minimum_amount)) = RebalanceSupplyThreshold::::get(¤cy_id) { + let supply = SwapLimit::ExactSupply(supply_amount, 0); + if let Some((_, target_amount)) = Pallet::::get_aggregated_swap_amount(&swap_path, supply) { + if target_amount > minimum_amount { + Pallet::::do_aggregated_swap(&Self::treasury_account(), &swap_path, supply)?; - if let Ok((_, target_3)) = T::DEX::swap_with_specific_path(&Self::treasury_account(), &first_path, supply_1) - { - if let Ok((_, target_amount)) = T::DEX::swap_with_specific_path( - &Self::treasury_account(), - &second_path, - SwapLimit::ExactSupply(target_3, minimum_amount), - ) { Self::deposit_event(Event::RebalanceTrading { - currency_1: current.0, - currency_2: current.1, - currency_3: current.2, + currency_id, supply_amount, target_amount, + swap_path, }); } } diff --git a/modules/aggregated-dex/src/tests.rs b/modules/aggregated-dex/src/tests.rs index ae7c8e5bc9..ddd9e09db4 100644 --- a/modules/aggregated-dex/src/tests.rs +++ b/modules/aggregated-dex/src/tests.rs @@ -697,25 +697,43 @@ fn check_swap_paths_work() { ); assert_ok!(initial_taiga_dot_ldot_pool()); - assert_ok!(AggregatedDex::check_swap_paths(&vec![SwapPath::Taiga(0, 0, 1)])); + assert_noop!( AggregatedDex::check_swap_paths(&vec![SwapPath::Taiga(0, 2, 0)]), Error::::InvalidTokenIndex ); - - assert_ok!(AggregatedDex::check_swap_paths(&vec![ - SwapPath::Taiga(0, 0, 1), - SwapPath::Dex(vec![LDOT, AUSD]) - ]),); assert_noop!( AggregatedDex::check_swap_paths(&vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![AUSD, LDOT])]), Error::::InvalidSwapPath ); - - assert_ok!(AggregatedDex::check_swap_paths(&vec![ - SwapPath::Dex(vec![AUSD, LDOT]), - SwapPath::Taiga(0, 1, 0) - ]),); + assert_eq!( + AggregatedDex::check_swap_paths(&vec![SwapPath::Taiga(0, 0, 1)]), + Ok((DOT, LDOT)) + ); + assert_eq!( + AggregatedDex::check_swap_paths(&vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![LDOT, AUSD])]), + Ok((DOT, AUSD)) + ); + assert_eq!( + AggregatedDex::check_swap_paths(&vec![SwapPath::Dex(vec![AUSD, LDOT]), SwapPath::Taiga(0, 1, 0)]), + Ok((AUSD, DOT)) + ); + assert_eq!( + AggregatedDex::check_swap_paths(&vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![LDOT, DOT]),]), + Ok((DOT, DOT)) + ); + assert_eq!( + AggregatedDex::check_swap_paths(&vec![SwapPath::Taiga(0, 1, 0), SwapPath::Dex(vec![DOT, LDOT]),]), + Ok((LDOT, LDOT)) + ); + assert_eq!( + AggregatedDex::check_swap_paths(&vec![SwapPath::Taiga(0, 1, 0), SwapPath::Taiga(0, 0, 1),]), + Ok((LDOT, LDOT)) + ); + assert_eq!( + AggregatedDex::check_swap_paths(&vec![SwapPath::Taiga(0, 0, 1), SwapPath::Taiga(0, 1, 0),]), + Ok((DOT, DOT)) + ); }); } @@ -750,6 +768,8 @@ fn get_aggregated_swap_amount_work() { 100_000_000_000u128, 20_000_000_000_000u128 )); + + // dex assert_eq!( AggregatedDex::get_aggregated_swap_amount( &vec![SwapPath::Dex(vec![AUSD, LDOT])], @@ -786,6 +806,7 @@ fn get_aggregated_swap_amount_work() { None ); + // taiga assert_ok!(initial_taiga_dot_ldot_pool()); assert_eq!( AggregatedDex::get_aggregated_swap_amount( @@ -816,6 +837,7 @@ fn get_aggregated_swap_amount_work() { None ); + // taiga + dex assert_eq!( AggregatedDex::get_aggregated_swap_amount( &vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![LDOT, AUSD])], @@ -845,6 +867,7 @@ fn get_aggregated_swap_amount_work() { None ); + // dex + taiga assert_eq!( AggregatedDex::get_aggregated_swap_amount( &vec![SwapPath::Dex(vec![AUSD, LDOT]), SwapPath::Taiga(0, 1, 0)], @@ -866,6 +889,110 @@ fn get_aggregated_swap_amount_work() { ), None ); + + // more complicate aggregated dex test. + // We've already Taiga(DOT,LDOT), and Dex(LDOT, AUSD). + assert_ok!(inject_liquidity(DOT, AUSD, 100_000_000_000u128, 20_000_000_000_000u128)); + assert_ok!(inject_liquidity(DOT, LDOT, 100_000_000_000u128, 100_000_000_000u128)); + + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![LDOT, DOT]),], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 9_089_554_318u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![LDOT, DOT]),], + SwapLimit::ExactTarget(1_000_000_000u128, 1_000_000_000u128) + ), + Some((101_011_873u128, 1_000_000_960u128)) + ); + + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Taiga(0, 1, 0), SwapPath::Dex(vec![DOT, LDOT]),], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 99_898_463u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Taiga(0, 1, 0), SwapPath::Dex(vec![DOT, LDOT]),], + SwapLimit::ExactTarget(2_000_000_000u128, 100_000_000u128) + ), + Some((1_001_018_430u128, 100_000_098u128)) + ); + + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + ], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 8_332_194_934u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + ], + SwapLimit::ExactTarget(1_000_000_000u128, 1_000_000_000u128) + ), + Some((102_042_622u128, 1_000_000_938u128)) + ); + + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Dex(vec![DOT, LDOT]), + ], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 970_873_785u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Dex(vec![DOT, LDOT]), + ], + SwapLimit::ExactTarget(1_000_000_000u128, 100_000_000u128) + ), + Some((100_300_904u128, 100_000_000u128)) + ); + + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LDOT, AUSD]), + ], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 9_994_493_008u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LDOT, AUSD]), + ], + SwapLimit::ExactTarget(1_000_000_000u128, 100_000_000u128) + ), + Some((10_019_806u128, 100_195_498u128)) + ); }); } @@ -1319,8 +1446,8 @@ fn aggregated_swap_swap_work() { fn inject_liquidity_default_pairs() { assert_ok!(inject_liquidity(AUSD, DOT, 1_000_000u128, 2_000_000u128)); - assert_ok!(inject_liquidity(AUSD, BTC, 1_000_000u128, 2_000_000u128)); assert_ok!(inject_liquidity(DOT, BTC, 1_000_000u128, 2_000_000u128)); + assert_ok!(inject_liquidity(AUSD, BTC, 1_000_000u128, 2_000_000u128)); } #[test] @@ -1336,7 +1463,7 @@ fn offchain_worker_max_iteration_works() { System::set_block_number(1); inject_liquidity_default_pairs(); - trigger_unsigned_rebalance_swap(2, pool_state.clone(), None); + trigger_unsigned_rebalance_swap(2, pool_state.clone(), vec![], vec![], None); let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); let start_key = to_be_continue.get::>().unwrap_or_default(); @@ -1344,7 +1471,7 @@ fn offchain_worker_max_iteration_works() { // sets max iterations value to 1 offchain.local_storage_set(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS, &1u32.encode()); - trigger_unsigned_rebalance_swap(3, pool_state.clone(), None); + trigger_unsigned_rebalance_swap(3, pool_state.clone(), vec![], vec![], None); let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); let start_key = to_be_continue.get::>().unwrap_or_default().unwrap(); @@ -1378,52 +1505,104 @@ fn offchain_worker_trigger_unsigned_rebalance_swap() { supply_amount: 1000, threshold: 1960, })); - let supply_threshold = RebalanceSupplyThreshold::::get(AUSD).unwrap(); assert_eq!(supply_threshold, (1000, 1960)); + assert_ok!(Tokens::deposit( AUSD, &Pallet::::treasury_account(), - 1_000_000_000_000_000u128 + 1_000_000u128 )); - trigger_unsigned_rebalance_swap(2, pool_state.clone(), Some(1990)); - trigger_unsigned_rebalance_swap(3, pool_state.clone(), Some(1970)); - trigger_unsigned_rebalance_swap(4, pool_state.clone(), None); + // offchain worker execution trigger dex swap: AUSD->DOT-BTC->AUSD + trigger_unsigned_rebalance_swap( + 2, + pool_state.clone(), + vec![1000, 1998, 3988], + vec![3988, 1990], + Some(1990), + ); + // treasury account use 1000 to swap 1990, that's gain 990. + assert_eq!( + Tokens::free_balance(AUSD, &Pallet::::treasury_account()), + 1000990 + ); + + // treasury account use 1000 to swap 1970, that's gain 970. + trigger_unsigned_rebalance_swap( + 3, + pool_state.clone(), + vec![1000, 1994, 3964], + vec![3964, 1970], + Some(1970), + ); + assert_eq!( + Tokens::free_balance(AUSD, &Pallet::::treasury_account()), + 1001960 + ); + + trigger_unsigned_rebalance_swap(4, pool_state.clone(), vec![], vec![], None); + assert_eq!( + Tokens::free_balance(AUSD, &Pallet::::treasury_account()), + 1001960 + ); + + // AUSD-DOT: AUSD+(1000+1000), DOT-(1998+1994) + assert_eq!(Dex::get_liquidity_pool(AUSD, DOT), (1002000, 1996008)); + // DOT-BTC: DOT+(1998+1994), BTC-(3988+3964) + assert_eq!(Dex::get_liquidity_pool(DOT, BTC), (1003992, 1992048)); + // BTC-AUSD: BTC+(3988+3964), AUSD-(1990+1970) + assert_eq!(Dex::get_liquidity_pool(AUSD, BTC), (996040, 2007952)); }); } -fn trigger_unsigned_rebalance_swap(n: u64, pool_state: Arc>, actual_target_amount: Option) { +fn trigger_unsigned_rebalance_swap( + n: u64, + pool_state: Arc>, + dex_lp1: Vec, + dex_lp2: Vec, + actual_target_amount: Option, +) { System::reset_events(); run_to_block_offchain(n); // trigger unsigned tx let tx = pool_state.write().transactions.pop().unwrap(); let tx = Extrinsic::decode(&mut &*tx).unwrap(); - if let MockCall::AggregatedDex(crate::Call::force_rebalance_swap { - currency_1, - currency_2, - currency_3, - }) = tx.call - { - assert_eq!((AUSD, DOT, BTC), (currency_1, currency_2, currency_3)); + let swap_path = vec![ + AggregatedSwapPath::Dex(vec![AUSD, DOT, BTC]), + AggregatedSwapPath::Dex(vec![BTC, AUSD]), + ]; + if let MockCall::AggregatedDex(crate::Call::force_rebalance_swap { currency_id, swap_path }) = tx.call { assert_ok!(AggregatedDex::force_rebalance_swap( Origin::none(), - currency_1, - currency_2, - currency_3 + currency_id, + swap_path )); } assert!(pool_state.write().transactions.pop().is_none()); // if target amount is less than threshold, then rebalance swap not triggered. if let Some(target_amount) = actual_target_amount { + System::assert_has_event(crate::mock::Event::Dex(module_dex::Event::Swap { + trader: Pallet::::treasury_account(), + path: vec![AUSD, DOT, BTC], + liquidity_changes: dex_lp1, + })); + System::assert_has_event(crate::mock::Event::Dex(module_dex::Event::Swap { + trader: Pallet::::treasury_account(), + path: vec![BTC, AUSD], + liquidity_changes: dex_lp2, + })); System::assert_last_event(Event::AggregatedDex(crate::Event::RebalanceTrading { - currency_1: AUSD, - currency_2: DOT, - currency_3: BTC, + currency_id: AUSD, supply_amount: 1000, target_amount, + swap_path, })); + } else { + assert!(System::events() + .iter() + .all(|r| { !matches!(r.event, Event::AggregatedDex(crate::Event::RebalanceTrading { .. })) })); } } From cd955b75bcb7ed4dde7f074286a70ce161e0fd98 Mon Sep 17 00:00:00 2001 From: zqh Date: Sat, 13 Aug 2022 19:18:37 +0800 Subject: [PATCH 17/20] benchmark --- Cargo.lock | 1 + modules/aggregated-dex/Cargo.toml | 1 + modules/aggregated-dex/src/lib.rs | 2 +- modules/aggregated-dex/src/mock.rs | 1 + modules/aggregated-dex/src/tests.rs | 264 +++++++++++++++--- runtime/acala/src/benchmarking/mod.rs | 3 + runtime/acala/src/lib.rs | 1 + runtime/karura/src/benchmarking/mod.rs | 3 + runtime/karura/src/lib.rs | 1 + .../src/benchmarking/aggregated_dex.rs | 133 ++++++--- runtime/mandala/src/benchmarking/utils.rs | 2 +- 11 files changed, 328 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27541d2ecc..a9fe513f81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5493,6 +5493,7 @@ name = "module-aggregated-dex" version = "2.9.1" dependencies = [ "acala-primitives", + "env_logger", "frame-support", "frame-system", "log", diff --git a/modules/aggregated-dex/Cargo.toml b/modules/aggregated-dex/Cargo.toml index 9d65bf489b..d509e73713 100644 --- a/modules/aggregated-dex/Cargo.toml +++ b/modules/aggregated-dex/Cargo.toml @@ -30,6 +30,7 @@ nutsfinance-stable-asset = { path = "../../ecosystem-modules/stable-asset/lib/st [dev-dependencies] pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } parking_lot = { version = "0.12.0" } +env_logger = "0.9.0" [features] default = ["std"] diff --git a/modules/aggregated-dex/src/lib.rs b/modules/aggregated-dex/src/lib.rs index 4bed13a514..4998030116 100644 --- a/modules/aggregated-dex/src/lib.rs +++ b/modules/aggregated-dex/src/lib.rs @@ -260,7 +260,7 @@ pub mod module { /// Requires `GovernanceOrigin` /// /// Parameters: - /// - `updates`: Vec<(Token, Option>)> + /// - `updates`: Vec<(CurrencyId, Option>)> #[pallet::weight(::WeightInfo::update_aggregated_swap_paths(updates.len() as u32))] #[transactional] pub fn update_rebalance_swap_paths( diff --git a/modules/aggregated-dex/src/mock.rs b/modules/aggregated-dex/src/mock.rs index b7f479d239..33a77f09b4 100644 --- a/modules/aggregated-dex/src/mock.rs +++ b/modules/aggregated-dex/src/mock.rs @@ -46,6 +46,7 @@ mod aggregated_dex { pub const ALICE: AccountId = AccountId32::new([1u8; 32]); pub const BOB: AccountId = AccountId32::new([2u8; 32]); +pub const ACA: CurrencyId = CurrencyId::Token(TokenSymbol::ACA); pub const AUSD: CurrencyId = CurrencyId::Token(TokenSymbol::AUSD); pub const DOT: CurrencyId = CurrencyId::Token(TokenSymbol::DOT); pub const LDOT: CurrencyId = CurrencyId::Token(TokenSymbol::LDOT); diff --git a/modules/aggregated-dex/src/tests.rs b/modules/aggregated-dex/src/tests.rs index ddd9e09db4..0f13e26819 100644 --- a/modules/aggregated-dex/src/tests.rs +++ b/modules/aggregated-dex/src/tests.rs @@ -83,13 +83,15 @@ fn initial_taiga_dot_ldot_pool() -> DispatchResult { 10_000_000_000u128, )?; - Tokens::deposit(DOT, &BOB, 100_000_000_000u128)?; - Tokens::deposit(LDOT, &BOB, 1_000_000_000_000u128)?; + let amount = 100_000_000_000u128; + Tokens::deposit(DOT, &BOB, amount)?; + Tokens::deposit(LDOT, &BOB, amount * 10)?; - StableAssetWrapper::mint(&BOB, 0, vec![100_000_000_000u128, 1_000_000_000_000u128], 0)?; + // The DOT and LDOT convert rate in `mock::ConvertBalanceHoma` is 1/10. + StableAssetWrapper::mint(&BOB, 0, vec![amount, amount * 10], 0)?; assert_eq!( StableAssetWrapper::pool(0).map(|p| p.balances).unwrap(), - vec![100_000_000_000u128, 100_000_000_000u128] + vec![amount, amount] ); Ok(()) @@ -993,6 +995,63 @@ fn get_aggregated_swap_amount_work() { ), Some((10_019_806u128, 100_195_498u128)) ); + + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Dex(vec![DOT, LDOT]), + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Dex(vec![DOT, LDOT]), + ], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 943_396_225u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Dex(vec![DOT, LDOT]), + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Dex(vec![DOT, LDOT]), + ], + SwapLimit::ExactTarget(2_000_000_000u128, 1_000_000_000u128) + ), + Some((1_063_829_789u128, 1_000_000_000u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LDOT, AUSD]), + ], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 99_397_727_227u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![ + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LDOT, AUSD]), + SwapPath::Dex(vec![AUSD, DOT]), + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LDOT, AUSD]), + ], + SwapLimit::ExactTarget(2_000_000_000u128, 1_000_000_000u128) + ), + Some((10_022_406u128, 1_002_155_781u128)) + ); }); } @@ -1444,6 +1503,88 @@ fn aggregated_swap_swap_work() { }); } +#[test] +fn aggregated_dex_is_balance_without_arbitrage() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(initial_taiga_dot_ldot_pool()); + assert_ok!(inject_liquidity(DOT, LDOT, 10_000_000_000u128, 100_000_000_000u128)); + + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![LDOT, DOT])], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 908_955_431u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![LDOT, DOT])], + SwapLimit::ExactTarget(2_000_000_000u128, 1_000_000_000u128) + ), + Some((1_111_313_659u128, 1_000_000_079u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Dex(vec![DOT, LDOT]), SwapPath::Taiga(0, 1, 0),], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 908_955_434u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Dex(vec![DOT, LDOT]), SwapPath::Taiga(0, 1, 0),], + SwapLimit::ExactTarget(2_000_000_000u128, 1_000_000_000u128) + ), + Some((1111313678, 1000000098)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Taiga(0, 1, 0), SwapPath::Dex(vec![DOT, LDOT])], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 990_082_933u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Taiga(0, 1, 0), SwapPath::Dex(vec![DOT, LDOT])], + SwapLimit::ExactTarget(2_000_000_000u128, 1_000_000_000u128) + ), + Some((1_010_118_740u128, 1_000_000_970u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Dex(vec![LDOT, DOT]), SwapPath::Taiga(0, 0, 1),], + SwapLimit::ExactSupply(1_000_000_000u128, 0) + ), + Some((1_000_000_000u128, 990_082_920u128)) + ); + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Dex(vec![LDOT, DOT]), SwapPath::Taiga(0, 0, 1),], + SwapLimit::ExactTarget(2_000_000_000u128, 1_000_000_000u128) + ), + Some((1_010_118_754u128, 1_000_000_980u128)) + ); + }); +} + +fn set_rebalance_info_works(token: CurrencyId, supply_amount: Balance, minimum_amount: Balance) { + // set swap supply and threshold + assert_ok!(AggregatedDex::set_rebalance_swap_info( + Origin::signed(BOB), + token, + supply_amount, + minimum_amount, + )); + System::assert_last_event(Event::AggregatedDex(crate::Event::SetupRebalanceSwapInfo { + currency_id: token, + supply_amount: supply_amount, + threshold: minimum_amount, + })); + let supply_threshold = RebalanceSupplyThreshold::::get(token).unwrap(); + assert_eq!(supply_threshold, (supply_amount, minimum_amount)); +} + fn inject_liquidity_default_pairs() { assert_ok!(inject_liquidity(AUSD, DOT, 1_000_000u128, 2_000_000u128)); assert_ok!(inject_liquidity(DOT, BTC, 1_000_000u128, 2_000_000u128)); @@ -1463,7 +1604,7 @@ fn offchain_worker_max_iteration_works() { System::set_block_number(1); inject_liquidity_default_pairs(); - trigger_unsigned_rebalance_swap(2, pool_state.clone(), vec![], vec![], None); + trigger_unsigned_rebalance_dex_swap(2, pool_state.clone(), vec![], vec![], None); let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); let start_key = to_be_continue.get::>().unwrap_or_default(); @@ -1471,7 +1612,7 @@ fn offchain_worker_max_iteration_works() { // sets max iterations value to 1 offchain.local_storage_set(StorageKind::PERSISTENT, OFFCHAIN_WORKER_MAX_ITERATIONS, &1u32.encode()); - trigger_unsigned_rebalance_swap(3, pool_state.clone(), vec![], vec![], None); + trigger_unsigned_rebalance_dex_swap(3, pool_state.clone(), vec![], vec![], None); let to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); let start_key = to_be_continue.get::>().unwrap_or_default().unwrap(); @@ -1481,7 +1622,7 @@ fn offchain_worker_max_iteration_works() { } #[test] -fn offchain_worker_trigger_unsigned_rebalance_swap() { +fn offchain_worker_unsigned_rebalance_dex_swap() { let (offchain, _offchain_state) = testing::TestOffchainExt::new(); let (pool, pool_state) = testing::TestTransactionPoolExt::new(); let mut ext = ExtBuilder::default().build(); @@ -1493,20 +1634,7 @@ fn offchain_worker_trigger_unsigned_rebalance_swap() { System::set_block_number(1); inject_liquidity_default_pairs(); - // set swap supply and threshold - assert_ok!(AggregatedDex::set_rebalance_swap_info( - Origin::signed(BOB), - AUSD, - 1000, - 1960, - )); - System::assert_last_event(Event::AggregatedDex(crate::Event::SetupRebalanceSwapInfo { - currency_id: AUSD, - supply_amount: 1000, - threshold: 1960, - })); - let supply_threshold = RebalanceSupplyThreshold::::get(AUSD).unwrap(); - assert_eq!(supply_threshold, (1000, 1960)); + set_rebalance_info_works(AUSD, 1000, 1960); assert_ok!(Tokens::deposit( AUSD, @@ -1515,7 +1643,7 @@ fn offchain_worker_trigger_unsigned_rebalance_swap() { )); // offchain worker execution trigger dex swap: AUSD->DOT-BTC->AUSD - trigger_unsigned_rebalance_swap( + trigger_unsigned_rebalance_dex_swap( 2, pool_state.clone(), vec![1000, 1998, 3988], @@ -1529,7 +1657,7 @@ fn offchain_worker_trigger_unsigned_rebalance_swap() { ); // treasury account use 1000 to swap 1970, that's gain 970. - trigger_unsigned_rebalance_swap( + trigger_unsigned_rebalance_dex_swap( 3, pool_state.clone(), vec![1000, 1994, 3964], @@ -1541,7 +1669,7 @@ fn offchain_worker_trigger_unsigned_rebalance_swap() { 1001960 ); - trigger_unsigned_rebalance_swap(4, pool_state.clone(), vec![], vec![], None); + trigger_unsigned_rebalance_dex_swap(4, pool_state.clone(), vec![], vec![], None); assert_eq!( Tokens::free_balance(AUSD, &Pallet::::treasury_account()), 1001960 @@ -1556,7 +1684,81 @@ fn offchain_worker_trigger_unsigned_rebalance_swap() { }); } -fn trigger_unsigned_rebalance_swap( +#[test] +fn offchain_worker_unsigned_rebalance_aggregated_dex_swap() { + let (offchain, _offchain_state) = testing::TestOffchainExt::new(); + let (pool, pool_state) = testing::TestTransactionPoolExt::new(); + let mut ext = ExtBuilder::default().build(); + ext.register_extension(OffchainWorkerExt::new(offchain.clone())); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + + ext.execute_with(|| { + System::set_block_number(1); + + // Taiga(DOT, LDOT) + Dex(LDOT, DOT) + assert_ok!(initial_taiga_dot_ldot_pool()); + assert_ok!(inject_liquidity(DOT, LDOT, 11_000_000_000u128, 100_000_000_000u128)); + + assert_eq!( + AggregatedDex::get_aggregated_swap_amount( + &vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![LDOT, DOT])], + SwapLimit::ExactSupply(1_000_000u128, 0) + ), + Some((1_000_000u128, 1_099_888u128)) + ); + + set_rebalance_info_works(DOT, 1_000_000, 1_050_000); + + let swap_path = vec![SwapPath::Taiga(0, 0, 1), SwapPath::Dex(vec![LDOT, DOT])]; + assert_ok!(AggregatedDex::update_rebalance_swap_paths( + Origin::signed(BOB), + vec![(DOT, Some(swap_path.clone()))] + )); + let swap_paths = RebalanceSwapPaths::::get(DOT).unwrap(); + assert_eq!(swap_paths.into_inner(), swap_path); + + assert_ok!(Tokens::deposit( + DOT, + &Pallet::::treasury_account(), + 10_000_000_000u128 + )); + + System::reset_events(); + run_to_block_offchain(2); + assert_unsigned_call_executed(pool_state); + + System::assert_last_event(Event::AggregatedDex(crate::Event::RebalanceTrading { + currency_id: DOT, + supply_amount: 1000000, + target_amount: 1099888, + swap_path, + })); + assert!(System::events().iter().any(|r| { + matches!( + r.event, + crate::mock::Event::Dex(module_dex::Event::Swap { .. }) + | crate::mock::Event::StableAsset(nutsfinance_stable_asset::Event::TokenSwapped { .. }) + ) + })); + }); +} + +fn assert_unsigned_call_executed(pool_state: Arc>) { + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + + if let MockCall::AggregatedDex(crate::Call::force_rebalance_swap { currency_id, swap_path }) = tx.call { + assert_ok!(AggregatedDex::force_rebalance_swap( + Origin::none(), + currency_id, + swap_path + )); + } + assert!(pool_state.write().transactions.pop().is_none()); +} + +fn trigger_unsigned_rebalance_dex_swap( n: u64, pool_state: Arc>, dex_lp1: Vec, @@ -1565,22 +1767,12 @@ fn trigger_unsigned_rebalance_swap( ) { System::reset_events(); run_to_block_offchain(n); + assert_unsigned_call_executed(pool_state); - // trigger unsigned tx - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); let swap_path = vec![ AggregatedSwapPath::Dex(vec![AUSD, DOT, BTC]), AggregatedSwapPath::Dex(vec![BTC, AUSD]), ]; - if let MockCall::AggregatedDex(crate::Call::force_rebalance_swap { currency_id, swap_path }) = tx.call { - assert_ok!(AggregatedDex::force_rebalance_swap( - Origin::none(), - currency_id, - swap_path - )); - } - assert!(pool_state.write().transactions.pop().is_none()); // if target amount is less than threshold, then rebalance swap not triggered. if let Some(target_amount) = actual_target_amount { diff --git a/runtime/acala/src/benchmarking/mod.rs b/runtime/acala/src/benchmarking/mod.rs index ec81dd2ba0..876523b215 100644 --- a/runtime/acala/src/benchmarking/mod.rs +++ b/runtime/acala/src/benchmarking/mod.rs @@ -26,6 +26,9 @@ pub mod utils { } // module benchmarking +pub mod aggregated_dex { + include!("../../../mandala/src/benchmarking/aggregated_dex.rs"); +} pub mod asset_registry { include!("../../../mandala/src/benchmarking/asset_registry.rs"); } diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 1d5051e179..df7b07ebf5 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1780,6 +1780,7 @@ mod benches { define_benchmarks!( [module_dex, benchmarking::dex] [module_dex_oracle, benchmarking::dex_oracle] + [module_aggregated_dex, benchmarking::aggregated_dex] [module_asset_registry, benchmarking::asset_registry] [module_auction_manager, benchmarking::auction_manager] [module_cdp_engine, benchmarking::cdp_engine] diff --git a/runtime/karura/src/benchmarking/mod.rs b/runtime/karura/src/benchmarking/mod.rs index 1a3252be2c..56f2af53b3 100644 --- a/runtime/karura/src/benchmarking/mod.rs +++ b/runtime/karura/src/benchmarking/mod.rs @@ -26,6 +26,9 @@ pub mod utils { } // module benchmarking +pub mod aggregated_dex { + include!("../../../mandala/src/benchmarking/aggregated_dex.rs"); +} pub mod asset_registry { include!("../../../mandala/src/benchmarking/asset_registry.rs"); } diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index ca49e55fdb..b701875bc5 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1813,6 +1813,7 @@ mod benches { define_benchmarks!( [module_dex, benchmarking::dex] [module_dex_oracle, benchmarking::dex_oracle] + [module_aggregated_dex, benchmarking::aggregated_dex] [module_asset_registry, benchmarking::asset_registry] [module_auction_manager, benchmarking::auction_manager] [module_cdp_engine, benchmarking::cdp_engine] diff --git a/runtime/mandala/src/benchmarking/aggregated_dex.rs b/runtime/mandala/src/benchmarking/aggregated_dex.rs index e053c2faa7..c485d71df7 100644 --- a/runtime/mandala/src/benchmarking/aggregated_dex.rs +++ b/runtime/mandala/src/benchmarking/aggregated_dex.rs @@ -16,63 +16,104 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use super::utils::{dollar, set_balance}; -use crate::{ - AccountId, Balance, CurrencyId, Dex, GetLiquidCurrencyId, GetNativeCurrencyId, GetStableCurrencyId, - GetStakingCurrencyId, Runtime, -}; +use super::utils::{create_stable_pools, dollar, inject_liquidity, LIQUID, NATIVE, SEED, STABLECOIN, STAKING}; +use crate::{AccountId, AggregatedDex, Balance, Currencies, Dex, Runtime, System, TreasuryPalletId}; use frame_benchmarking::account; +use frame_support::assert_ok; use frame_system::RawOrigin; -use sp_runtime::traits::UniqueSaturatedInto; - -const SEED: u32 = 0; - -const STABLECOIN: CurrencyId = GetStableCurrencyId::get(); -const STAKINGCOIN: CurrencyId = GetStakingCurrencyId::get(); -const NATIVECOIN: CurrencyId = GetNativeCurrencyId::get(); -const LIQUIDCOIN: CurrencyId = GetLiquidCurrencyId::get(); - -fn inject_liquidity( - maker: AccountId, - currency_id_a: CurrencyId, - currency_id_b: CurrencyId, - max_amount_a: Balance, - max_amount_b: Balance, -) -> Result<(), &'static str> { - // set balance - set_balance(currency_id_a, &maker, max_amount_a.unique_saturated_into()); - set_balance(currency_id_b, &maker, max_amount_b.unique_saturated_into()); - - let _ = Dex::enable_trading_pair(RawOrigin::Root.into(), currency_id_a, currency_id_b); - - Dex::add_liquidity( - RawOrigin::Signed(maker.clone()).into(), - currency_id_a, - currency_id_b, - max_amount_a, - max_amount_b, - Default::default(), - false, - )?; - - Ok(()) -} +use module_aggregated_dex::SwapPath; +use module_support::DEXManager; +use orml_traits::MultiCurrency; +use sp_runtime::traits::AccountIdConversion; runtime_benchmarks! { { Runtime, module_aggregated_dex } + update_aggregated_swap_paths { + let funder: AccountId = account("funder", 0, SEED); + + create_stable_pools(vec![STAKING, LIQUID], vec![1, 1])?; + inject_liquidity(funder.clone(), LIQUID, NATIVE, 1000 * dollar(LIQUID), 1000 * dollar(NATIVE), false)?; + + let swap_path = vec![ + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LIQUID, NATIVE]) + ]; + + let updates = vec![((STAKING, NATIVE), Some(swap_path.clone()))]; + }: _(RawOrigin::Root, updates) + verify { + assert_eq!(module_aggregated_dex::AggregatedSwapPaths::::get((STAKING, NATIVE)).unwrap().into_inner(), swap_path); + } + + update_rebalance_swap_paths { + let funder: AccountId = account("funder", 0, SEED); + + create_stable_pools(vec![STAKING, LIQUID], vec![1, 1])?; + inject_liquidity(funder.clone(), LIQUID, STAKING, 1000 * dollar(LIQUID), 1000 * dollar(STAKING), false)?; + + let swap_path = vec![ + SwapPath::Taiga(0, 0, 1), + SwapPath::Dex(vec![LIQUID, STAKING]) + ]; + + let updates = vec![(STAKING, Some(swap_path.clone()))]; + }: _(RawOrigin::Root, updates) + verify { + assert_eq!(module_aggregated_dex::RebalanceSwapPaths::::get(STAKING).unwrap().into_inner(), swap_path); + } + set_rebalance_swap_info { - let supply: Balance = 100_000_000_000_000; + let supply_amount: Balance = 100_000_000_000_000; let threshold: Balance = 110_000_000_000_000; - - }: _(RawOrigin::Root, STABLECOIN, supply, threshold) + }: _(RawOrigin::Root, STABLECOIN, supply_amount, threshold) + verify { + System::assert_has_event(module_aggregated_dex::Event::SetupRebalanceSwapInfo { + currency_id: STABLECOIN, + supply_amount, + threshold, + }.into()); + } force_rebalance_swap { let funder: AccountId = account("funder", 0, SEED); - - let _ = inject_liquidity(funder.clone(), STABLECOIN, STAKINGCOIN, 100 * dollar(STABLECOIN), 200 * dollar(STAKINGCOIN)); - let _ = inject_liquidity(funder.clone(), STAKINGCOIN, NATIVECOIN, 100 * dollar(STAKINGCOIN), 200 * dollar(NATIVECOIN)); - }: _(RawOrigin::None, STABLECOIN, STAKINGCOIN, STAKINGCOIN) + let supply: Balance = 10 * dollar(STABLECOIN); + let threshold: Balance = 11 * dollar(STABLECOIN); + + inject_liquidity(funder.clone(), STABLECOIN, STAKING, 1000 * dollar(STABLECOIN), 1200 * dollar(STAKING), false)?; + inject_liquidity(funder.clone(), STAKING, NATIVE, 1000 * dollar(STAKING), 1000 * dollar(NATIVE), false)?; + inject_liquidity(funder.clone(), NATIVE, STABLECOIN, 1000 * dollar(NATIVE), 1000 * dollar(STABLECOIN), false)?; + + assert_ok!(AggregatedDex::set_rebalance_swap_info(RawOrigin::Root.into(), STABLECOIN, supply, threshold)); + + let treasury_account: AccountId = TreasuryPalletId::get().into_account_truncating(); + Currencies::deposit( + STABLECOIN, + &treasury_account, + 100 * dollar(STABLECOIN), + )?; + + let swap_path = vec![ + SwapPath::Dex(vec![STABLECOIN, STAKING]), + SwapPath::Dex(vec![STAKING, NATIVE]), + SwapPath::Dex(vec![NATIVE, STABLECOIN]) + ]; + }: _(RawOrigin::None, STABLECOIN, swap_path) + verify { + assert_eq!(Dex::get_liquidity_pool(STABLECOIN, STAKING).0, 1000 * dollar(STABLECOIN) + supply); + assert_eq!( + 1200 * dollar(STAKING) - Dex::get_liquidity_pool(STABLECOIN, STAKING).1, + Dex::get_liquidity_pool(STAKING, NATIVE).0 - 1000 * dollar(STAKING) + ); + assert_eq!( + 1000 * dollar(STAKING) - Dex::get_liquidity_pool(STAKING, NATIVE).1, + Dex::get_liquidity_pool(NATIVE, STABLECOIN).0 - 1000 * dollar(NATIVE) + ); + assert_eq!( + 1000 * dollar(STAKING) - Dex::get_liquidity_pool(NATIVE, STABLECOIN).1, + Currencies::free_balance(STABLECOIN, &treasury_account) - 90 * dollar(STABLECOIN) + ); + } } #[cfg(test)] diff --git a/runtime/mandala/src/benchmarking/utils.rs b/runtime/mandala/src/benchmarking/utils.rs index 29c24addbd..519fab6ea8 100644 --- a/runtime/mandala/src/benchmarking/utils.rs +++ b/runtime/mandala/src/benchmarking/utils.rs @@ -42,7 +42,7 @@ pub const NATIVE: CurrencyId = GetNativeCurrencyId::get(); pub const STABLECOIN: CurrencyId = GetStableCurrencyId::get(); pub const LIQUID: CurrencyId = GetLiquidCurrencyId::get(); pub const STAKING: CurrencyId = GetStakingCurrencyId::get(); -const SEED: u32 = 0; +pub const SEED: u32 = 0; pub fn lookup_of_account(who: AccountId) -> <::Lookup as StaticLookup>::Source { ::Lookup::unlookup(who) From acc72295b5c784926b297fd08388703052bc345f Mon Sep 17 00:00:00 2001 From: zqh Date: Sat, 13 Aug 2022 19:42:16 +0800 Subject: [PATCH 18/20] fmt --- Cargo.lock | 1 - modules/aggregated-dex/Cargo.toml | 1 - modules/aggregated-dex/src/mock.rs | 1 - modules/transaction-payment/src/tests.rs | 30 ------------------------ runtime/acala/src/lib.rs | 2 +- runtime/common/src/lib.rs | 2 +- runtime/karura/src/lib.rs | 2 +- runtime/mandala/src/lib.rs | 2 +- 8 files changed, 4 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9fe513f81..27541d2ecc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5493,7 +5493,6 @@ name = "module-aggregated-dex" version = "2.9.1" dependencies = [ "acala-primitives", - "env_logger", "frame-support", "frame-system", "log", diff --git a/modules/aggregated-dex/Cargo.toml b/modules/aggregated-dex/Cargo.toml index d509e73713..9d65bf489b 100644 --- a/modules/aggregated-dex/Cargo.toml +++ b/modules/aggregated-dex/Cargo.toml @@ -30,7 +30,6 @@ nutsfinance-stable-asset = { path = "../../ecosystem-modules/stable-asset/lib/st [dev-dependencies] pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.26" } parking_lot = { version = "0.12.0" } -env_logger = "0.9.0" [features] default = ["std"] diff --git a/modules/aggregated-dex/src/mock.rs b/modules/aggregated-dex/src/mock.rs index 33a77f09b4..b7f479d239 100644 --- a/modules/aggregated-dex/src/mock.rs +++ b/modules/aggregated-dex/src/mock.rs @@ -46,7 +46,6 @@ mod aggregated_dex { pub const ALICE: AccountId = AccountId32::new([1u8; 32]); pub const BOB: AccountId = AccountId32::new([2u8; 32]); -pub const ACA: CurrencyId = CurrencyId::Token(TokenSymbol::ACA); pub const AUSD: CurrencyId = CurrencyId::Token(TokenSymbol::AUSD); pub const DOT: CurrencyId = CurrencyId::Token(TokenSymbol::DOT); pub const LDOT: CurrencyId = CurrencyId::Token(TokenSymbol::LDOT); diff --git a/modules/transaction-payment/src/tests.rs b/modules/transaction-payment/src/tests.rs index 6ac7296020..8bbed11e61 100644 --- a/modules/transaction-payment/src/tests.rs +++ b/modules/transaction-payment/src/tests.rs @@ -2242,33 +2242,3 @@ fn with_fee_call_validation_works() { ); }); } - -#[test] -fn compare_currency() { - use std::cmp::Ordering; - - let c1 = CurrencyId::Token(TokenSymbol::ACA); - let c2 = CurrencyId::Token(TokenSymbol::AUSD); - let c3 = CurrencyId::Token(TokenSymbol::DOT); - - cmp_cur(c1, c2); - cmp_cur(c2, c1); - cmp_cur(c1, c3); - cmp_cur(c3, c1); - cmp_cur(c2, c3); - cmp_cur(c2, c2); - - fn cmp_cur(c1: CurrencyId, c2: CurrencyId) { - match c1.cmp(&c2) { - Ordering::Equal => { - println!("==!"); - } - Ordering::Less => { - println!("<-!"); - } - Ordering::Greater => { - println!(">-!"); - } - } - } -} diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index df7b07ebf5..f5ce47f78c 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1121,7 +1121,7 @@ impl module_aggregated_dex::Config for Runtime { type DexSwapJointList = AlternativeSwapPathJointList; type SwapPathLimit = ConstU32<3>; type TreasuryPallet = TreasuryPalletId; - type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; + type UnsignedPriority = runtime_common::DexRebalanceSwapUnsignedPriority; type WeightInfo = (); } diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 20c590c821..669f7dcbb0 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -90,7 +90,7 @@ parameter_types! { pub CdpEngineUnsignedPriority: TransactionPriority = MinOperationalPriority::get() - 1000; pub AuctionManagerUnsignedPriority: TransactionPriority = MinOperationalPriority::get() - 2000; pub RenvmBridgeUnsignedPriority: TransactionPriority = MinOperationalPriority::get() - 3000; - pub DexTriangleSwapUnsignedPriority: TransactionPriority = MinOperationalPriority::get() - 4000; + pub DexRebalanceSwapUnsignedPriority: TransactionPriority = MinOperationalPriority::get() - 4000; } /// The call is allowed only if caller is a system contract. diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index b701875bc5..5716abbabb 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1127,7 +1127,7 @@ impl module_aggregated_dex::Config for Runtime { type DexSwapJointList = AlternativeSwapPathJointList; type SwapPathLimit = ConstU32<3>; type TreasuryPallet = TreasuryPalletId; - type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; + type UnsignedPriority = runtime_common::DexRebalanceSwapUnsignedPriority; type WeightInfo = (); } diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index c08e0ca797..f47f064e80 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1185,7 +1185,7 @@ impl module_aggregated_dex::Config for Runtime { type DexSwapJointList = AlternativeSwapPathJointList; type SwapPathLimit = ConstU32<3>; type TreasuryPallet = TreasuryPalletId; - type UnsignedPriority = runtime_common::DexTriangleSwapUnsignedPriority; + type UnsignedPriority = runtime_common::DexRebalanceSwapUnsignedPriority; type WeightInfo = (); } From 19c86a308010653fa6c58f924056f778a3bfd9c5 Mon Sep 17 00:00:00 2001 From: zqh Date: Wed, 17 Aug 2022 08:28:29 +0800 Subject: [PATCH 19/20] extract calculate rebalance path and test --- modules/aggregated-dex/src/lib.rs | 136 +++--- modules/aggregated-dex/src/tests.rs | 65 --- .../integration-tests/src/aggregated_dex.rs | 435 ++++++++++++++++++ runtime/integration-tests/src/lib.rs | 7 + runtime/integration-tests/src/stable_asset.rs | 1 + .../src/benchmarking/aggregated_dex.rs | 3 + 6 files changed, 526 insertions(+), 121 deletions(-) create mode 100644 runtime/integration-tests/src/aggregated_dex.rs diff --git a/modules/aggregated-dex/src/lib.rs b/modules/aggregated-dex/src/lib.rs index 4998030116..44eba86ea7 100644 --- a/modules/aggregated-dex/src/lib.rs +++ b/modules/aggregated-dex/src/lib.rs @@ -31,6 +31,7 @@ use frame_system::{ use nutsfinance_stable_asset::traits::StableAsset as StableAssetT; use orml_utilities::OffchainErr; use primitives::{Balance, CurrencyId, TradingPair}; +use sp_runtime::offchain::storage_lock::StorageLockGuard; use sp_runtime::{ offchain::{ storage::StorageValueRef, @@ -287,6 +288,12 @@ pub mod module { Ok(()) } + /// Update the rebalance swap information for specify token. + /// + /// Parameters: + /// - `currency_id`: the token used for rebalance swap + /// - `supply_amount`: the supply amount of `currency_id` used for rebalance swap + /// - `threshold`: the target amount of `currency_id` used for rebalance swap #[pallet::weight(::WeightInfo::set_rebalance_swap_info())] #[transactional] pub fn set_rebalance_swap_info( @@ -299,6 +306,11 @@ pub mod module { Self::do_set_rebalance_swap_info(currency_id, supply_amount, threshold) } + /// Force execution rebalance swap by offchain worker. + /// + /// Parameters: + /// - `currency_id`: the token used for rebalance swap + /// - `swap_path`: the aggregated swap path used for rebalance swap #[pallet::weight(::WeightInfo::force_rebalance_swap())] #[transactional] pub fn force_rebalance_swap( @@ -537,6 +549,24 @@ impl Pallet { } } + fn do_set_rebalance_swap_info( + currency_id: CurrencyId, + supply_amount: Balance, + threshold: Balance, + ) -> DispatchResult { + ensure!(threshold > supply_amount, Error::::RebalanceSwapInfoInvalid); + RebalanceSupplyThreshold::::try_mutate(currency_id, |maybe_supply_threshold| -> DispatchResult { + *maybe_supply_threshold = Some((supply_amount, threshold)); + Ok(()) + })?; + Self::deposit_event(Event::SetupRebalanceSwapInfo { + currency_id, + supply_amount, + threshold, + }); + Ok(()) + } + fn submit_rebalance_swap_tx(currency_id: CurrencyId, swap_path: Vec) { let call = Call::::force_rebalance_swap { currency_id, swap_path }; if let Err(err) = SubmitTransaction::>::submit_unsigned_transaction(call.into()) { @@ -548,19 +578,15 @@ impl Pallet { } } - fn _offchain_worker(_now: T::BlockNumber) -> Result<(), OffchainErr> { - // acquire offchain worker lock - let lock_expiration = Duration::from_millis(LOCK_DURATION); - let mut lock = StorageLock::<'_, Time>::with_deadline(OFFCHAIN_WORKER_LOCK, lock_expiration); - let mut guard = lock.try_lock().map_err(|_| OffchainErr::OffchainLock)?; - // get the max iterations config - let max_iterations = StorageValueRef::persistent(OFFCHAIN_WORKER_MAX_ITERATIONS) - .get::() - .unwrap_or(Some(DEFAULT_MAX_ITERATIONS)) - .unwrap_or(DEFAULT_MAX_ITERATIONS); - let mut to_be_continue = StorageValueRef::persistent(OFFCHAIN_WORKER_DATA); - let start_key = to_be_continue.get::>().unwrap_or_default(); - + pub fn calculate_rebalance_paths( + mut _finished: bool, + mut iteration_count: u32, + max_iterations: u32, + mut _last_currency_id: Option, + start_key: Option>, + mut guard: Option<&mut StorageLockGuard