diff --git a/Cargo.lock b/Cargo.lock index 1521366fe..c2d9b8792 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,6 +152,7 @@ dependencies = [ "module-evm-accounts", "module-evm-bridge", "module-evm-rpc-runtime-api", + "module-fees", "module-homa", "module-honzon", "module-idle-scheduler", @@ -4335,6 +4336,7 @@ dependencies = [ "module-evm-accounts", "module-evm-bridge", "module-evm-rpc-runtime-api", + "module-fees", "module-homa", "module-honzon", "module-honzon-bridge", @@ -5380,6 +5382,7 @@ dependencies = [ "module-evm-bridge", "module-evm-rpc-runtime-api", "module-evm-utility", + "module-fees", "module-homa", "module-honzon", "module-idle-scheduler", @@ -5700,6 +5703,7 @@ dependencies = [ "frame-system", "module-cdp-treasury", "module-dex", + "module-fees", "module-loans", "module-support", "nutsfinance-stable-asset", @@ -6016,6 +6020,29 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "module-fees" +version = "2.6.3" +dependencies = [ + "acala-primitives", + "frame-support", + "frame-system", + "module-currencies", + "module-dex", + "module-support", + "orml-tokens", + "orml-traits", + "pallet-balances", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "module-homa" version = "2.8.0" @@ -6025,6 +6052,7 @@ dependencies = [ "frame-support", "frame-system", "module-currencies", + "module-fees", "module-support", "orml-tokens", "orml-traits", @@ -6097,6 +6125,7 @@ dependencies = [ "frame-system", "module-cdp-engine", "module-cdp-treasury", + "module-fees", "module-loans", "module-support", "orml-currencies", @@ -10632,6 +10661,7 @@ dependencies = [ "module-evm-accounts", "module-evm-bridge", "module-evm-utility-macro", + "module-fees", "module-homa", "module-honzon", "module-idle-scheduler", @@ -10716,6 +10746,7 @@ dependencies = [ "module-evm-accounts", "module-evm-bridge", "module-evm-rpc-runtime-api", + "module-fees", "module-homa", "module-homa-lite", "module-honzon", diff --git a/modules/cdp-engine/Cargo.toml b/modules/cdp-engine/Cargo.toml index 5e3f0eb2a..9c48408c1 100644 --- a/modules/cdp-engine/Cargo.toml +++ b/modules/cdp-engine/Cargo.toml @@ -18,6 +18,7 @@ sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkad sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } support = { package = "module-support", path = "../support", default-features = false } loans = { package = "module-loans", path = "../loans", default-features = false } +module-fees = { path = "../fees", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } rand_chacha = { version = "0.2", default-features = false } nutsfinance-stable-asset = { version = "0.1.0", default-features = false, path = "../../ecosystem-modules/stable-asset/lib/stable-asset", package = "nutsfinance-stable-asset" } @@ -46,6 +47,7 @@ std = [ "sp-std/std", "support/std", "loans/std", + "module-fees/std", "primitives/std", "orml-utilities/std", ] diff --git a/modules/cdp-engine/src/lib.rs b/modules/cdp-engine/src/lib.rs index 90d05ca51..14f9e09e1 100644 --- a/modules/cdp-engine/src/lib.rs +++ b/modules/cdp-engine/src/lib.rs @@ -36,7 +36,7 @@ use frame_system::{ }; use orml_traits::{Change, GetByKey, MultiCurrency}; use orml_utilities::OffchainErr; -use primitives::{Amount, Balance, CurrencyId, Position}; +use primitives::{Amount, Balance, CurrencyId, IncomeSource, Position}; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaChaRng, @@ -56,8 +56,8 @@ use sp_runtime::{ }; use sp_std::prelude::*; use support::{ - CDPTreasury, CDPTreasuryExtended, DEXManager, EmergencyShutdown, ExchangeRate, Price, PriceProvider, Rate, Ratio, - RiskManager, Swap, SwapLimit, + CDPTreasury, CDPTreasuryExtended, DEXManager, EmergencyShutdown, ExchangeRate, OnFeeDeposit, Price, PriceProvider, + Rate, Ratio, RiskManager, Swap, SwapLimit, }; mod mock; @@ -186,6 +186,9 @@ pub mod module { /// Swap type Swap: Swap; + /// Where the fees are go to. + type OnFeeDeposit: OnFeeDeposit; + /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; } @@ -553,8 +556,12 @@ impl Pallet { let debit_exchange_rate_increment = debit_exchange_rate.saturating_mul(rate_to_accumulate); let issued_stable_coin_balance = debit_exchange_rate_increment.saturating_mul_int(total_debits); - // issue stablecoin to surplus pool - let res = ::CDPTreasury::on_system_surplus(issued_stable_coin_balance); + // Staking rewards goes to T::OnFeeDeposit + let res = T::OnFeeDeposit::on_fee_deposit( + IncomeSource::HonzonStabilityFee, + T::GetStableCurrencyId::get(), + issued_stable_coin_balance, + ); match res { Ok(_) => { // update exchange rate when issue success diff --git a/modules/cdp-engine/src/mock.rs b/modules/cdp-engine/src/mock.rs index b634d4a48..990cf7d70 100644 --- a/modules/cdp-engine/src/mock.rs +++ b/modules/cdp-engine/src/mock.rs @@ -26,7 +26,7 @@ use frame_support::{ traits::{ConstU128, ConstU32, ConstU64, Everything, Nothing}, PalletId, }; -use frame_system::EnsureSignedBy; +use frame_system::{EnsureRoot, EnsureSignedBy}; use orml_traits::parameter_type_with_key; use primitives::{DexShare, Moment, TokenSymbol, TradingPair}; use sp_core::H256; @@ -122,6 +122,7 @@ pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter; + type Currency = PalletBalances; + type Currencies = Currencies; + type NativeCurrencyId = GetNativeCurrencyId; + type AllocationPeriod = ConstU64<10>; + type DEX = (); + type DexSwapJointList = AlternativeSwapPathJointList; + type WeightInfo = (); +} + impl Config for Runtime { type Event = Event; type PriceSource = MockPriceSource; @@ -323,6 +336,7 @@ impl Config for Runtime { type Currency = Currencies; type DEX = DEXModule; type Swap = SpecificJointsSwap; + type OnFeeDeposit = Fees; type WeightInfo = (); } @@ -335,15 +349,16 @@ construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - CDPEngineModule: cdp_engine::{Pallet, Storage, Call, Event, Config, ValidateUnsigned}, - CDPTreasuryModule: cdp_treasury::{Pallet, Storage, Call, Config, Event}, - Currencies: orml_currencies::{Pallet, Call}, - Tokens: orml_tokens::{Pallet, Storage, Event, Config}, - LoansModule: loans::{Pallet, Storage, Call, Event}, - PalletBalances: pallet_balances::{Pallet, Call, Storage, Event}, - DEXModule: dex::{Pallet, Storage, Call, Event, Config}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + System: frame_system, + CDPEngineModule: cdp_engine, + CDPTreasuryModule: cdp_treasury, + Currencies: orml_currencies, + Tokens: orml_tokens, + LoansModule: loans, + PalletBalances: pallet_balances, + DEXModule: dex, + Timestamp: pallet_timestamp, + Fees: module_fees, } ); diff --git a/modules/cdp-engine/src/tests.rs b/modules/cdp-engine/src/tests.rs index 280b5fd02..cc2d3d5f6 100644 --- a/modules/cdp-engine/src/tests.rs +++ b/modules/cdp-engine/src/tests.rs @@ -23,6 +23,7 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use mock::{Call as MockCall, Event, *}; +use module_fees::PoolPercent; use orml_traits::MultiCurrency; use sp_core::offchain::{testing, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}; use sp_io::offchain; @@ -58,6 +59,17 @@ fn setup_default_collateral(currency_id: CurrencyId) { )); } +fn setup_fees_distribution() { + assert_ok!(Fees::set_income_fee( + Origin::root(), + IncomeSource::HonzonStabilityFee, + vec![PoolPercent { + pool: BOB, + rate: Rate::one(), + }], + )); +} + #[test] fn check_cdp_status_work() { ExtBuilder::default().build().execute_with(|| { @@ -1347,6 +1359,8 @@ fn compound_interest_rate_work() { #[test] fn accumulate_interest_work() { ExtBuilder::default().build().execute_with(|| { + setup_fees_distribution(); + assert_ok!(CDPEngineModule::set_collateral_params( Origin::signed(1), BTC, @@ -1862,3 +1876,77 @@ fn minimal_collateral_works() { assert_ok!(CDPEngineModule::adjust_position(&ALICE, BTC, 0, 0)); }); } + +#[test] +fn accumulated_interest_goes_to_on_fee_deposit() { + ExtBuilder::default().build().execute_with(|| { + setup_fees_distribution(); + + assert_ok!(CDPEngineModule::set_collateral_params( + Origin::signed(1), + BTC, + Change::NewValue(Some(Rate::saturating_from_rational(1, 100))), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(Some(Rate::zero())), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(10000), + )); + assert_ok!(CDPEngineModule::set_collateral_params( + Origin::signed(1), + DOT, + Change::NewValue(Some(Rate::one())), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(Some(Rate::zero())), + Change::NewValue(Some(Ratio::saturating_from_rational(2, 1))), + Change::NewValue(10000), + )); + assert_eq!( + CDPEngineModule::get_interest_rate_per_sec(BTC), + Ok(Rate::saturating_from_rational(1, 100)) + ); + assert_eq!(CDPEngineModule::get_interest_rate_per_sec(DOT), Ok(Rate::one())); + + // Treasury starts off empty. + assert_eq!(Currencies::free_balance(AUSD, &BOB), 0); + + CDPEngineModule::accumulate_interest(1, 0); + + // No debit generates no interest. + assert_eq!(Currencies::free_balance(AUSD, &BOB), 0); + + assert_ok!(CDPEngineModule::adjust_position(&ALICE, BTC, 1000, 1000)); + assert_ok!(CDPEngineModule::adjust_position(&ALICE, DOT, 1000, 1000)); + + CDPEngineModule::accumulate_interest(2, 1); + assert_eq!(CDPEngineModule::last_accumulation_secs(), 2); + + // Generated interest = debit * debit_exchange_rate * interest_rate + // = 1000 * 0.1 * 0.01 + 1000 * 0.1 * 1 = 101 + assert_eq!(Currencies::free_balance(AUSD, &BOB), 101); + + assert_eq!( + CDPEngineModule::get_debit_exchange_rate(BTC), + ExchangeRate::saturating_from_rational(101, 1000) + ); + assert_eq!( + CDPEngineModule::get_debit_exchange_rate(DOT), + ExchangeRate::saturating_from_rational(2, 10) + ); + + CDPEngineModule::accumulate_interest(3, 2); + assert_eq!(CDPEngineModule::last_accumulation_secs(), 3); + + // Generated interest = debit * debit_exchange_rate * interest_rate + // = 1000 * 0.101 * 0.01 + 1000 * 0.2 * 1 = 201 + assert_eq!(Currencies::free_balance(AUSD, &BOB), 302); + + assert_eq!( + CDPEngineModule::get_debit_exchange_rate(BTC), + ExchangeRate::saturating_from_rational(10201, 100000) + ); + assert_eq!( + CDPEngineModule::get_debit_exchange_rate(DOT), + ExchangeRate::saturating_from_rational(4, 10) + ); + }); +} diff --git a/modules/fees/Cargo.toml b/modules/fees/Cargo.toml new file mode 100644 index 000000000..403a6ee7f --- /dev/null +++ b/modules/fees/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "module-fees" +version = "2.6.3" +authors = ["Acala Developers"] +edition = "2021" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24", default-features = false } +sp-runtime = { 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 = { package = "orml-traits", path = "../../orml/traits", default-features = false } +orml-tokens = { package = "orml-tokens", path = "../../orml/tokens", default-features = false } + +support = { package = "module-support", path = "../support", default-features = false } +primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } + +paste = "1.0" + +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" } +orml-tokens = { path = "../../orml/tokens" } +module-currencies = { path = "../../modules/currencies" } +module-dex = { path = "../../modules/dex" } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "scale-info/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "sp-core/std", + "sp-std/std", + "orml-traits/std", + "orml-tokens/std", + "support/std", + "primitives/std", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/modules/fees/src/lib.rs b/modules/fees/src/lib.rs new file mode 100644 index 000000000..8064f0ab2 --- /dev/null +++ b/modules/fees/src/lib.rs @@ -0,0 +1,459 @@ +// 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 . + +//! # Network Fee Distribution & Incentive Pools Module + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +use frame_support::{ + pallet_prelude::*, + parameter_types, + traits::{Currency, Imbalance, OnUnbalanced}, + transactional, +}; +use frame_system::pallet_prelude::*; +use orml_traits::MultiCurrency; +use primitives::{Balance, CurrencyId, IncomeSource}; +use sp_runtime::{ + traits::{One, Saturating, Zero}, + FixedPointNumber, FixedU128, +}; +use sp_std::vec::Vec; +use support::{DEXManager, OnFeeDeposit, SwapLimit}; + +mod mock; +mod tests; +pub mod weights; +pub use weights::WeightInfo; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_runtime::traits::UniqueSaturatedInto; + +pub type NegativeImbalanceOf = + <::Currency as Currency<::AccountId>>::NegativeImbalance; +pub type Incomes = Vec<(IncomeSource, Vec<(::AccountId, u32)>)>; +pub type Treasuries = Vec<( + ::AccountId, + Balance, + Vec<(::AccountId, u32)>, +)>; + +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct PoolPercent { + pub pool: AccountId, + pub rate: FixedU128, +} + +/// helper method to create `PoolPercent` list by tuple. +pub fn build_pool_percents(list: Vec<(AccountId, u32)>) -> Vec> { + list.iter() + .map(|data| PoolPercent { + pool: data.clone().0, + rate: FixedU128::saturating_from_rational(data.1, 100), + }) + .collect() +} + +pub use module::*; + +#[frame_support::pallet] +pub mod module { + use super::*; + + parameter_types! { + pub const MaxPoolSize: u8 = 10; + pub const MaxTokenSize: u8 = 100; + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + + type UpdateOrigin: EnsureOrigin; + + type Currency: Currency; + + type Currencies: MultiCurrency; + + #[pallet::constant] + type NativeCurrencyId: Get; + + /// Allocation period from treasury to incentive pools. + #[pallet::constant] + type AllocationPeriod: Get; + + /// DEX to exchange currencies when allocation. + type DEX: DEXManager; + + #[pallet::constant] + type DexSwapJointList: Get>>; + + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + InvalidParams, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + IncomeFeeSet { + income: IncomeSource, + pools: Vec>, + }, + TreasuryPoolSet { + treasury: T::AccountId, + pools: Vec>, + }, + IncentiveDistribution { + treasury: T::AccountId, + amount: Balance, + }, + } + + /// Income fee source mapping to different treasury pools. + /// + /// IncomeToTreasuries: map IncomeSource => Vec + #[pallet::storage] + #[pallet::getter(fn income_to_treasuries)] + pub type IncomeToTreasuries = + StorageMap<_, Twox64Concat, IncomeSource, BoundedVec, MaxPoolSize>, ValueQuery>; + + /// Treasury pool allocation mapping to different income pools. + /// Only allocation token from treasury pool account to income pool accounts when native token + /// of treasury pool account is large than threshold. + /// + /// TreasuryToIncentives: map AccountId => (Balance, Vec) + #[pallet::storage] + #[pallet::getter(fn treasury_to_incentives)] + pub type TreasuryToIncentives = StorageMap< + _, + Twox64Concat, + T::AccountId, + (Balance, BoundedVec, MaxPoolSize>), + ValueQuery, + >; + + /// Treasury pool tokens list. + /// + /// TreasuryTokens: map AccountId => Vec + #[pallet::storage] + #[pallet::getter(fn treasury_tokens)] + pub type TreasuryTokens = + StorageMap<_, Twox64Concat, T::AccountId, BoundedVec, ValueQuery>; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub incomes: Incomes, + pub treasuries: Treasuries, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { + incomes: Default::default(), + treasuries: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + self.incomes.iter().for_each(|(income, pools)| { + let pool_rates = build_pool_percents::(pools.clone()); + let _ = >::do_set_treasury_rate(*income, pool_rates); + }); + self.treasuries.iter().for_each(|(treasury, threshold, pools)| { + let pool_rates = build_pool_percents::(pools.clone()); + let _ = >::do_set_incentive_rate(treasury.clone(), *threshold, pool_rates); + }); + } + } + + #[pallet::hooks] + impl Hooks for Pallet { + fn on_initialize(now: T::BlockNumber) -> Weight { + if now % T::AllocationPeriod::get() == Zero::zero() { + Self::distribute_incentives(); + ::WeightInfo::force_transfer_to_incentive() + } else { + 0 + } + } + } + + #[pallet::call] + impl Pallet { + /// Set how much percentage of income fee go to different treasury pools + #[pallet::weight(T::WeightInfo::set_income_fee())] + #[transactional] + pub fn set_income_fee( + origin: OriginFor, + income_source: IncomeSource, + treasury_pool_rates: Vec>, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + + Self::do_set_treasury_rate(income_source, treasury_pool_rates) + } + + /// Set how much percentage of treasury pool go to different incentive pools + #[pallet::weight(T::WeightInfo::set_treasury_pool())] + #[transactional] + pub fn set_treasury_pool( + origin: OriginFor, + treasury: T::AccountId, + threshold: Balance, + incentive_pools: Vec>, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + + Self::do_set_incentive_rate(treasury, threshold, incentive_pools) + } + + /// Force transfer token from treasury pool to incentive pool. + #[pallet::weight(T::WeightInfo::force_transfer_to_incentive())] + #[transactional] + pub fn force_transfer_to_incentive(origin: OriginFor, treasury: T::AccountId) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + + Self::distribution_incentive(treasury) + } + } +} + +impl Pallet { + pub fn do_set_treasury_rate( + income: IncomeSource, + treasury_pool_rates: Vec>, + ) -> DispatchResult { + ensure!(!treasury_pool_rates.is_empty(), Error::::InvalidParams); + Self::check_rates(&treasury_pool_rates)?; + + let pool_rates: BoundedVec, MaxPoolSize> = treasury_pool_rates + .clone() + .try_into() + .map_err(|_| Error::::InvalidParams)?; + IncomeToTreasuries::::try_mutate(income, |maybe_pool_rates| -> DispatchResult { + *maybe_pool_rates = pool_rates; + Ok(()) + })?; + + Self::deposit_event(Event::IncomeFeeSet { + income, + pools: treasury_pool_rates, + }); + Ok(()) + } + + pub fn do_set_incentive_rate( + treasury: T::AccountId, + threshold: Balance, + incentive_pool_rates: Vec>, + ) -> DispatchResult { + ensure!(!incentive_pool_rates.is_empty(), Error::::InvalidParams); + Self::check_rates(&incentive_pool_rates)?; + + let pool_rates: BoundedVec, MaxPoolSize> = incentive_pool_rates + .clone() + .try_into() + .map_err(|_| Error::::InvalidParams)?; + TreasuryToIncentives::::try_mutate(&treasury, |(maybe_threshold, maybe_pool_rates)| -> DispatchResult { + *maybe_pool_rates = pool_rates; + *maybe_threshold = threshold; + Ok(()) + })?; + + Self::deposit_event(Event::TreasuryPoolSet { + treasury, + pools: incentive_pool_rates, + }); + Ok(()) + } + + fn check_rates(pool_rates: &[PoolPercent]) -> DispatchResult { + let mut sum = FixedU128::zero(); + pool_rates.iter().for_each(|pool_rate| { + sum = sum.saturating_add(pool_rate.rate); + }); + ensure!(One::is_one(&sum), Error::::InvalidParams); + Ok(()) + } + + /// Distribute/Deposit income to treasury pool account. + fn distribution_fees( + pool_rates: BoundedVec, MaxPoolSize>, + currency_id: CurrencyId, + amount: Balance, + ) -> DispatchResult { + ensure!(!pool_rates.is_empty(), Error::::InvalidParams); + + pool_rates.into_iter().for_each(|pool_rate| { + let treasury_account = pool_rate.pool; + let amount_to_pool = pool_rate.rate.saturating_mul_int(amount); + + let deposit = T::Currencies::deposit(currency_id, &treasury_account, amount_to_pool); + if deposit.is_ok() { + // record token type for treasury account, used when distribute to incentive pools. + let _ = TreasuryTokens::::try_mutate(&treasury_account, |maybe_tokens| -> DispatchResult { + if !maybe_tokens.contains(¤cy_id) { + maybe_tokens + .try_push(currency_id) + .map_err(|_| Error::::InvalidParams)?; + } + Ok(()) + }); + } + }); + Ok(()) + } + + /// Transfer balance from treasury pool account to incentive pool account. + fn distribution_incentive(treasury: T::AccountId) -> DispatchResult { + let native_token = T::NativeCurrencyId::get(); + let tokens = TreasuryTokens::::get(&treasury); + let (threshold, pool_rates) = TreasuryToIncentives::::get(&treasury); + + let mut total_native: Balance = 0; + tokens.into_iter().for_each(|token| { + if let Some(native_amount) = Self::get_native_account(&treasury, native_token, token) { + total_native = total_native.saturating_add(native_amount); + } + }); + + // If treasury pool account has small native token, do not distribute to incentive pools. + if total_native < threshold { + return Ok(()); + } + + pool_rates.into_iter().for_each(|pool_rate| { + let treasury_account = pool_rate.pool; + let amount_to_pool = pool_rate.rate.saturating_mul_int(total_native); + + let _ = T::Currencies::transfer(native_token, &treasury, &treasury_account, amount_to_pool); + }); + + Self::deposit_event(Event::IncentiveDistribution { + treasury, + amount: total_native, + }); + Ok(()) + } + + /// Use dex swap foreign token to native token, then treasury pool account only has native + /// token. + fn get_native_account(treasury: &T::AccountId, native_token: CurrencyId, token: CurrencyId) -> Option { + if native_token == token { + let amount = T::Currency::free_balance(treasury); + Some(amount.unique_saturated_into()) + } else { + let amount = T::Currencies::free_balance(token, treasury); + let limit = SwapLimit::ExactSupply(amount, 0); + let swap_path = T::DEX::get_best_price_swap_path(token, native_token, limit, T::DexSwapJointList::get()); + if let Some((swap_path, _, _)) = swap_path { + if let Ok((_, native_amount)) = T::DEX::swap_with_specific_path(treasury, &swap_path, limit) { + return Some(native_amount); + } + } + None + } + } + + fn distribute_incentives() { + TreasuryToIncentives::::iter_keys().for_each(|treasury| { + let _ = Self::distribution_incentive(treasury); + }); + } +} + +impl OnFeeDeposit for Pallet { + /// Parameters: + /// - income: Income source, normally means existing modules. + /// - account_id: If given account, then the whole fee amount directly deposit to it. + /// - currency_id: currency type. + /// - amount: fee amount. + fn on_fee_deposit(income: IncomeSource, currency_id: CurrencyId, amount: Balance) -> DispatchResult { + // use `IncomeSource` to distribution fee to different treasury pool based on percentage. + let pool_rates: BoundedVec, MaxPoolSize> = IncomeToTreasuries::::get(income); + Pallet::::distribution_fees(pool_rates, currency_id, amount) + } +} + +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct DistributeTxFees(PhantomData); + +/// Transaction payment fee distribution. +impl OnUnbalanced> for DistributeTxFees { + fn on_unbalanceds(mut fees_then_tips: impl Iterator>) { + if let Some(mut fees) = fees_then_tips.next() { + if let Some(tips) = fees_then_tips.next() { + tips.merge_into(&mut fees); + } + + let pool_rates: BoundedVec, MaxPoolSize> = + IncomeToTreasuries::::get(IncomeSource::TxFee); + let pool_rates = pool_rates.into_iter().collect::>(); + + if let Some(pool) = pool_rates.get(0) { + let pool_id: &T::AccountId = &pool.pool; + let pool_rate: FixedU128 = pool.rate; + let pool_amount = pool_rate.saturating_mul_int(100u32); + let amount_other = 100u32.saturating_sub(pool_amount); + let split = fees.ration(pool_amount, amount_other); + ::Currency::resolve_creating(pool_id, split.0); + + // Current only support at least two treasury pool account for tx fee. + if let Some(pool) = pool_rates.get(1) { + let pool_id: &T::AccountId = &pool.pool; + ::Currency::resolve_creating(pool_id, split.1); + } + } + } + } +} + +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct DealTxFeesWithAccount(PhantomData<(T, A)>); + +/// All transaction fee distribute to treasury pool account. +impl> OnUnbalanced> + for DealTxFeesWithAccount +{ + fn on_unbalanceds(mut fees_then_tips: impl Iterator>) { + if let Some(mut fees) = fees_then_tips.next() { + if let Some(tips) = fees_then_tips.next() { + tips.merge_into(&mut fees); + } + + // Must resolve into existing but better to be safe. + T::Currency::resolve_creating(&A::get(), fees); + } + } +} diff --git a/modules/fees/src/mock.rs b/modules/fees/src/mock.rs new file mode 100644 index 000000000..b6e547e64 --- /dev/null +++ b/modules/fees/src/mock.rs @@ -0,0 +1,280 @@ +// 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 . + +//! Mocks for asset fee distribution module. + +#![cfg(test)] + +use crate as fees; +use frame_support::{ + construct_runtime, ord_parameter_types, + pallet_prelude::*, + parameter_types, + traits::{ConstU128, ConstU32, ConstU64, Everything, Nothing}, + PalletId, +}; +use frame_system::EnsureSignedBy; +use orml_traits::parameter_type_with_key; +use primitives::{ + AccountId, Amount, Balance, BlockNumber, CurrencyId, IncomeSource, ReserveIdentifier, TokenSymbol, TradingPair, +}; +use sp_core::H160; +use sp_runtime::traits::AccountIdConversion; +use support::mocks::MockAddressMapping; + +pub const ALICE: AccountId = AccountId::new([1u8; 32]); +pub const AUSD: CurrencyId = CurrencyId::Token(TokenSymbol::AUSD); +pub const ACA: CurrencyId = CurrencyId::Token(TokenSymbol::ACA); +pub const DOT: CurrencyId = CurrencyId::Token(TokenSymbol::DOT); + +impl frame_system::Config for Runtime { + type BaseCallFilter = Everything; + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = Call; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = sp_runtime::testing::Header; + type Event = Event; + type BlockHashCount = ConstU64<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = ReserveIdentifier; + type WeightInfo = (); +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = CurrencyId; + type ExistentialDeposits = ExistentialDeposits; + type OnDust = (); + type WeightInfo = (); + type MaxLocks = ConstU32<100>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type DustRemovalWhitelist = Nothing; + type OnNewTokenAccount = (); + type OnKilledTokenAccount = (); +} + +pub type AdaptedBasicCurrency = module_currencies::BasicCurrencyAdapter; + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = ACA; + pub const GetStakingCurrencyId: CurrencyId = DOT; + pub Erc20HoldingAccount: H160 = H160::from_low_u64_be(1); +} + +ord_parameter_types! { + pub const ListingOrigin: AccountId = ALICE; +} + +impl module_currencies::Config for Runtime { + type Event = Event; + type MultiCurrency = Tokens; + type NativeCurrency = AdaptedBasicCurrency; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); + type AddressMapping = MockAddressMapping; + type Erc20HoldingAccount = Erc20HoldingAccount; + type EVMBridge = (); + type GasToWeight = (); + type SweepOrigin = EnsureSignedBy; + type OnDust = (); +} + +parameter_types! { + pub TreasuryAccount: AccountId = PalletId(*b"aca/trsy").into_account_truncating(); + // Treasury pools + pub NetworkTreasuryPool: AccountId = PalletId(*b"aca/nktp").into_account_truncating(); + pub HonzonTreasuryPool: AccountId = PalletId(*b"aca/hztp").into_account_truncating(); + pub HomaTreasuryPool: AccountId = PalletId(*b"aca/hmtp").into_account_truncating(); + // Incentive reward Pools + pub HonzonInsuranceRewardPool: AccountId = PalletId(*b"aca/hirp").into_account_truncating(); + pub HonzonLiquitationRewardPool: AccountId = PalletId(*b"aca/hlrp").into_account_truncating(); + pub StakingRewardPool: AccountId = PalletId(*b"aca/strp").into_account_truncating(); + pub CollatorsRewardPool: AccountId = PalletId(*b"aca/clrp").into_account_truncating(); + pub EcosystemRewardPool: AccountId = PalletId(*b"aca/esrp").into_account_truncating(); + + pub AlternativeSwapPathJointList: Vec> = vec![vec![GetStakingCurrencyId::get()]]; +} + +impl fees::Config for Runtime { + type Event = Event; + type UpdateOrigin = EnsureSignedBy; + type Currency = Balances; + type Currencies = Currencies; + type NativeCurrencyId = GetNativeCurrencyId; + type AllocationPeriod = ConstU64<10>; + type DEX = DEX; + type DexSwapJointList = AlternativeSwapPathJointList; + type WeightInfo = (); +} + +parameter_types! { + pub const DEXPalletId: PalletId = PalletId(*b"aca/dexm"); + pub const GetExchangeFee: (u32, u32) = (0, 100); + pub EnabledTradingPairs: Vec = vec![ + TradingPair::from_currency_ids(AUSD, ACA).unwrap(), + TradingPair::from_currency_ids(AUSD, DOT).unwrap(), + ]; + pub const TradingPathLimit: u32 = 4; +} + +impl module_dex::Config for Runtime { + type Event = Event; + type Currency = Currencies; + type GetExchangeFee = GetExchangeFee; + type TradingPathLimit = TradingPathLimit; + type PalletId = DEXPalletId; + type Erc20InfoMapping = (); + type DEXIncentives = (); + type WeightInfo = (); + type ListingOrigin = EnsureSignedBy; + type ExtendedProvisioningBlocks = ConstU64<0>; + type OnLiquidityPoolUpdated = (); +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system, + Balances: pallet_balances, + Tokens: orml_tokens, + Currencies: module_currencies, + Fees: fees, + DEX: module_dex, + } +); + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![] } + } +} + +impl ExtBuilder { + pub fn balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + let native_currency_id = ACA; + + pallet_balances::GenesisConfig:: { + balances: self + .balances + .clone() + .into_iter() + .filter(|(_, currency_id, _)| *currency_id == native_currency_id) + .map(|(account_id, _, initial_balance)| (account_id, initial_balance)) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self + .balances + .into_iter() + .filter(|(_, currency_id, _)| *currency_id != native_currency_id) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + fees::GenesisConfig:: { + incomes: vec![ + ( + IncomeSource::TxFee, + vec![(NetworkTreasuryPool::get(), 80), (CollatorsRewardPool::get(), 20)], + ), + (IncomeSource::XcmFee, vec![(NetworkTreasuryPool::get(), 100)]), + ], + treasuries: vec![( + NetworkTreasuryPool::get(), + 100, + vec![ + (StakingRewardPool::get(), 80), + (CollatorsRewardPool::get(), 10), + (EcosystemRewardPool::get(), 10), + ], + )], + } + .assimilate_storage(&mut t) + .unwrap(); + + module_dex::GenesisConfig:: { + initial_listing_trading_pairs: vec![], + initial_enabled_trading_pairs: EnabledTradingPairs::get(), + initial_added_liquidity_pools: vec![], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/modules/fees/src/tests.rs b/modules/fees/src/tests.rs new file mode 100644 index 000000000..6833924ab --- /dev/null +++ b/modules/fees/src/tests.rs @@ -0,0 +1,408 @@ +// 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 . + +//! Unit tests for fee distribution module. + +#![cfg(test)] + +use super::*; +use crate::mock::*; +use frame_support::traits::{ExistenceRequirement, WithdrawReasons}; +use frame_support::{assert_noop, assert_ok}; +use mock::{Event, ExtBuilder, Origin, Runtime, System}; +use primitives::AccountId; + +#[test] +fn set_income_fee_works() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Fees::set_income_fee(Origin::signed(ALICE), IncomeSource::TxFee, vec![]), + Error::::InvalidParams, + ); + + let pools = + build_pool_percents::(vec![(NetworkTreasuryPool::get(), 70), (HonzonTreasuryPool::get(), 30)]); + assert_ok!(Fees::set_income_fee( + Origin::signed(ALICE), + IncomeSource::TxFee, + pools.clone() + )); + let incomes = IncomeToTreasuries::::get(IncomeSource::TxFee); + assert_eq!(incomes.len(), 2); + System::assert_last_event(Event::Fees(crate::Event::IncomeFeeSet { + income: IncomeSource::TxFee, + pools, + })); + }); +} + +#[test] +fn set_treasury_pool_works() { + ExtBuilder::default() + .balances(vec![(ALICE, ACA, 10000)]) + .build() + .execute_with(|| { + assert_noop!( + Fees::set_treasury_pool(Origin::signed(ALICE), NetworkTreasuryPool::get(), 100, vec![]), + Error::::InvalidParams, + ); + + let pools = build_pool_percents::(vec![ + (StakingRewardPool::get(), 70), + (CollatorsRewardPool::get(), 30), + ]); + assert_ok!(Fees::set_treasury_pool( + Origin::signed(ALICE), + NetworkTreasuryPool::get(), + 100, + pools.clone() + )); + let (threshold, incentives) = TreasuryToIncentives::::get(NetworkTreasuryPool::get()); + assert_eq!(incentives.len(), 2); + assert_eq!(threshold, 100); + System::assert_last_event(Event::Fees(crate::Event::TreasuryPoolSet { + treasury: NetworkTreasuryPool::get(), + pools: pools.clone(), + })); + + assert_ok!(Fees::set_treasury_pool( + Origin::signed(ALICE), + NetworkTreasuryPool::get(), + 10, + pools.clone() + )); + let (threshold, incentives) = TreasuryToIncentives::::get(NetworkTreasuryPool::get()); + assert_eq!(incentives.len(), 2); + assert_eq!(threshold, 10); + System::assert_last_event(Event::Fees(crate::Event::TreasuryPoolSet { + treasury: NetworkTreasuryPool::get(), + pools, + })); + }); +} + +#[test] +fn invalid_pool_rates_works() { + ExtBuilder::default().build().execute_with(|| { + let pools1 = + build_pool_percents::(vec![(NetworkTreasuryPool::get(), 70), (HonzonTreasuryPool::get(), 20)]); + let pools2 = + build_pool_percents::(vec![(NetworkTreasuryPool::get(), 70), (HonzonTreasuryPool::get(), 40)]); + let pools3 = + build_pool_percents::(vec![(StakingRewardPool::get(), 70), (CollatorsRewardPool::get(), 20)]); + + assert_noop!( + Fees::set_income_fee(Origin::signed(ALICE), IncomeSource::TxFee, pools1), + Error::::InvalidParams + ); + assert_noop!( + Fees::set_income_fee(Origin::signed(ALICE), IncomeSource::TxFee, pools2), + Error::::InvalidParams + ); + assert_noop!( + Fees::set_treasury_pool(Origin::signed(ALICE), NetworkTreasuryPool::get(), 100, pools3), + Error::::InvalidParams + ); + }); +} + +#[test] +fn tx_fee_allocation_works() { + ExtBuilder::default() + .balances(vec![(ALICE, ACA, 10000)]) + .build() + .execute_with(|| { + let pool_rates: BoundedVec, MaxPoolSize> = + IncomeToTreasuries::::get(IncomeSource::TxFee); + assert_eq!(2, pool_rates.len()); + + assert_eq!(0, Balances::free_balance(&NetworkTreasuryPool::get())); + assert_eq!(0, Balances::free_balance(&CollatorsRewardPool::get())); + + // Tx fee has two configuration in mock.rs setup. + let negative_balance = Balances::withdraw( + &ALICE, + 1000, + WithdrawReasons::TRANSACTION_PAYMENT, + ExistenceRequirement::KeepAlive, + ); + match negative_balance { + Ok(imbalance) => { + DistributeTxFees::::on_unbalanceds(Some(imbalance).into_iter()); + assert_eq!(800, Balances::free_balance(&NetworkTreasuryPool::get())); + assert_eq!(200, Balances::free_balance(&CollatorsRewardPool::get())); + } + Err(_) => {} + } + + // Update tx fee only to NetworkTreasuryPool account. + let pools = build_pool_percents::(vec![(NetworkTreasuryPool::get(), 100)]); + assert_ok!(Fees::set_income_fee( + Origin::signed(ALICE), + IncomeSource::TxFee, + pools.clone() + )); + let negative_balance = Balances::withdraw( + &ALICE, + 1000, + WithdrawReasons::TRANSACTION_PAYMENT, + ExistenceRequirement::KeepAlive, + ); + match negative_balance { + Ok(imbalance) => { + DistributeTxFees::::on_unbalanceds(Some(imbalance).into_iter()); + assert_eq!(1800, Balances::free_balance(&NetworkTreasuryPool::get())); + assert_eq!(200, Balances::free_balance(&CollatorsRewardPool::get())); + } + Err(_) => {} + } + + // Update tx fee to NetworkTreasuryPool and CollatorsRewardPool both 50%. + let pools = build_pool_percents::(vec![ + (NetworkTreasuryPool::get(), 50), + (CollatorsRewardPool::get(), 50), + ]); + assert_ok!(Fees::set_income_fee( + Origin::signed(ALICE), + IncomeSource::TxFee, + pools.clone() + )); + let negative_balance = Balances::withdraw( + &ALICE, + 1000, + WithdrawReasons::TRANSACTION_PAYMENT, + ExistenceRequirement::KeepAlive, + ); + match negative_balance { + Ok(imbalance) => { + DistributeTxFees::::on_unbalanceds(Some(imbalance).into_iter()); + assert_eq!(2300, Balances::free_balance(&NetworkTreasuryPool::get())); + assert_eq!(700, Balances::free_balance(&CollatorsRewardPool::get())); + } + Err(_) => {} + } + + // emit deposit event, just validate for last on_unbalanced() action + System::assert_has_event(Event::Balances(pallet_balances::Event::Deposit { + who: NetworkTreasuryPool::get(), + amount: 500, + })); + System::assert_has_event(Event::Balances(pallet_balances::Event::Deposit { + who: CollatorsRewardPool::get(), + amount: 500, + })); + }); +} + +#[test] +fn on_fee_deposit_works() { + ExtBuilder::default() + .balances(vec![(ALICE, ACA, 10000), (ALICE, DOT, 10000)]) + .build() + .execute_with(|| { + // Native token tests + // FeeToTreasuryPool based on pre-configured treasury pool percentage. + assert_ok!(Pallet::::on_fee_deposit(IncomeSource::TxFee, ACA, 10000)); + + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 8000); + assert_eq!(Currencies::free_balance(ACA, &CollatorsRewardPool::get()), 2000); + System::assert_has_event(Event::Balances(pallet_balances::Event::Deposit { + who: NetworkTreasuryPool::get(), + amount: 8000, + })); + System::assert_has_event(Event::Balances(pallet_balances::Event::Deposit { + who: CollatorsRewardPool::get(), + amount: 2000, + })); + assert_eq!( + crate::TreasuryTokens::::get(&NetworkTreasuryPool::get()).to_vec(), + vec![ACA] + ); + assert_eq!( + crate::TreasuryTokens::::get(&CollatorsRewardPool::get()).to_vec(), + vec![ACA] + ); + + // Non native token tests + // FeeToTreasuryPool based on pre-configured treasury pool percentage. + assert_ok!(Pallet::::on_fee_deposit(IncomeSource::TxFee, DOT, 10000)); + + assert_eq!(Currencies::free_balance(DOT, &NetworkTreasuryPool::get()), 8000); + assert_eq!(Currencies::free_balance(DOT, &CollatorsRewardPool::get()), 2000); + System::assert_has_event(Event::Tokens(orml_tokens::Event::Deposited { + currency_id: DOT, + who: NetworkTreasuryPool::get(), + amount: 8000, + })); + System::assert_has_event(Event::Tokens(orml_tokens::Event::Deposited { + currency_id: DOT, + who: CollatorsRewardPool::get(), + amount: 2000, + })); + assert_eq!( + crate::TreasuryTokens::::get(&NetworkTreasuryPool::get()).to_vec(), + vec![ACA, DOT] + ); + assert_eq!( + crate::TreasuryTokens::::get(&CollatorsRewardPool::get()).to_vec(), + vec![ACA, DOT] + ); + }); +} + +#[test] +fn force_transfer_to_incentive_works() { + ExtBuilder::default() + .balances(vec![(ALICE, ACA, 100000), (ALICE, AUSD, 10000)]) + .build() + .execute_with(|| { + let pool_rates = IncomeToTreasuries::::get(IncomeSource::TxFee); + + assert_ok!(Pallet::::distribution_fees(pool_rates.clone(), ACA, 1000,)); + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 800); + assert_eq!(Currencies::free_balance(ACA, &CollatorsRewardPool::get()), 200); + + assert_ok!(Pallet::::force_transfer_to_incentive( + Origin::signed(ALICE), + NetworkTreasuryPool::get() + )); + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 0); + assert_eq!(Currencies::free_balance(ACA, &StakingRewardPool::get()), 640); + assert_eq!(Currencies::free_balance(ACA, &CollatorsRewardPool::get()), 280); + assert_eq!(Currencies::free_balance(ACA, &EcosystemRewardPool::get()), 80); + System::assert_has_event(Event::Fees(crate::Event::IncentiveDistribution { + treasury: NetworkTreasuryPool::get(), + amount: 800, + })); + + assert_ok!(DEX::add_liquidity( + Origin::signed(ALICE), + ACA, + AUSD, + 10000, + 1000, + 0, + false + )); + + assert_ok!(Pallet::::distribution_fees(pool_rates, AUSD, 100)); + assert_eq!(Currencies::free_balance(AUSD, &NetworkTreasuryPool::get()), 80); + assert_eq!(Currencies::free_balance(AUSD, &CollatorsRewardPool::get()), 20); + + assert_ok!(Pallet::::force_transfer_to_incentive( + Origin::signed(ALICE), + NetworkTreasuryPool::get() + )); + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 0); + assert_eq!(Currencies::free_balance(AUSD, &NetworkTreasuryPool::get()), 0); + assert_eq!(Currencies::free_balance(ACA, &StakingRewardPool::get()), 640 + 592); + assert_eq!(Currencies::free_balance(ACA, &CollatorsRewardPool::get()), 280 + 74); + assert_eq!(Currencies::free_balance(ACA, &EcosystemRewardPool::get()), 80 + 74); + System::assert_has_event(Event::DEX(module_dex::Event::Swap { + trader: NetworkTreasuryPool::get(), + path: vec![AUSD, ACA], + liquidity_changes: vec![80, 740], + })); + System::assert_has_event(Event::Fees(crate::Event::IncentiveDistribution { + treasury: NetworkTreasuryPool::get(), + amount: 740, + })); + }); +} + +#[test] +fn distribution_incentive_threshold_works() { + ExtBuilder::default() + .balances(vec![(ALICE, ACA, 100000), (ALICE, AUSD, 10000)]) + .build() + .execute_with(|| { + let pool_rates = IncomeToTreasuries::::get(IncomeSource::TxFee); + + assert_ok!(Pallet::::distribution_fees(pool_rates.clone(), ACA, 100)); + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 80); + assert_eq!(Currencies::free_balance(ACA, &CollatorsRewardPool::get()), 20); + + assert_ok!(Pallet::::distribution_incentive(NetworkTreasuryPool::get())); + // due to native token less than threshold, not distribute to incentive pools. + // but swap still happened, so treasury account got native token. + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 80); + assert_eq!(Currencies::free_balance(ACA, &StakingRewardPool::get()), 0); + assert_eq!(Currencies::free_balance(ACA, &EcosystemRewardPool::get()), 0); + + assert_ok!(Pallet::::distribution_fees(pool_rates.clone(), ACA, 25)); + // now treasury account native token large than threshold + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 100); + assert_eq!(Currencies::free_balance(ACA, &CollatorsRewardPool::get()), 25); + + assert_ok!(Pallet::::distribution_incentive(NetworkTreasuryPool::get())); + // then distribution to incentive pools + assert_eq!(Currencies::free_balance(ACA, &StakingRewardPool::get()), 80); + assert_eq!(Currencies::free_balance(ACA, &EcosystemRewardPool::get()), 10); + assert_eq!(Currencies::free_balance(ACA, &CollatorsRewardPool::get()), 25 + 10); + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 0); + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 0); + System::assert_has_event(Event::Fees(crate::Event::IncentiveDistribution { + treasury: NetworkTreasuryPool::get(), + amount: 100, + })); + + assert_ok!(DEX::add_liquidity( + Origin::signed(ALICE), + ACA, + AUSD, + 10000, + 1000, + 0, + false + )); + + // swapped out native token less then threshold + assert_ok!(Pallet::::distribution_fees(pool_rates.clone(), AUSD, 10)); + assert_eq!(Currencies::free_balance(AUSD, &NetworkTreasuryPool::get()), 8); + assert_eq!(Currencies::free_balance(AUSD, &CollatorsRewardPool::get()), 2); + + assert_ok!(Pallet::::distribution_incentive(NetworkTreasuryPool::get())); + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 79); + System::assert_has_event(Event::DEX(module_dex::Event::Swap { + trader: NetworkTreasuryPool::get(), + path: vec![AUSD, ACA], + liquidity_changes: vec![8, 79], + })); + + assert_ok!(Pallet::::distribution_fees(pool_rates, AUSD, 10)); + assert_eq!(Currencies::free_balance(AUSD, &NetworkTreasuryPool::get()), 8); + assert_eq!(Currencies::free_balance(AUSD, &CollatorsRewardPool::get()), 2 + 2); + + assert_ok!(Pallet::::distribution_incentive(NetworkTreasuryPool::get())); + assert_eq!(Currencies::free_balance(ACA, &StakingRewardPool::get()), 80 + 125); + assert_eq!(Currencies::free_balance(ACA, &CollatorsRewardPool::get()), 35 + 15); + assert_eq!(Currencies::free_balance(ACA, &EcosystemRewardPool::get()), 10 + 15); + // due to percent round, there are some native token left in treasury account. + assert_eq!(Currencies::free_balance(ACA, &NetworkTreasuryPool::get()), 2); + assert_eq!(Currencies::free_balance(AUSD, &NetworkTreasuryPool::get()), 0); + System::assert_has_event(Event::DEX(module_dex::Event::Swap { + trader: NetworkTreasuryPool::get(), + path: vec![AUSD, ACA], + liquidity_changes: vec![8, 78], + })); + System::assert_has_event(Event::Fees(crate::Event::IncentiveDistribution { + treasury: NetworkTreasuryPool::get(), + amount: 79 + 78, + })); + }); +} diff --git a/modules/fees/src/weights.rs b/modules/fees/src/weights.rs new file mode 100644 index 000000000..5f8902673 --- /dev/null +++ b/modules/fees/src/weights.rs @@ -0,0 +1,101 @@ +// 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 . + +//! Autogenerated weights for module_fees +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-06-20, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/acala +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_fees +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./modules/fees/src/weights.rs +// --template=./templates/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for module_fees. +pub trait WeightInfo { + fn set_income_fee() -> Weight; + fn set_treasury_pool() -> Weight; + fn force_transfer_to_incentive() -> Weight; +} + +/// Weights for module_fees using the Acala node and recommended hardware. +pub struct AcalaWeight(PhantomData); +impl WeightInfo for AcalaWeight { + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees IncomeToTreasuries (r:1 w:1) + fn set_income_fee() -> Weight { + (24_046_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees TreasuryToIncentives (r:1 w:1) + fn set_treasury_pool() -> Weight { + (24_273_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees TreasuryTokens (r:1 w:0) + // Storage: Fees TreasuryToIncentives (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + fn force_transfer_to_incentive() -> Weight { + (69_853_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn set_income_fee() -> Weight { + (24_046_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + fn set_treasury_pool() -> Weight { + (24_273_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + fn force_transfer_to_incentive() -> Weight { + (69_853_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } +} diff --git a/modules/homa/Cargo.toml b/modules/homa/Cargo.toml index 41d138331..622cfe7d5 100644 --- a/modules/homa/Cargo.toml +++ b/modules/homa/Cargo.toml @@ -17,6 +17,7 @@ sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } orml-traits = { path = "../../orml/traits", default-features = false } module-support = { path = "../../modules/support", default-features = false } +module-fees = { path = "../fees", default-features = false } [dev-dependencies] sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" } @@ -40,6 +41,7 @@ std = [ "primitives/std", "orml-traits/std", "module-support/std", + "module-fees/std", ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/modules/homa/src/lib.rs b/modules/homa/src/lib.rs index c06413828..736735581 100644 --- a/modules/homa/src/lib.rs +++ b/modules/homa/src/lib.rs @@ -23,9 +23,9 @@ use frame_support::{log, pallet_prelude::*, transactional, PalletId}; use frame_system::{ensure_signed, pallet_prelude::*}; -use module_support::{ExchangeRate, ExchangeRateProvider, HomaManager, HomaSubAccountXcm, Rate, Ratio}; +use module_support::{ExchangeRate, ExchangeRateProvider, HomaManager, HomaSubAccountXcm, OnFeeDeposit, Rate, Ratio}; use orml_traits::MultiCurrency; -use primitives::{Balance, CurrencyId, EraIndex}; +use primitives::{Balance, CurrencyId, EraIndex, IncomeSource}; use scale_info::TypeInfo; use sp_runtime::{ traits::{ @@ -123,10 +123,6 @@ pub mod module { #[pallet::constant] type DefaultExchangeRate: Get; - /// Vault reward of Homa protocol - #[pallet::constant] - type TreasuryAccount: Get; - /// The index list of active Homa subaccounts. /// `active` means these subaccounts can continue do bond/unbond operations by Homa. #[pallet::constant] @@ -152,6 +148,9 @@ pub mod module { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; + + /// Where the staking reward fee go to. + type OnFeeDeposit: OnFeeDeposit; } #[pallet::error] @@ -324,7 +323,7 @@ pub mod module { pub type SoftBondedCapPerSubAccount = StorageValue<_, Balance, ValueQuery>; /// The rate of Homa drawn from the staking reward as commission. - /// The draw will be transfer to TreasuryAccount of Homa in liquid currency. + /// The draw will be transfer to OnFeeDeposit in liquid currency. /// /// CommissionRate: value: Rate #[pallet::storage] @@ -472,8 +471,7 @@ pub mod module { /// on relaychain to obtain the best staking rewards. /// - `estimated_reward_rate_per_era`: the estimated staking yield of each era on the /// current relay chain. - /// - `commission_rate`: the rate to draw from estimated staking rewards as commission to - /// HomaTreasury + /// - `commission_rate`: the rate to draw from estimated staking rewards as commission /// - `fast_match_fee_rate`: the fixed fee rate when redeem request is been fast matched. #[pallet::weight(< T as Config >::WeightInfo::update_homa_params())] #[transactional] @@ -860,7 +858,7 @@ pub mod module { /// Accumulate staking rewards according to EstimatedRewardRatePerEra and era internally. /// And draw commission from estimated staking rewards by issuing liquid currency to - /// TreasuryAccount. Note: This will cause some losses to the minters in previous_era, + /// OnFeeDeposit. Note: This will cause some losses to the minters in previous_era, /// because they have been already deducted some liquid currency amount when mint in /// previous_era. Until there is a better way to calculate, this part of the loss can only /// be regarded as an implicit mint fee! @@ -901,7 +899,12 @@ pub mod module { .unwrap_or_else(Ratio::max_value); let inflate_liquid_amount = inflate_rate.saturating_mul_int(Self::get_total_liquid_currency()); - T::Currency::deposit(liquid_currency_id, &T::TreasuryAccount::get(), inflate_liquid_amount)?; + // Staking rewards goes to T::OnFeeDeposit + T::OnFeeDeposit::on_fee_deposit( + IncomeSource::HomaStakingRewardFee, + liquid_currency_id, + inflate_liquid_amount, + )?; } } diff --git a/modules/homa/src/mock.rs b/modules/homa/src/mock.rs index bad1b8e47..469cf90f1 100644 --- a/modules/homa/src/mock.rs +++ b/modules/homa/src/mock.rs @@ -171,11 +171,25 @@ ord_parameter_types! { pub const HomaAdmin: AccountId = DAVE; } +parameter_types! { + pub AlternativeSwapPathJointList: Vec> = vec![]; +} +impl module_fees::Config for Runtime { + type Event = Event; + type UpdateOrigin = EnsureRoot; + type Currency = Balances; + type Currencies = Currencies; + type DEX = (); + type WeightInfo = (); + type NativeCurrencyId = GetNativeCurrencyId; + type AllocationPeriod = ConstU64<10>; + type DexSwapJointList = AlternativeSwapPathJointList; +} + parameter_types! { pub const StakingCurrencyId: CurrencyId = STAKING_CURRENCY_ID; pub const LiquidCurrencyId: CurrencyId = LIQUID_CURRENCY_ID; pub const HomaPalletId: PalletId = PalletId(*b"aca/homa"); - pub const TreasuryAccount: AccountId = HOMA_TREASURY; pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10); pub ActiveSubAccountsIndexList: Vec = vec![0, 1, 2]; pub const BondingDuration: EraIndex = 28; @@ -191,7 +205,6 @@ impl Config for Runtime { type StakingCurrencyId = StakingCurrencyId; type LiquidCurrencyId = LiquidCurrencyId; type PalletId = HomaPalletId; - type TreasuryAccount = TreasuryAccount; type DefaultExchangeRate = DefaultExchangeRate; type ActiveSubAccountsIndexList = ActiveSubAccountsIndexList; type BondingDuration = BondingDuration; @@ -199,6 +212,7 @@ impl Config for Runtime { type RedeemThreshold = RedeemThreshold; type RelayChainBlockNumber = MockRelayBlockNumberProvider; type XcmInterface = MockHomaSubAccountXcm; + type OnFeeDeposit = Fees; type WeightInfo = (); } @@ -211,11 +225,12 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Homa: homa::{Pallet, Call, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Tokens: orml_tokens::{Pallet, Storage, Event, Config}, - Currencies: module_currencies::{Pallet, Call, Event}, + System: frame_system, + Homa: homa, + Balances: pallet_balances, + Tokens: orml_tokens, + Currencies: module_currencies, + Fees: module_fees, } ); diff --git a/modules/homa/src/tests.rs b/modules/homa/src/tests.rs index a7b720394..e2a6c9b1b 100644 --- a/modules/homa/src/tests.rs +++ b/modules/homa/src/tests.rs @@ -19,13 +19,24 @@ //! Unit tests for the Homa Module #![cfg(test)] - use super::*; use frame_support::{assert_noop, assert_ok}; use mock::{Event, *}; +use module_fees::PoolPercent; use orml_traits::MultiCurrency; use sp_runtime::{traits::BadOrigin, FixedPointNumber}; +fn setup_fees_distribution() { + assert_ok!(Fees::set_income_fee( + Origin::root(), + IncomeSource::HomaStakingRewardFee, + vec![PoolPercent { + pool: HOMA_TREASURY, + rate: Rate::one(), + }], + )); +} + #[test] fn mint_works() { ExtBuilder::default() @@ -675,6 +686,8 @@ fn process_staking_rewards_works() { .balances(vec![(ALICE, LIQUID_CURRENCY_ID, 40_000_000)]) .build() .execute_with(|| { + setup_fees_distribution(); + assert_ok!(Homa::reset_ledgers( Origin::signed(HomaAdmin::get()), vec![(0, Some(3_000_000), None), (1, Some(1_000_000), None),] @@ -702,7 +715,7 @@ fn process_staking_rewards_works() { ); assert_eq!(Homa::get_total_bonded(), 4_000_000); assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 40_000_000); - assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &HOMA_TREASURY), 0); // accumulate staking rewards, no commission assert_ok!(Homa::process_staking_rewards(1, 0)); @@ -722,7 +735,7 @@ fn process_staking_rewards_works() { ); assert_eq!(Homa::get_total_bonded(), 4_800_000); assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 40_000_000); - assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &HOMA_TREASURY), 0); assert_ok!(Homa::update_homa_params( Origin::signed(HomaAdmin::get()), @@ -750,10 +763,7 @@ fn process_staking_rewards_works() { ); assert_eq!(Homa::get_total_bonded(), 5_760_000); assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 40_677_966); - assert_eq!( - Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), - 677_966 - ); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &HOMA_TREASURY), 677_966); }); } @@ -1166,6 +1176,8 @@ fn bump_current_era_works() { .balances(vec![(ALICE, STAKING_CURRENCY_ID, 100_000_000)]) .build() .execute_with(|| { + setup_fees_distribution(); + assert_ok!(Homa::update_homa_params( Origin::signed(HomaAdmin::get()), Some(20_000_000), @@ -1188,7 +1200,7 @@ fn bump_current_era_works() { assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 0); assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); - assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &HOMA_TREASURY), 0); assert_ok!(Homa::mint(Origin::signed(ALICE), 30_000_000)); assert_eq!(Homa::to_bond_pool(), 30_000_000); @@ -1229,7 +1241,7 @@ fn bump_current_era_works() { assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 297_029_702); assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); - assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &HOMA_TREASURY), 0); // bump era to #2, // accumulate staking reward and draw commission @@ -1260,10 +1272,7 @@ fn bump_current_era_works() { assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 297_619_046); assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); - assert_eq!( - Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), - 589_344 - ); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &HOMA_TREASURY), 589_344); // assuming now staking has no rewards any more. assert_ok!(Homa::update_homa_params( @@ -1322,10 +1331,7 @@ fn bump_current_era_works() { assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 17_619_046); assert_eq!(Currencies::free_balance(STAKING_CURRENCY_ID, &Homa::account_id()), 0); assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); - assert_eq!( - Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), - 589_344 - ); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &HOMA_TREASURY), 589_344); // bump era to #31, // will process scheduled unbonded @@ -1353,9 +1359,68 @@ fn bump_current_era_works() { 26_605_824 ); assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &Homa::account_id()), 0); - assert_eq!( - Currencies::free_balance(LIQUID_CURRENCY_ID, &TreasuryAccount::get()), - 589_344 - ); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &HOMA_TREASURY), 589_344); }); } + +#[test] +fn staking_reward_fee_distribution_works() { + ExtBuilder::default().build().execute_with(|| { + setup_fees_distribution(); + // 100% staking reward and 100% commission rate + assert_ok!(Homa::update_homa_params( + Origin::signed(HomaAdmin::get()), + Some(1_000_000_000), + Some(Rate::one()), + Some(Rate::one()), + Some(Rate::saturating_from_rational(1, 100)), + )); + + assert_ok!(Homa::reset_ledgers( + Origin::signed(HomaAdmin::get()), + vec![(0, Some(50_000), None), (1, Some(50_000), None),] + )); + assert_ok!(Currencies::deposit(LIQUID_CURRENCY_ID, &ALICE, 100_000)); + + assert_eq!(Homa::get_total_bonded(), 100_000); + + // Forward 1 era + assert_ok!(Homa::process_staking_rewards(1, 0)); + + // as per setup, 100% of staking reward goes to HOMA_TREASURY + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &HOMA_TREASURY), 100_000); + + assert_ok!(Fees::set_income_fee( + Origin::root(), + IncomeSource::HomaStakingRewardFee, + vec![ + PoolPercent { + pool: BOB, + rate: Rate::saturating_from_rational(2, 10), + }, + PoolPercent { + pool: CHARLIE, + rate: Rate::saturating_from_rational(3, 10), + }, + PoolPercent { + pool: DAVE, + rate: Rate::saturating_from_rational(5, 10), + } + ], + )); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &BOB), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &CHARLIE), 0); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &DAVE), 0); + + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 200_000); + + // Forward 1 era + assert_ok!(Homa::process_staking_rewards(2, 1)); + assert_eq!(Currencies::total_issuance(LIQUID_CURRENCY_ID), 400_000); + + // Liquid reward is distributed into + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &BOB), 40_000); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &CHARLIE), 60_000); + assert_eq!(Currencies::free_balance(LIQUID_CURRENCY_ID, &DAVE), 100_000); + }); +} diff --git a/modules/honzon/Cargo.toml b/modules/honzon/Cargo.toml index 6881e54fe..a526c7798 100644 --- a/modules/honzon/Cargo.toml +++ b/modules/honzon/Cargo.toml @@ -15,6 +15,7 @@ sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v cdp-engine = { package = "module-cdp-engine", path = "../cdp-engine", default-features = false } loans = { package = "module-loans", path = "../loans", default-features = false } support = { package = "module-support", path = "../support", default-features = false } +module-fees = {path = "../fees", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } [dev-dependencies] @@ -39,6 +40,7 @@ std = [ "sp-std/std", "loans/std", "cdp-engine/std", + "module-fees/std", "support/std", "primitives/std", ] diff --git a/modules/honzon/src/mock.rs b/modules/honzon/src/mock.rs index 9e1177b68..d5fd11591 100644 --- a/modules/honzon/src/mock.rs +++ b/modules/honzon/src/mock.rs @@ -27,7 +27,7 @@ use frame_support::{ traits::{ConstU128, ConstU32, ConstU64, Everything, Nothing}, PalletId, }; -use frame_system::{offchain::SendTransactionTypes, EnsureSignedBy}; +use frame_system::{offchain::SendTransactionTypes, EnsureRoot, EnsureSignedBy}; use orml_traits::parameter_type_with_key; use primitives::{Balance, Moment, ReserveIdentifier, TokenSymbol}; use sp_core::H256; @@ -262,6 +262,19 @@ impl cdp_engine::Config for Runtime { type Currency = Currencies; type DEX = (); type Swap = SpecificJointsSwap<(), AlternativeSwapPathJointList>; + type OnFeeDeposit = Fees; + type WeightInfo = (); +} + +impl module_fees::Config for Runtime { + type Event = Event; + type UpdateOrigin = EnsureRoot; + type Currency = PalletBalances; + type Currencies = Currencies; + type NativeCurrencyId = GetNativeCurrencyId; + type AllocationPeriod = ConstU64<10>; + type DEX = (); + type DexSwapJointList = AlternativeSwapPathJointList; type WeightInfo = (); } @@ -282,15 +295,16 @@ construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - HonzonModule: honzon::{Pallet, Storage, Call, Event}, - Tokens: orml_tokens::{Pallet, Storage, Event, Config}, - PalletBalances: pallet_balances::{Pallet, Call, Storage, Event}, - Currencies: orml_currencies::{Pallet, Call}, - LoansModule: loans::{Pallet, Storage, Call, Event}, - CDPTreasuryModule: cdp_treasury::{Pallet, Storage, Call, Event}, - CDPEngineModule: cdp_engine::{Pallet, Storage, Call, Event, Config, ValidateUnsigned}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + System: frame_system, + HonzonModule: honzon, + Tokens: orml_tokens, + PalletBalances: pallet_balances, + Currencies: orml_currencies, + LoansModule: loans, + CDPTreasuryModule: cdp_treasury, + CDPEngineModule: cdp_engine, + Timestamp: pallet_timestamp, + Fees: module_fees, } ); diff --git a/modules/support/src/lib.rs b/modules/support/src/lib.rs index d0bc2b8a7..c9700a2e0 100644 --- a/modules/support/src/lib.rs +++ b/modules/support/src/lib.rs @@ -23,7 +23,7 @@ use codec::FullCodec; use frame_support::pallet_prelude::{DispatchClass, Pays, Weight}; -use primitives::{task::TaskResult, CurrencyId, Multiplier, ReserveIdentifier}; +use primitives::{task::TaskResult, CurrencyId, IncomeSource, Multiplier, ReserveIdentifier}; use sp_runtime::{ traits::CheckedDiv, transaction_validity::TransactionValidityError, DispatchError, DispatchResult, FixedU128, }; @@ -94,6 +94,10 @@ pub trait TransactionPayment { fn apply_multiplier_to_fee(fee: Balance, multiplier: Option) -> Balance; } +pub trait OnFeeDeposit { + fn on_fee_deposit(income: IncomeSource, currency_id: CurrencyId, amount: Balance) -> DispatchResult; +} + /// Used to interface with the Compound's Cash module pub trait CompoundCashTrait { fn set_future_yield(next_cash_yield: Balance, yield_index: u128, timestamp_effective: Moment) -> DispatchResult; diff --git a/node/service/src/chain_spec/acala.rs b/node/service/src/chain_spec/acala.rs index 9ba10c2b8..316f96869 100644 --- a/node/service/src/chain_spec/acala.rs +++ b/node/service/src/chain_spec/acala.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use acala_primitives::AccountId; +use acala_primitives::{AccountId, IncomeSource}; use sc_chain_spec::{ChainType, Properties}; use serde_json::map::Map; use sp_consensus_aura::sr25519::AuthorityId as AuraId; @@ -26,11 +26,12 @@ use sp_runtime::traits::Zero; use crate::chain_spec::{get_account_id_from_seed, get_parachain_authority_keys_from_seed, Extensions}; use acala_runtime::{ - dollar, Balance, BalancesConfig, BlockNumber, CdpEngineConfig, CdpTreasuryConfig, CollatorSelectionConfig, - DexConfig, EVMConfig, FinancialCouncilMembershipConfig, GeneralCouncilMembershipConfig, - HomaCouncilMembershipConfig, OperatorMembershipAcalaConfig, OrmlNFTConfig, ParachainInfoConfig, PolkadotXcmConfig, - SS58Prefix, SessionConfig, SessionDuration, SessionKeys, SessionManagerConfig, SudoConfig, SystemConfig, - TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, ACA, AUSD, DOT, LDOT, + dollar, AcalaTreasuryAccount, Balance, BalancesConfig, BlockNumber, CdpEngineConfig, CdpTreasuryConfig, + CollatorSelectionConfig, DexConfig, EVMConfig, FeesConfig, FinancialCouncilMembershipConfig, + GeneralCouncilMembershipConfig, HomaCouncilMembershipConfig, OperatorMembershipAcalaConfig, OrmlNFTConfig, + ParachainInfoConfig, PolkadotXcmConfig, SS58Prefix, SessionConfig, SessionDuration, SessionKeys, + SessionManagerConfig, SudoConfig, SystemConfig, TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, + ACA, AUSD, DOT, LDOT, }; use runtime_common::TokenInfo; @@ -194,5 +195,66 @@ fn acala_dev_genesis( polkadot_xcm: PolkadotXcmConfig { safe_xcm_version: Some(2), }, + fees: fees_config(), + } +} + +fn fees_config() -> FeesConfig { + FeesConfig { + incomes: vec![ + ( + IncomeSource::TxFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::XcmFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::DexSwapFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::HonzonStabilityFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HonzonTreasuryPool::get(), 30), + ], + ), + ( + IncomeSource::HonzonLiquidationFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 30), + (runtime_common::HonzonTreasuryPool::get(), 70), + ], + ), + ( + IncomeSource::HomaStakingRewardFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HomaTreasuryPool::get(), 30), + ], + ), + ], + treasuries: vec![ + ( + runtime_common::NetworkTreasuryPool::get(), + 1000 * dollar(ACA), + vec![ + (runtime_common::StakingRewardPool::get(), 70), + (runtime_common::CollatorsRewardPool::get(), 10), + (runtime_common::EcosystemRewardPool::get(), 10), + (AcalaTreasuryAccount::get(), 10), + ], + ), + ( + runtime_common::HonzonTreasuryPool::get(), + 1000 * dollar(ACA), + vec![ + (runtime_common::HonzonInsuranceRewardPool::get(), 30), + (runtime_common::HonzonLiquitationRewardPool::get(), 70), + ], + ), + ], } } diff --git a/node/service/src/chain_spec/karura.rs b/node/service/src/chain_spec/karura.rs index 720a53dbc..01d20aa3e 100644 --- a/node/service/src/chain_spec/karura.rs +++ b/node/service/src/chain_spec/karura.rs @@ -16,21 +16,22 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use acala_primitives::AccountId; +use acala_primitives::{AccountId, IncomeSource}; use sc_chain_spec::{ChainType, Properties}; use serde_json::map::Map; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::sr25519; -use sp_runtime::traits::Zero; +use sp_runtime::traits::{AccountIdConversion, Zero}; use crate::chain_spec::{get_account_id_from_seed, get_parachain_authority_keys_from_seed, Extensions}; use karura_runtime::{ - dollar, Balance, BalancesConfig, BlockNumber, CdpEngineConfig, CdpTreasuryConfig, CollatorSelectionConfig, - DexConfig, EVMConfig, FinancialCouncilMembershipConfig, GeneralCouncilMembershipConfig, - HomaCouncilMembershipConfig, OperatorMembershipAcalaConfig, OrmlNFTConfig, ParachainInfoConfig, PolkadotXcmConfig, - SS58Prefix, SessionConfig, SessionDuration, SessionKeys, SessionManagerConfig, SudoConfig, SystemConfig, - TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, BNC, KAR, KSM, KUSD, LKSM, PHA, VSKSM, + dollar, Balance, BalancesConfig, BlockNumber, CDPTreasuryPalletId, CdpEngineConfig, CdpTreasuryConfig, + CollatorSelectionConfig, DexConfig, EVMConfig, FeesConfig, FinancialCouncilMembershipConfig, + GeneralCouncilMembershipConfig, HomaCouncilMembershipConfig, KaruraTreasuryAccount, OperatorMembershipAcalaConfig, + OrmlNFTConfig, ParachainInfoConfig, PolkadotXcmConfig, SS58Prefix, SessionConfig, SessionDuration, SessionKeys, + SessionManagerConfig, SudoConfig, SystemConfig, TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, + BNC, KAR, KSM, KUSD, LKSM, PHA, VSKSM, }; use runtime_common::TokenInfo; @@ -194,5 +195,60 @@ fn karura_dev_genesis( polkadot_xcm: PolkadotXcmConfig { safe_xcm_version: Some(2), }, + fees: fees_config(), + } +} + +fn fees_config() -> FeesConfig { + FeesConfig { + incomes: vec![ + ( + IncomeSource::TxFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::XcmFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::DexSwapFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::HonzonStabilityFee, + vec![(runtime_common::HonzonTreasuryPool::get(), 100)], + ), + ( + IncomeSource::HonzonLiquidationFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 30), + (runtime_common::HonzonTreasuryPool::get(), 70), + ], + ), + ( + IncomeSource::HomaStakingRewardFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HomaTreasuryPool::get(), 30), + ], + ), + ], + treasuries: vec![ + ( + runtime_common::NetworkTreasuryPool::get(), + 1000 * dollar(KAR), + vec![ + (runtime_common::StakingRewardPool::get(), 70), + (runtime_common::CollatorsRewardPool::get(), 10), + (runtime_common::EcosystemRewardPool::get(), 10), + (KaruraTreasuryAccount::get(), 10), + ], + ), + ( + runtime_common::HonzonTreasuryPool::get(), + 10 * dollar(KAR), + vec![(CDPTreasuryPalletId::get().into_account_truncating(), 100)], + ), + ], } } diff --git a/node/service/src/chain_spec/mandala.rs b/node/service/src/chain_spec/mandala.rs index 946e0c98c..92e901d22 100644 --- a/node/service/src/chain_spec/mandala.rs +++ b/node/service/src/chain_spec/mandala.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use acala_primitives::{orml_traits::GetByKey, AccountId, Balance, TokenSymbol}; +use acala_primitives::{orml_traits::GetByKey, AccountId, Balance, IncomeSource, TokenSymbol}; use coins_bip39::{English, Mnemonic, Wordlist}; use elliptic_curve::sec1::ToEncodedPoint; use hex_literal::hex; @@ -24,7 +24,8 @@ use k256::{ ecdsa::{SigningKey, VerifyingKey}, EncodedPoint as K256PublicKey, }; -use runtime_common::evm_genesis; +use mandala_runtime::{FeesConfig, TreasuryAccount, ACA}; +use runtime_common::{dollar, evm_genesis}; use sc_chain_spec::ChainType; use sc_telemetry::TelemetryEndpoints; use serde_json::map::Map; @@ -308,12 +309,12 @@ fn testnet_genesis( evm_accounts: Vec, ) -> mandala_runtime::GenesisConfig { use mandala_runtime::{ - dollar, get_all_module_accounts, AssetRegistryConfig, BalancesConfig, CdpEngineConfig, CdpTreasuryConfig, + get_all_module_accounts, AssetRegistryConfig, BalancesConfig, CdpEngineConfig, CdpTreasuryConfig, CollatorSelectionConfig, DexConfig, EVMConfig, EnabledTradingPairs, ExistentialDeposits, FinancialCouncilMembershipConfig, GeneralCouncilMembershipConfig, HomaCouncilMembershipConfig, IndicesConfig, NativeTokenExistentialDeposit, OperatorMembershipAcalaConfig, OrmlNFTConfig, ParachainInfoConfig, PolkadotXcmConfig, RenVmBridgeConfig, SessionConfig, SessionDuration, SessionKeys, SessionManagerConfig, - StarportConfig, SudoConfig, SystemConfig, TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, ACA, + StarportConfig, SudoConfig, SystemConfig, TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, AUSD, DOT, LDOT, RENBTC, }; @@ -498,6 +499,7 @@ fn testnet_genesis( safe_xcm_version: Some(2), }, phragmen_election: Default::default(), + fees: fees_config(), } } @@ -508,12 +510,12 @@ fn mandala_genesis( endowed_accounts: Vec, ) -> mandala_runtime::GenesisConfig { use mandala_runtime::{ - cent, dollar, get_all_module_accounts, AssetRegistryConfig, BalancesConfig, CdpEngineConfig, CdpTreasuryConfig, + cent, get_all_module_accounts, AssetRegistryConfig, BalancesConfig, CdpEngineConfig, CdpTreasuryConfig, CollatorSelectionConfig, DexConfig, EVMConfig, EnabledTradingPairs, ExistentialDeposits, FinancialCouncilMembershipConfig, GeneralCouncilMembershipConfig, HomaCouncilMembershipConfig, IndicesConfig, NativeTokenExistentialDeposit, OperatorMembershipAcalaConfig, OrmlNFTConfig, ParachainInfoConfig, PolkadotXcmConfig, RenVmBridgeConfig, SessionConfig, SessionDuration, SessionKeys, SessionManagerConfig, - StarportConfig, SudoConfig, SystemConfig, TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, ACA, + StarportConfig, SudoConfig, SystemConfig, TechnicalCommitteeMembershipConfig, TokensConfig, VestingConfig, AUSD, DOT, LDOT, RENBTC, }; @@ -680,5 +682,69 @@ fn mandala_genesis( safe_xcm_version: Some(2), }, phragmen_election: Default::default(), + fees: fees_config(), + } +} + +fn fees_config() -> FeesConfig { + FeesConfig { + incomes: vec![ + ( + IncomeSource::TxFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 80), + (runtime_common::CollatorsRewardPool::get(), 20), + ], + ), + ( + IncomeSource::XcmFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::DexSwapFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::HonzonStabilityFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HonzonTreasuryPool::get(), 30), + ], + ), + ( + IncomeSource::HonzonLiquidationFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 30), + (runtime_common::HonzonTreasuryPool::get(), 70), + ], + ), + ( + IncomeSource::HomaStakingRewardFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HomaTreasuryPool::get(), 30), + ], + ), + ], + treasuries: vec![ + ( + runtime_common::NetworkTreasuryPool::get(), + 1000 * dollar(ACA), + vec![ + (runtime_common::StakingRewardPool::get(), 70), + (runtime_common::CollatorsRewardPool::get(), 10), + (runtime_common::EcosystemRewardPool::get(), 10), + (TreasuryAccount::get(), 10), + ], + ), + ( + runtime_common::HonzonTreasuryPool::get(), + 1000 * dollar(ACA), + vec![ + (runtime_common::HonzonInsuranceRewardPool::get(), 30), + (runtime_common::HonzonLiquitationRewardPool::get(), 70), + ], + ), + ], } } diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index eecc305bb..240371fb9 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -192,6 +192,17 @@ pub enum ReserveIdentifier { Count, } +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum IncomeSource { + TxFee, + XcmFee, + DexSwapFee, + HonzonStabilityFee, + HonzonLiquidationFee, + HomaStakingRewardFee, +} + pub type CashYieldIndex = u128; /// Convert any type that implements Into into byte representation ([u8, 32]) diff --git a/runtime/acala/Cargo.toml b/runtime/acala/Cargo.toml index 4bab5b838..177019234 100644 --- a/runtime/acala/Cargo.toml +++ b/runtime/acala/Cargo.toml @@ -107,6 +107,7 @@ module-evm = { path = "../../modules/evm", default-features = false } module-evm-accounts = { path = "../../modules/evm-accounts", default-features = false } module-evm-bridge = { path = "../../modules/evm-bridge", default-features = false } module-evm-rpc-runtime-api = { path = "../../modules/evm/rpc/runtime_api", default-features = false } +module-fees = { path = "../../modules/fees", default-features = false } module-honzon = { path = "../../modules/honzon", default-features = false } module-loans = { path = "../../modules/loans", default-features = false } module-nft = { path = "../../modules/nft", default-features = false } @@ -232,6 +233,7 @@ std = [ "module-evm/std", "module-evm-accounts/std", "module-evm-bridge/std", + "module-fees/std", "module-honzon/std", "module-loans/std", "module-nft/std", diff --git a/runtime/acala/src/authority.rs b/runtime/acala/src/authority.rs index 0a57adadb..c202234d6 100644 --- a/runtime/acala/src/authority.rs +++ b/runtime/acala/src/authority.rs @@ -22,8 +22,8 @@ use crate::{ AccountId, AccountIdConversion, AuthoritysOriginId, BadOrigin, BlockNumber, DispatchResult, EnsureRoot, EnsureRootOrHalfFinancialCouncil, EnsureRootOrHalfGeneralCouncil, EnsureRootOrHalfHomaCouncil, EnsureRootOrOneThirdsTechnicalCommittee, EnsureRootOrThreeFourthsGeneralCouncil, - EnsureRootOrTwoThirdsTechnicalCommittee, HomaTreasuryPalletId, HonzonTreasuryPalletId, OneDay, Origin, - OriginCaller, SevenDays, TreasuryPalletId, TreasuryReservePalletId, HOURS, + EnsureRootOrTwoThirdsTechnicalCommittee, HonzonTreasuryPalletId, OneDay, Origin, OriginCaller, SevenDays, + TreasuryPalletId, TreasuryReservePalletId, HOURS, }; pub use frame_support::traits::{schedule::Priority, EnsureOrigin, OriginTrait}; use frame_system::ensure_root; @@ -87,9 +87,9 @@ impl orml_authority::AsOriginId for AuthoritysOriginId { .caller() .clone() } - AuthoritysOriginId::HomaTreasury => Origin::signed(HomaTreasuryPalletId::get().into_account_truncating()) - .caller() - .clone(), + AuthoritysOriginId::HomaTreasury => { + Origin::signed(runtime_common::HomaTreasuryPool::get()).caller().clone() + } AuthoritysOriginId::TreasuryReserve => { Origin::signed(TreasuryReservePalletId::get().into_account_truncating()) .caller() diff --git a/runtime/acala/src/benchmarking/mod.rs b/runtime/acala/src/benchmarking/mod.rs index ec81dd2ba..ed4cba5aa 100644 --- a/runtime/acala/src/benchmarking/mod.rs +++ b/runtime/acala/src/benchmarking/mod.rs @@ -59,6 +59,9 @@ pub mod evm { pub mod evm_accounts { include!("../../../mandala/src/benchmarking/evm_accounts.rs"); } +pub mod fees { + include!("../../../mandala/src/benchmarking/fees.rs"); +} pub mod homa { include!("../../../mandala/src/benchmarking/homa.rs"); } diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index efa9f4c3d..7ba1dc646 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -70,10 +70,9 @@ pub use frame_support::{ pallet_prelude::InvalidTransaction, parameter_types, traits::{ - ConstBool, ConstU128, ConstU16, ConstU32, Contains, ContainsLengthBound, Currency as PalletCurrency, + ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, Contains, ContainsLengthBound, Currency as PalletCurrency, EnsureOrigin, EqualPrivilegeOnly, Everything, Get, Imbalance, InstanceFilter, IsSubType, IsType, - KeyOwnerProofSystem, LockIdentifier, Nothing, OnRuntimeUpgrade, OnUnbalanced, Randomness, SortedMembers, - U128CurrencyToVote, + KeyOwnerProofSystem, LockIdentifier, Nothing, OnRuntimeUpgrade, Randomness, SortedMembers, U128CurrencyToVote, }, weights::{constants::RocksDbWeight, IdentityFee, Weight}, PalletId, RuntimeDebug, StorageValue, @@ -155,7 +154,6 @@ parameter_types! { pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub const HomaPalletId: PalletId = PalletId(*b"aca/homa"); pub const HonzonTreasuryPalletId: PalletId = PalletId(*b"aca/hztr"); - pub const HomaTreasuryPalletId: PalletId = PalletId(*b"aca/hmtr"); pub const IncentivesPalletId: PalletId = PalletId(*b"aca/inct"); pub const CollatorPotId: PalletId = PalletId(*b"aca/cpot"); // Treasury reserve @@ -176,13 +174,21 @@ pub fn get_all_module_accounts() -> Vec { CollatorPotId::get().into_account_truncating(), DEXPalletId::get().into_account_truncating(), HomaPalletId::get().into_account_truncating(), - HomaTreasuryPalletId::get().into_account_truncating(), HonzonTreasuryPalletId::get().into_account_truncating(), IncentivesPalletId::get().into_account_truncating(), TreasuryPalletId::get().into_account_truncating(), TreasuryReservePalletId::get().into_account_truncating(), UnreleasedNativeVaultAccountId::get(), StableAssetPalletId::get().into_account_truncating(), + // treasury pools and incentive pools + runtime_common::NetworkTreasuryPool::get(), + runtime_common::HonzonTreasuryPool::get(), + runtime_common::HomaTreasuryPool::get(), + runtime_common::HonzonInsuranceRewardPool::get(), + runtime_common::HonzonLiquitationRewardPool::get(), + runtime_common::StakingRewardPool::get(), + runtime_common::CollatorsRewardPool::get(), + runtime_common::EcosystemRewardPool::get(), ] } @@ -1055,6 +1061,7 @@ impl module_cdp_engine::Config for Runtime { type Currency = Currencies; type DEX = Dex; type Swap = AcalaSwap; + type OnFeeDeposit = Fees; type WeightInfo = weights::module_cdp_engine::WeightInfo; } @@ -1160,27 +1167,13 @@ parameter_types! { pub DefaultFeeTokens: Vec = vec![AUSD, LCDOT, DOT, LDOT]; } -type NegativeImbalance = >::NegativeImbalance; -pub struct DealWithFees; -impl OnUnbalanced for DealWithFees { - fn on_unbalanceds(mut fees_then_tips: impl Iterator) { - if let Some(mut fees) = fees_then_tips.next() { - if let Some(tips) = fees_then_tips.next() { - tips.merge_into(&mut fees); - } - // for fees and tips, 100% to treasury - Treasury::on_unbalanced(fees); - } - } -} - impl module_transaction_payment::Config for Runtime { type Event = Event; type Call = Call; type NativeCurrencyId = GetNativeCurrencyId; type Currency = Balances; type MultiCurrency = Currencies; - type OnTransactionPayment = DealWithFees; + type OnTransactionPayment = module_fees::DistributeTxFees; type AlternativeFeeSwapDeposit = NativeTokenExistentialDeposit; type OperationalFeeMultiplier = OperationalFeeMultiplier; type TipPerWeightStep = TipPerWeightStep; @@ -1447,7 +1440,6 @@ impl cumulus_pallet_aura_ext::Config for Runtime {} parameter_types! { pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10); - pub HomaTreasuryAccount: AccountId = HomaTreasuryPalletId::get().into_account_truncating(); pub ActiveSubAccountsIndexList: Vec = vec![ 0, // 15sr8Dvq3AT3Z2Z1y8FnQ4VipekAHhmQnrkgzegUr1tNgbcn ]; @@ -1462,7 +1454,6 @@ impl module_homa::Config for Runtime { type StakingCurrencyId = GetStakingCurrencyId; type LiquidCurrencyId = GetLiquidCurrencyId; type PalletId = HomaPalletId; - type TreasuryAccount = HomaTreasuryAccount; type DefaultExchangeRate = DefaultExchangeRate; type ActiveSubAccountsIndexList = ActiveSubAccountsIndexList; type BondingDuration = ConstU32<28>; @@ -1470,9 +1461,26 @@ impl module_homa::Config for Runtime { type RedeemThreshold = RedeemThreshold; type RelayChainBlockNumber = RelaychainBlockNumberProvider; type XcmInterface = XcmInterface; + type OnFeeDeposit = Fees; type WeightInfo = weights::module_homa::WeightInfo; } +parameter_types! { + pub const AllocationPeriod: BlockNumber = 7 * DAYS; +} + +impl module_fees::Config for Runtime { + type Event = Event; + type WeightInfo = weights::module_fees::WeightInfo; + type UpdateOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type Currency = Balances; + type Currencies = Currencies; + type NativeCurrencyId = GetNativeCurrencyId; + type AllocationPeriod = AllocationPeriod; + type DEX = Dex; + type DexSwapJointList = AlternativeSwapPathJointList; +} + pub fn create_x2_parachain_multilocation(index: u16) -> MultiLocation { MultiLocation::new( 1, @@ -1623,6 +1631,7 @@ construct_runtime!( Currencies: module_currencies = 12, Vesting: orml_vesting = 13, TransactionPayment: module_transaction_payment = 14, + Fees: module_fees = 15, // Treasury Treasury: pallet_treasury = 20, @@ -1745,8 +1754,115 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. -pub type Executive = - frame_executive::Executive, Runtime, AllPalletsWithSystem, ()>; +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + FeesMigration, +>; + +use primitives::IncomeSource; +pub struct FeesMigration; + +impl OnRuntimeUpgrade for FeesMigration { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let incomes = vec![ + ( + IncomeSource::TxFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::XcmFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::DexSwapFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::HonzonStabilityFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HonzonTreasuryPool::get(), 30), + ], + ), + ( + IncomeSource::HonzonLiquidationFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 30), + (runtime_common::HonzonTreasuryPool::get(), 70), + ], + ), + ( + IncomeSource::HomaStakingRewardFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HomaTreasuryPool::get(), 30), + ], + ), + ]; + let treasuries = vec![ + ( + runtime_common::NetworkTreasuryPool::get(), + 1000 * dollar(ACA), + vec![ + (runtime_common::StakingRewardPool::get(), 70), + (runtime_common::CollatorsRewardPool::get(), 10), + (runtime_common::EcosystemRewardPool::get(), 10), + (AcalaTreasuryAccount::get(), 10), + ], + ), + ( + runtime_common::HonzonTreasuryPool::get(), + 1000 * dollar(ACA), + vec![ + (runtime_common::HonzonInsuranceRewardPool::get(), 30), + (runtime_common::HonzonLiquitationRewardPool::get(), 70), + ], + ), + ]; + incomes.iter().for_each(|(income, pools)| { + let pool_rates = module_fees::build_pool_percents::(pools.clone()); + let _ = >::do_set_treasury_rate(*income, pool_rates); + }); + treasuries.iter().for_each(|(treasury, threshold, pools)| { + let pool_rates = module_fees::build_pool_percents::(pools.clone()); + let _ = >::do_set_incentive_rate(treasury.clone(), *threshold, pool_rates); + }); + + ::BlockWeights::get().max_block + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + assert!(>::contains_key( + &IncomeSource::TxFee + )); + assert!(>::contains_key( + &IncomeSource::XcmFee + )); + assert!(>::contains_key( + &IncomeSource::HonzonStabilityFee + )); + assert!(>::contains_key( + &IncomeSource::HomaStakingRewardFee + )); + assert!(>::contains_key( + &runtime_common::NetworkTreasuryPool::get() + )); + assert!(>::contains_key( + &runtime_common::HonzonTreasuryPool::get() + )); + Ok(()) + } +} #[cfg(feature = "runtime-benchmarks")] #[macro_use] @@ -1762,6 +1878,7 @@ mod benches { [module_cdp_engine, benchmarking::cdp_engine] [module_emergency_shutdown, benchmarking::emergency_shutdown] [module_evm, benchmarking::evm] + [module_fees, benchmarking::fees] [module_homa, benchmarking::homa] [module_honzon, benchmarking::honzon] [module_cdp_treasury, benchmarking::cdp_treasury] diff --git a/runtime/acala/src/weights/mod.rs b/runtime/acala/src/weights/mod.rs index 6cb5a57d0..34f35ab45 100644 --- a/runtime/acala/src/weights/mod.rs +++ b/runtime/acala/src/weights/mod.rs @@ -30,6 +30,7 @@ pub mod module_dex_oracle; pub mod module_emergency_shutdown; pub mod module_evm; pub mod module_evm_accounts; +pub mod module_fees; pub mod module_homa; pub mod module_honzon; pub mod module_incentives; diff --git a/runtime/acala/src/weights/module_fees.rs b/runtime/acala/src/weights/module_fees.rs new file mode 100644 index 000000000..44d9cda67 --- /dev/null +++ b/runtime/acala/src/weights/module_fees.rs @@ -0,0 +1,72 @@ +// 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 . + +//! Autogenerated weights for module_fees +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-06-20, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acala-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/acala +// benchmark +// pallet +// --chain=acala-dev +// --steps=50 +// --repeat=20 +// --pallet=module_fees +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=./templates/runtime-weight-template.hbs +// --output=./runtime/acala/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for module_fees. +pub struct WeightInfo(PhantomData); +impl module_fees::WeightInfo for WeightInfo { + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees IncomeToTreasuries (r:1 w:1) + fn set_income_fee() -> Weight { + (15_453_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees TreasuryToIncentives (r:1 w:1) + fn set_treasury_pool() -> Weight { + (17_620_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees TreasuryTokens (r:1 w:0) + // Storage: Fees TreasuryToIncentives (r:1 w:0) + fn force_transfer_to_incentive() -> Weight { + (15_327_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } +} diff --git a/runtime/acala/src/xcm_config.rs b/runtime/acala/src/xcm_config.rs index 59e121d79..9fa94e95a 100644 --- a/runtime/acala/src/xcm_config.rs +++ b/runtime/acala/src/xcm_config.rs @@ -18,8 +18,8 @@ use super::{ constants::fee::*, AcalaTreasuryAccount, AccountId, AssetIdMapping, AssetIdMaps, Balance, Call, Convert, - Currencies, CurrencyId, Event, ExistentialDeposits, GetNativeCurrencyId, NativeTokenExistentialDeposit, Origin, - ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, UnknownTokens, XcmpQueue, ACA, AUSD, + Currencies, CurrencyId, Event, ExistentialDeposits, Fees, GetNativeCurrencyId, NativeTokenExistentialDeposit, + Origin, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, UnknownTokens, XcmpQueue, ACA, AUSD, }; use codec::{Decode, Encode}; pub use cumulus_primitives_core::ParaId; @@ -30,19 +30,21 @@ pub use frame_support::{ }; use module_asset_registry::{BuyWeightRateOfErc20, BuyWeightRateOfForeignAsset}; use module_transaction_payment::BuyWeightRateOfTransactionFeePool; -use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key, MultiCurrency}; +use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use primitives::evm::is_system_contract; -use runtime_common::{native_currency_location, AcalaDropAssets, EnsureRootOrHalfGeneralCouncil, FixedRateOfAsset}; +use runtime_common::{ + native_currency_location, AcalaDropAssets, EnsureRootOrHalfGeneralCouncil, FixedRateOfAsset, XcmFeeToTreasury, +}; use xcm::latest::prelude::*; pub use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeRevenue, TakeWeightCredit, + TakeWeightCredit, }; use xcm_executor::XcmExecutor; @@ -95,24 +97,6 @@ pub type Barrier = ( AllowSubscriptionsFrom, ); -pub struct ToTreasury; -impl TakeRevenue for ToTreasury { - fn take_revenue(revenue: MultiAsset) { - if let MultiAsset { - id: Concrete(location), - fun: Fungible(amount), - } = revenue - { - if let Some(currency_id) = CurrencyIdConvert::convert(location) { - // Ensure AcalaTreasuryAccount have ed requirement for native asset, but don't need - // ed requirement for cross-chain asset because it's one of whitelist accounts. - // Ignore the result. - let _ = Currencies::deposit(currency_id, &AcalaTreasuryAccount::get(), amount); - } - } - } -} - parameter_types! { // One XCM operation is 200_000_000 weight, cross-chain transfer ~= 2x of transfer. pub const UnitWeightCost: Weight = 200_000_000; @@ -136,13 +120,15 @@ parameter_types! { pub BaseRate: u128 = aca_per_second(); } +type XcmToTreasury = XcmFeeToTreasury; + pub type Trader = ( - FixedRateOfAsset>, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfAsset>, - FixedRateOfAsset>, + FixedRateOfAsset>, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfAsset>, + FixedRateOfAsset>, ); pub struct XcmConfig; @@ -162,7 +148,7 @@ impl xcm_executor::Config for XcmConfig { type ResponseHandler = PolkadotXcm; type AssetTrap = AcalaDropAssets< PolkadotXcm, - ToTreasury, + XcmToTreasury, CurrencyIdConvert, GetNativeCurrencyId, NativeTokenExistentialDeposit, diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index f63ef9e55..ee1d5deea 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -54,6 +54,7 @@ module-prices = { path = "../../modules/prices", default-features = false } module-transaction-payment = { path = "../../modules/transaction-payment", default-features = false } module-nft = { path = "../../modules/nft", default-features = false } module-dex = { path = "../../modules/dex", default-features = false } +module-fees = { path = "../../modules/fees", default-features = false } module-evm-accounts = { path = "../../modules/evm-accounts", default-features = false } module-homa = {path = "../../modules/homa", default-features = false } module-asset-registry = { path = "../../modules/asset-registry", default-features = false, optional = true } @@ -123,6 +124,7 @@ std = [ "module-evm-accounts/std", "module-asset-registry/std", "module-evm-bridge/std", + "module-fees/std", "module-loans/std", "module-cdp-engine/std", "module-honzon/std", diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 655232c13..9bb78cebd 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -29,7 +29,7 @@ use frame_support::{ constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_PER_MILLIS}, DispatchClass, Weight, }, - RuntimeDebug, + PalletId, RuntimeDebug, }; use frame_system::{limits, EnsureRoot}; use module_evm::GenesisAccount; @@ -37,7 +37,11 @@ use orml_traits::GetByKey; use primitives::{evm::is_system_contract, Balance, CurrencyId, Nonce}; use scale_info::TypeInfo; use sp_core::{Bytes, H160}; -use sp_runtime::{traits::Convert, transaction_validity::TransactionPriority, Perbill}; +use sp_runtime::{ + traits::{AccountIdConversion, Convert}, + transaction_validity::TransactionPriority, + Perbill, +}; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*}; use static_assertions::const_assert; @@ -51,7 +55,7 @@ pub use primitives::{ currency::{TokenInfo, ACA, AUSD, BNC, DOT, KAR, KBTC, KINT, KSM, KUSD, LCDOT, LDOT, LKSM, PHA, RENBTC, VSKSM}, AccountId, }; -pub use xcm_impl::{native_currency_location, AcalaDropAssets, FixedRateOfAsset}; +pub use xcm_impl::{native_currency_location, AcalaDropAssets, FixedRateOfAsset, XcmFeeToTreasury}; #[cfg(feature = "std")] use sp_core::bytes::from_hex; @@ -394,6 +398,21 @@ pub fn evm_genesis(evm_accounts: Vec) -> BTreeMap; + type OnFeeDeposit = Fees; type WeightInfo = (); } @@ -678,6 +679,18 @@ impl HomaSubAccountXcm for MockHomaSubAccountXcm { } } +impl module_fees::Config for Test { + type Event = Event; + type UpdateOrigin = EnsureRoot; + type Currency = Balances; + type Currencies = Currencies; + type DEX = (); + type WeightInfo = (); + type NativeCurrencyId = GetNativeCurrencyId; + type AllocationPeriod = ConstU32<10>; + type DexSwapJointList = AlternativeSwapPathJointList; +} + ord_parameter_types! { pub const HomaAdmin: AccountId = ALICE; } @@ -701,7 +714,6 @@ impl module_homa::Config for Test { type StakingCurrencyId = StakingCurrencyId; type LiquidCurrencyId = LiquidCurrencyId; type PalletId = HomaPalletId; - type TreasuryAccount = HomaTreasuryAccount; type DefaultExchangeRate = DefaultExchangeRate; type ActiveSubAccountsIndexList = ActiveSubAccountsIndexList; type BondingDuration = BondingDuration; @@ -709,6 +721,7 @@ impl module_homa::Config for Test { type RedeemThreshold = RedeemThreshold; type RelayChainBlockNumber = MockRelayBlockNumberProvider; type XcmInterface = MockHomaSubAccountXcm; + type OnFeeDeposit = Fees; type WeightInfo = (); } @@ -821,6 +834,7 @@ frame_support::construct_runtime!( Incentives: module_incentives, Rewards: orml_rewards, StableAsset: nutsfinance_stable_asset, + Fees: module_fees, } ); diff --git a/runtime/common/src/xcm_impl.rs b/runtime/common/src/xcm_impl.rs index 44f03b047..b9a819a09 100644 --- a/runtime/common/src/xcm_impl.rs +++ b/runtime/common/src/xcm_impl.rs @@ -23,9 +23,9 @@ use frame_support::{ traits::Get, weights::{constants::WEIGHT_PER_SECOND, Weight}, }; -use module_support::BuyWeightRate; +use module_support::{BuyWeightRate, OnFeeDeposit}; use orml_traits::GetByKey; -use primitives::{Balance, CurrencyId}; +use primitives::{AccountId, Balance, CurrencyId, IncomeSource}; use sp_runtime::{traits::Convert, FixedPointNumber, FixedU128}; use sp_std::{marker::PhantomData, prelude::*}; use xcm::latest::prelude::*; @@ -106,6 +106,28 @@ where } } +pub struct XcmFeeToTreasury(PhantomData<(C, F)>); +impl TakeRevenue for XcmFeeToTreasury +where + C: Convert>, + F: OnFeeDeposit, +{ + fn take_revenue(revenue: MultiAsset) { + if let MultiAsset { + id: Concrete(location), + fun: Fungible(amount), + } = revenue + { + if let Some(currency_id) = C::convert(location) { + // Ensure given treasury account have ed requirement for native asset, but don't need + // ed requirement for cross-chain asset because it's one of whitelist accounts. + // Ignore the result. + let _ = F::on_fee_deposit(IncomeSource::XcmFee, currency_id, amount); + } + } + } +} + /// Simple fee calculator that requires payment in a single fungible at a fixed rate. /// /// - The `FixedRate` constant should be the concrete fungible ID and the amount of it diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index f44dfddf5..ba1d27992 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -115,6 +115,7 @@ module-xcm-interface = {path = "../../modules/xcm-interface" } module-homa = {path = "../../modules/homa" } module-session-manager = { path = "../../modules/session-manager" } module-relaychain = {path = "../../modules/relaychain" } +module-fees = {path = "../../modules/fees" } primitives = { package = "acala-primitives", path = "../../primitives" } runtime-common = { path = "../common" } diff --git a/runtime/integration-tests/src/authority.rs b/runtime/integration-tests/src/authority.rs index b82147728..351c98e45 100644 --- a/runtime/integration-tests/src/authority.rs +++ b/runtime/integration-tests/src/authority.rs @@ -115,7 +115,7 @@ fn test_authority_module() { run_to_block(one_day_later); assert_eq!( - Currencies::free_balance(USD_CURRENCY, &TreasuryPalletId::get().into_account_truncating()), + Currencies::free_balance(USD_CURRENCY, &TreasuryAccount::get()), 500 * dollar(USD_CURRENCY) ); assert_eq!( diff --git a/runtime/integration-tests/src/honzon.rs b/runtime/integration-tests/src/honzon.rs index 581b7340c..8a6a1e902 100644 --- a/runtime/integration-tests/src/honzon.rs +++ b/runtime/integration-tests/src/honzon.rs @@ -17,6 +17,9 @@ // along with this program. If not, see . use crate::setup::*; +use module_fees::PoolPercent; +use primitives::IncomeSource; +use sp_runtime::traits::One; fn setup_default_collateral(currency_id: CurrencyId) { assert_ok!(CdpEngine::set_collateral_params( @@ -30,6 +33,17 @@ fn setup_default_collateral(currency_id: CurrencyId) { )); } +fn setup_fees_distribution() { + assert_ok!(Fees::set_income_fee( + Origin::root(), + IncomeSource::HonzonStabilityFee, + vec![PoolPercent { + pool: CdpTreasury::account_id(), + rate: Rate::one(), + }], + )); +} + #[test] fn emergency_shutdown_and_cdp_treasury() { ExtBuilder::default() @@ -457,7 +471,7 @@ fn test_cdp_engine_module() { }); } -// Honzon's surplus can be transfered and DebitExchangeRate updates accordingly +// Honzon's surplus can be transferred and DebitExchangeRate updates accordingly #[test] fn cdp_treasury_handles_honzon_surplus_correctly() { ExtBuilder::default() @@ -476,6 +490,7 @@ fn cdp_treasury_handles_honzon_surplus_correctly() { ]) .build() .execute_with(|| { + setup_fees_distribution(); System::set_block_number(1); set_oracle_price(vec![(RELAY_CHAIN_CURRENCY, Price::saturating_from_rational(100, 1))]); assert_ok!(CdpEngine::set_collateral_params( diff --git a/runtime/integration-tests/src/relaychain/erc20.rs b/runtime/integration-tests/src/relaychain/erc20.rs index cbc773d2c..921e4e815 100644 --- a/runtime/integration-tests/src/relaychain/erc20.rs +++ b/runtime/integration-tests/src/relaychain/erc20.rs @@ -22,11 +22,12 @@ use crate::relaychain::kusama_test_net::*; use crate::setup::*; use frame_support::assert_ok; -use karura_runtime::{AssetRegistry, Erc20HoldingAccount, KaruraTreasuryAccount}; +use karura_runtime::{AssetRegistry, Erc20HoldingAccount}; use module_evm_accounts::EvmAddressMapping; use module_support::EVM as EVMTrait; use orml_traits::MultiCurrency; use primitives::evm::EvmAddress; +use runtime_common::NetworkTreasuryPool; use sp_core::{H256, U256}; use std::str::FromStr; use xcm_emulator::TestExt; @@ -140,7 +141,7 @@ fn erc20_transfer_between_sibling() { // charge storage. assert_ok!(Currencies::deposit( NATIVE_CURRENCY, - &KaruraTreasuryAccount::get(), + &NetworkTreasuryPool::get(), initial_native_amount )); @@ -194,7 +195,7 @@ fn erc20_transfer_between_sibling() { // initial_native_amount + ed assert_eq!( 1_100_000_000_000, - Currencies::free_balance(NATIVE_CURRENCY, &KaruraTreasuryAccount::get()) + Currencies::free_balance(NATIVE_CURRENCY, &NetworkTreasuryPool::get()) ); System::reset_events(); @@ -247,7 +248,7 @@ fn erc20_transfer_between_sibling() { ); assert_eq!( 9_324_000_000, - Currencies::free_balance(CurrencyId::Erc20(erc20_address_0()), &KaruraTreasuryAccount::get()) + Currencies::free_balance(CurrencyId::Erc20(erc20_address_0()), &NetworkTreasuryPool::get()) ); assert_eq!( 0, @@ -266,7 +267,7 @@ fn erc20_transfer_between_sibling() { // deposit reserve and unreserve storage fee, so the native token not changed. assert_eq!( 1_100_000_000_000, - Currencies::free_balance(NATIVE_CURRENCY, &KaruraTreasuryAccount::get()) + Currencies::free_balance(NATIVE_CURRENCY, &NetworkTreasuryPool::get()) ); // withdraw operation transfer from sibling parachain account to erc20 holding account @@ -284,7 +285,7 @@ fn erc20_transfer_between_sibling() { // TakeRevenue deposit from erc20 holding account to treasury account System::assert_has_event(Event::Currencies(module_currencies::Event::Deposited { currency_id: CurrencyId::Erc20(erc20_address_0()), - who: KaruraTreasuryAccount::get(), + who: NetworkTreasuryPool::get(), amount: 9_324_000_000, })); }); diff --git a/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs b/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs index f2454d0dd..ce1ac31da 100644 --- a/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs +++ b/runtime/integration-tests/src/relaychain/kusama_cross_chain_transfer.rs @@ -22,15 +22,17 @@ use crate::relaychain::kusama_test_net::*; use crate::setup::*; use frame_support::assert_ok; -use sp_runtime::traits::AccountIdConversion; -use xcm_builder::ParentIsPreset; - -use karura_runtime::parachains::bifrost::{BNC_KEY, ID as BIFROST_ID}; -use karura_runtime::{AssetRegistry, KaruraTreasuryAccount}; +use karura_runtime::{ + parachains::bifrost::{BNC_KEY, ID as BIFROST_ID}, + AssetRegistry, +}; use module_relaychain::RelayChainCallBuilder; use module_support::CallBuilder; use orml_traits::MultiCurrency; use primitives::currency::{AssetMetadata, BNC}; +use runtime_common::NetworkTreasuryPool; +use sp_runtime::traits::AccountIdConversion; +use xcm_builder::ParentIsPreset; use xcm_emulator::TestExt; use xcm_executor::traits::Convert; @@ -236,8 +238,8 @@ fn transfer_from_relay_chain_deposit_to_treasury_if_below_ed() { Karura::execute_with(|| { assert_eq!(Tokens::free_balance(KSM, &AccountId::from(BOB)), 0); assert_eq!( - Tokens::free_balance(KSM, &karura_runtime::KaruraTreasuryAccount::get()), - 1_000_186_480_111 + Tokens::free_balance(KSM, &NetworkTreasuryPool::get()), + 1_000_186_480_000 ); }); } @@ -689,7 +691,7 @@ fn trap_assets_larger_than_ed_works() { assert_ok!(Tokens::deposit(KSM, &parent_account, 100 * dollar(KSM))); let _ = pallet_balances::Pallet::::deposit_creating(&parent_account, 100 * dollar(KAR)); - kar_treasury_amount = Currencies::free_balance(KAR, &KaruraTreasuryAccount::get()); + kar_treasury_amount = Currencies::free_balance(KAR, &NetworkTreasuryPool::get()); }); let assets: MultiAsset = (Parent, ksm_asset_amount).into(); @@ -715,11 +717,11 @@ fn trap_assets_larger_than_ed_works() { assert_eq!( trader_weight_to_treasury + dollar(KSM), - Currencies::free_balance(KSM, &KaruraTreasuryAccount::get()) + Currencies::free_balance(KSM, &NetworkTreasuryPool::get()) ); assert_eq!( kar_treasury_amount, - Currencies::free_balance(KAR, &KaruraTreasuryAccount::get()) + Currencies::free_balance(KAR, &NetworkTreasuryPool::get()) ); }); } @@ -740,7 +742,7 @@ fn trap_assets_lower_than_ed_works() { Karura::execute_with(|| { assert_ok!(Tokens::deposit(KSM, &parent_account, dollar(KSM))); let _ = pallet_balances::Pallet::::deposit_creating(&parent_account, dollar(KAR)); - kar_treasury_amount = Currencies::free_balance(KAR, &KaruraTreasuryAccount::get()); + kar_treasury_amount = Currencies::free_balance(KAR, &NetworkTreasuryPool::get()); }); let assets: MultiAsset = (Parent, ksm_asset_amount).into(); @@ -771,11 +773,11 @@ fn trap_assets_lower_than_ed_works() { assert_eq!( ksm_asset_amount + dollar(KSM), - Currencies::free_balance(KSM, &KaruraTreasuryAccount::get()) + Currencies::free_balance(KSM, &NetworkTreasuryPool::get()) ); assert_eq!( kar_asset_amount, - Currencies::free_balance(KAR, &KaruraTreasuryAccount::get()) - kar_treasury_amount + Currencies::free_balance(KAR, &NetworkTreasuryPool::get()) - kar_treasury_amount ); }); } @@ -790,7 +792,7 @@ fn sibling_trap_assets_works() { Karura::execute_with(|| { assert_ok!(Tokens::deposit(BNC, &sibling_reserve_account(), dollar(BNC))); let _ = pallet_balances::Pallet::::deposit_creating(&sibling_reserve_account(), dollar(KAR)); - kar_treasury_amount = Currencies::free_balance(KAR, &KaruraTreasuryAccount::get()); + kar_treasury_amount = Currencies::free_balance(KAR, &NetworkTreasuryPool::get()); }); Sibling::execute_with(|| { @@ -824,11 +826,11 @@ fn sibling_trap_assets_works() { None ); assert_eq!( - Currencies::free_balance(KAR, &KaruraTreasuryAccount::get()) - kar_treasury_amount, + Currencies::free_balance(KAR, &NetworkTreasuryPool::get()) - kar_treasury_amount, kar_asset_amount ); assert_eq!( - Currencies::free_balance(BNC, &KaruraTreasuryAccount::get()), + Currencies::free_balance(BNC, &NetworkTreasuryPool::get()), bnc_asset_amount ); }); diff --git a/runtime/integration-tests/src/relaychain/kusama_test_net.rs b/runtime/integration-tests/src/relaychain/kusama_test_net.rs index c28a3f5c3..ff41ee1bf 100644 --- a/runtime/integration-tests/src/relaychain/kusama_test_net.rs +++ b/runtime/integration-tests/src/relaychain/kusama_test_net.rs @@ -26,6 +26,7 @@ use polkadot_primitives::v2::{BlockNumber, MAX_CODE_SIZE, MAX_POV_SIZE}; use polkadot_runtime_parachains::configuration::HostConfiguration; use sp_runtime::traits::AccountIdConversion; +use runtime_common::NetworkTreasuryPool; use xcm_emulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; decl_test_relay_chain! { @@ -166,6 +167,7 @@ pub fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { .balances(vec![ (AccountId::from(ALICE), KSM, 10 * dollar(KSM)), (karura_runtime::KaruraTreasuryAccount::get(), KSM, dollar(KSM)), + (NetworkTreasuryPool::get(), KSM, dollar(KSM)), ]) .parachain_id(parachain_id) .build() diff --git a/runtime/integration-tests/src/setup.rs b/runtime/integration-tests/src/setup.rs index 27d6d0af8..2419e6ba0 100644 --- a/runtime/integration-tests/src/setup.rs +++ b/runtime/integration-tests/src/setup.rs @@ -21,7 +21,7 @@ use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use frame_support::traits::{GenesisBuild, OnFinalize, OnIdle, OnInitialize}; pub use frame_support::{assert_noop, assert_ok, traits::Currency}; pub use frame_system::RawOrigin; -use runtime_common::evm_genesis; +use runtime_common::{evm_genesis, CollatorsRewardPool, NetworkTreasuryPool}; pub use module_support::{ mocks::MockAddressMapping, AddressMapping, CDPTreasury, DEXManager, Price, Rate, Ratio, RiskManager, @@ -50,14 +50,14 @@ mod mandala_imports { create_x2_parachain_multilocation, get_all_module_accounts, AcalaOracle, AccountId, AssetRegistry, AuctionManager, Authority, AuthoritysOriginId, Authorship, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CollatorSelection, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, - DataDepositPerByte, DealWithFees, DefaultExchangeRate, Dex, EmergencyShutdown, EnabledTradingPairs, Event, - EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, GetNativeCurrencyId, Homa, Honzon, IdleScheduler, - Loans, MaxTipsOfPriority, MinRewardDistributeAmount, MinimumDebitValue, MultiLocation, - NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, PalletCurrency, - ParachainInfo, ParachainSystem, Proxy, ProxyType, Ratio, Runtime, Scheduler, Session, SessionKeys, - SessionManager, SevenDays, StableAsset, StableAssetPalletId, System, Timestamp, TipPerWeightStep, TokenSymbol, - Tokens, TransactionPayment, TransactionPaymentPalletId, TreasuryAccount, TreasuryPalletId, UncheckedExtrinsic, - Utility, Vesting, XcmInterface, EVM, NFT, + DataDepositPerByte, DefaultExchangeRate, Dex, EmergencyShutdown, EnabledTradingPairs, Event, EvmAccounts, + ExistentialDeposits, Fees, FinancialCouncil, Get, GetNativeCurrencyId, Homa, Honzon, IdleScheduler, Loans, + MaxTipsOfPriority, MinRewardDistributeAmount, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, + NetworkId, NftPalletId, OneDay, Origin, OriginCaller, PalletCurrency, ParachainInfo, ParachainSystem, Proxy, + ProxyType, Ratio, Runtime, Scheduler, Session, SessionKeys, SessionManager, SevenDays, StableAsset, + StableAssetPalletId, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TransactionPayment, + TransactionPaymentPalletId, TreasuryAccount, TreasuryPalletId, UncheckedExtrinsic, Utility, Vesting, + XcmInterface, EVM, NFT, }; use module_transaction_payment::BuyWeightRateOfTransactionFeePool; pub use runtime_common::{cent, dollar, millicent, FixedRateOfAsset, ACA, AUSD, DOT, KSM, LDOT, LKSM}; @@ -86,19 +86,19 @@ pub use karura_imports::*; mod karura_imports { pub use frame_support::parameter_types; pub use karura_runtime::xcm_config::*; - use karura_runtime::AlternativeFeeSurplus; pub use karura_runtime::{ constants::parachains, create_x2_parachain_multilocation, get_all_module_accounts, AcalaOracle, AccountId, AssetRegistry, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, DataDepositPerByte, - DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, - GetNativeCurrencyId, Homa, Honzon, IdleScheduler, KaruraFoundationAccounts, Loans, MaxTipsOfPriority, + DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, Fees, FinancialCouncil, + Get, GetNativeCurrencyId, Homa, Honzon, IdleScheduler, KaruraFoundationAccounts, Loans, MaxTipsOfPriority, MinimumDebitValue, MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, Ratio, Runtime, Scheduler, Session, SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TransactionPayment, TransactionPaymentPalletId, TreasuryPalletId, Utility, Vesting, XTokens, XcmInterface, EVM, NFT, }; + use karura_runtime::{AlternativeFeeSurplus, KaruraTreasuryAccount}; use module_transaction_payment::BuyWeightRateOfTransactionFeePool; pub use primitives::TradingPair; pub use runtime_common::{cent, dollar, millicent, FixedRateOfAsset, KAR, KSM, KUSD, LKSM}; @@ -113,7 +113,7 @@ mod karura_imports { TradingPair::from_currency_ids(USD_CURRENCY, LIQUID_CURRENCY).unwrap(), TradingPair::from_currency_ids(RELAY_CHAIN_CURRENCY, NATIVE_CURRENCY).unwrap(), ]; - pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub TreasuryAccount: AccountId = KaruraTreasuryAccount::get(); } pub const NATIVE_CURRENCY: CurrencyId = KAR; @@ -133,21 +133,24 @@ mod karura_imports { #[cfg(feature = "with-acala-runtime")] pub use acala_imports::*; +use primitives::IncomeSource; + #[cfg(feature = "with-acala-runtime")] mod acala_imports { pub use acala_runtime::xcm_config::*; - use acala_runtime::AlternativeFeeSurplus; pub use acala_runtime::{ create_x2_parachain_multilocation, get_all_module_accounts, AcalaFoundationAccounts, AcalaOracle, AccountId, AssetRegistry, AuctionManager, Authority, AuthoritysOriginId, Balance, Balances, BlockNumber, Call, CdpEngine, CdpTreasury, CreateClassDeposit, CreateTokenDeposit, Currencies, CurrencyId, DataDepositPerByte, - DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, FinancialCouncil, Get, - GetNativeCurrencyId, Homa, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, MinimumDebitValue, MultiLocation, - NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, ParachainAccount, - ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, Ratio, Runtime, Scheduler, Session, - SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, TransactionPayment, - TransactionPaymentPalletId, TreasuryPalletId, Utility, Vesting, XTokens, XcmInterface, EVM, LCDOT, NFT, + DefaultExchangeRate, Dex, EmergencyShutdown, Event, EvmAccounts, ExistentialDeposits, Fees, FinancialCouncil, + Get, GetNativeCurrencyId, Homa, Honzon, IdleScheduler, Loans, MaxTipsOfPriority, MinimumDebitValue, + MultiLocation, NativeTokenExistentialDeposit, NetworkId, NftPalletId, OneDay, Origin, OriginCaller, + ParachainAccount, ParachainInfo, ParachainSystem, PolkadotXcm, Proxy, ProxyType, Ratio, Runtime, Scheduler, + Session, SessionManager, SevenDays, System, Timestamp, TipPerWeightStep, TokenSymbol, Tokens, + TransactionPayment, TransactionPaymentPalletId, TreasuryPalletId, Utility, Vesting, XTokens, XcmInterface, EVM, + LCDOT, NFT, }; + use acala_runtime::{AcalaTreasuryAccount, AlternativeFeeSurplus}; pub use frame_support::parameter_types; use module_transaction_payment::BuyWeightRateOfTransactionFeePool; pub use primitives::TradingPair; @@ -165,7 +168,7 @@ mod acala_imports { TradingPair::from_currency_ids(RELAY_CHAIN_CURRENCY, NATIVE_CURRENCY).unwrap(), TradingPair::from_currency_ids(RELAY_CHAIN_CURRENCY, LCDOT).unwrap(), ]; - pub TreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); + pub TreasuryAccount: AccountId = AcalaTreasuryAccount::get(); } pub const NATIVE_CURRENCY: CurrencyId = ACA; @@ -365,6 +368,20 @@ impl ExtBuilder { ) .unwrap(); + module_fees::GenesisConfig:: { + incomes: vec![ + ( + IncomeSource::TxFee, + vec![(NetworkTreasuryPool::get(), 80), (CollatorsRewardPool::get(), 20)], + ), + // xcm buy weight revenue deposit to NetworkTreasuryPool account + (IncomeSource::XcmFee, vec![(NetworkTreasuryPool::get(), 100)]), + ], + treasuries: vec![], + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext diff --git a/runtime/integration-tests/src/treasury.rs b/runtime/integration-tests/src/treasury.rs index b531bfd37..5ca64ec31 100644 --- a/runtime/integration-tests/src/treasury.rs +++ b/runtime/integration-tests/src/treasury.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::setup::*; +use runtime_common::NetworkTreasuryPool; #[test] fn treasury_should_take_xcm_execution_revenue() { @@ -70,7 +71,7 @@ fn treasury_should_take_xcm_execution_revenue() { assert_eq!(Tokens::free_balance(RELAY_CHAIN_CURRENCY, &ALICE.into()), actual_amount); assert_eq!( - Tokens::free_balance(RELAY_CHAIN_CURRENCY, &TreasuryAccount::get()), + Tokens::free_balance(RELAY_CHAIN_CURRENCY, &NetworkTreasuryPool::get()), dot_amount - actual_amount ); }); @@ -235,7 +236,9 @@ mod mandala_only_tests { use super::*; type NegativeImbalance = >::NegativeImbalance; use frame_support::{pallet_prelude::Decode, traits::OnUnbalanced}; + use module_fees::DistributeTxFees; use pallet_authorship::EventHandler; + use runtime_common::{CollatorsRewardPool, NetworkTreasuryPool}; #[test] fn treasury_handles_collator_rewards_correctly() { @@ -250,7 +253,8 @@ mod mandala_only_tests { AccountId::from(ALICE) ))); - let pot_account_id = CollatorSelection::account_id(); + let pot_account_id = CollatorsRewardPool::get(); + let network_treasury = NetworkTreasuryPool::get(); // Currently pot has ExistentialDeposits assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &pot_account_id), @@ -266,40 +270,56 @@ mod mandala_only_tests { // Only 20% of the fee went into the pot let tip = NegativeImbalance::new((min_reward - 1) * 10); let fee = NegativeImbalance::new(0); - DealWithFees::on_unbalanceds(Some(fee).into_iter().chain(Some(tip))); + DistributeTxFees::::on_unbalanceds(Some(fee).into_iter().chain(Some(tip))); // The amount above existential is below the `MinRewardDistributeAmount`. assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &pot_account_id), 299_999_999_998 ); + assert_eq!( + Currencies::free_balance(NATIVE_CURRENCY, &network_treasury), + 899_999_999_992 + ); CollatorSelection::note_author(AccountId::from(BOB)); assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &pot_account_id), 299_999_999_998 ); + assert_eq!( + Currencies::free_balance(NATIVE_CURRENCY, &network_treasury), + 899_999_999_992 + ); assert_eq!(Currencies::free_balance(NATIVE_CURRENCY, &AccountId::from(BOB)), 0); // Put a little more money into the pot let tip = NegativeImbalance::new(10); let fee = NegativeImbalance::new(0); - DealWithFees::on_unbalanceds(Some(fee).into_iter().chain(Some(tip))); + DistributeTxFees::::on_unbalanceds(Some(fee).into_iter().chain(Some(tip))); // Now the above existential is above the `MinRewardDistributeAmount`. assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &pot_account_id), 300_000_000_000 ); + assert_eq!( + Currencies::free_balance(NATIVE_CURRENCY, &network_treasury), + 900_000_000_000 + ); - // Splits half of 300_000_000_000 to BOB + // Splits half of available pot to BOB: (pot - ED) / 2 = (30c - 10c) / 2 = 10c CollatorSelection::note_author(AccountId::from(BOB)); assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &pot_account_id), 200_000_000_000 ); + assert_eq!( + Currencies::free_balance(NATIVE_CURRENCY, &network_treasury), + 900_000_000_000 + ); assert_eq!( Currencies::free_balance(NATIVE_CURRENCY, &AccountId::from(BOB)), 100_000_000_000 diff --git a/runtime/integration-tests/src/vesting.rs b/runtime/integration-tests/src/vesting.rs index e9192f597..cd6531dac 100644 --- a/runtime/integration-tests/src/vesting.rs +++ b/runtime/integration-tests/src/vesting.rs @@ -23,7 +23,7 @@ use orml_vesting::VestingSchedule; fn test_vesting_use_relaychain_block_number() { ExtBuilder::default().build().execute_with(|| { #[cfg(feature = "with-mandala-runtime")] - let signer: AccountId = TreasuryPalletId::get().into_account_truncating(); + let signer: AccountId = TreasuryAccount::get(); #[cfg(feature = "with-karura-runtime")] let signer: AccountId = KaruraFoundationAccounts::get()[0].clone(); #[cfg(feature = "with-acala-runtime")] diff --git a/runtime/karura/Cargo.toml b/runtime/karura/Cargo.toml index 0ed1fd0c2..c650e4c0d 100644 --- a/runtime/karura/Cargo.toml +++ b/runtime/karura/Cargo.toml @@ -120,6 +120,7 @@ module-relaychain = { path = "../../modules/relaychain", default-features = fals module-idle-scheduler = { path = "../../modules/idle-scheduler", default-features = false } module-honzon-bridge = { path = "../../modules/honzon-bridge", default-features = false } module-aggregated-dex = { path = "../../modules/aggregated-dex", default-features = false } +module-fees = { path = "../../modules/fees", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } runtime-common = { path = "../common", default-features = false } @@ -246,6 +247,7 @@ std = [ "module-idle-scheduler/std", "module-honzon-bridge/std", "module-aggregated-dex/std", + "module-fees/std", "primitives/std", "runtime-common/std", diff --git a/runtime/karura/src/authority.rs b/runtime/karura/src/authority.rs index 0a57adadb..c202234d6 100644 --- a/runtime/karura/src/authority.rs +++ b/runtime/karura/src/authority.rs @@ -22,8 +22,8 @@ use crate::{ AccountId, AccountIdConversion, AuthoritysOriginId, BadOrigin, BlockNumber, DispatchResult, EnsureRoot, EnsureRootOrHalfFinancialCouncil, EnsureRootOrHalfGeneralCouncil, EnsureRootOrHalfHomaCouncil, EnsureRootOrOneThirdsTechnicalCommittee, EnsureRootOrThreeFourthsGeneralCouncil, - EnsureRootOrTwoThirdsTechnicalCommittee, HomaTreasuryPalletId, HonzonTreasuryPalletId, OneDay, Origin, - OriginCaller, SevenDays, TreasuryPalletId, TreasuryReservePalletId, HOURS, + EnsureRootOrTwoThirdsTechnicalCommittee, HonzonTreasuryPalletId, OneDay, Origin, OriginCaller, SevenDays, + TreasuryPalletId, TreasuryReservePalletId, HOURS, }; pub use frame_support::traits::{schedule::Priority, EnsureOrigin, OriginTrait}; use frame_system::ensure_root; @@ -87,9 +87,9 @@ impl orml_authority::AsOriginId for AuthoritysOriginId { .caller() .clone() } - AuthoritysOriginId::HomaTreasury => Origin::signed(HomaTreasuryPalletId::get().into_account_truncating()) - .caller() - .clone(), + AuthoritysOriginId::HomaTreasury => { + Origin::signed(runtime_common::HomaTreasuryPool::get()).caller().clone() + } AuthoritysOriginId::TreasuryReserve => { Origin::signed(TreasuryReservePalletId::get().into_account_truncating()) .caller() diff --git a/runtime/karura/src/benchmarking/mod.rs b/runtime/karura/src/benchmarking/mod.rs index 1a3252be2..6c9fc983e 100644 --- a/runtime/karura/src/benchmarking/mod.rs +++ b/runtime/karura/src/benchmarking/mod.rs @@ -59,6 +59,9 @@ pub mod evm { pub mod evm_accounts { include!("../../../mandala/src/benchmarking/evm_accounts.rs"); } +pub mod fees { + include!("../../../mandala/src/benchmarking/fees.rs"); +} pub mod homa { include!("../../../mandala/src/benchmarking/homa.rs"); } diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 783a4059b..d5f44c484 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -71,8 +71,7 @@ pub use frame_support::{ traits::{ ConstBool, ConstU128, ConstU16, ConstU32, Contains, ContainsLengthBound, Currency as PalletCurrency, EnsureOrigin, EqualPrivilegeOnly, Everything, Get, Imbalance, InstanceFilter, IsSubType, IsType, - KeyOwnerProofSystem, LockIdentifier, Nothing, OnRuntimeUpgrade, OnUnbalanced, Randomness, SortedMembers, - U128CurrencyToVote, + KeyOwnerProofSystem, LockIdentifier, Nothing, OnRuntimeUpgrade, Randomness, SortedMembers, U128CurrencyToVote, }, weights::{constants::RocksDbWeight, IdentityFee, Weight}, PalletId, RuntimeDebug, StorageValue, @@ -102,12 +101,11 @@ pub use runtime_common::{ EnsureRootOrHalfHomaCouncil, EnsureRootOrOneGeneralCouncil, EnsureRootOrOneThirdsTechnicalCommittee, EnsureRootOrThreeFourthsGeneralCouncil, EnsureRootOrTwoThirdsGeneralCouncil, EnsureRootOrTwoThirdsTechnicalCommittee, ExchangeRate, ExistentialDepositsTimesOneHundred, - FinancialCouncilInstance, FinancialCouncilMembershipInstance, FixedRateOfAsset, GasToWeight, - GeneralCouncilInstance, GeneralCouncilMembershipInstance, HomaCouncilInstance, HomaCouncilMembershipInstance, - MaxTipsOfPriority, OperationalFeeMultiplier, OperatorMembershipInstanceAcala, Price, ProxyType, Rate, Ratio, - RuntimeBlockLength, RuntimeBlockWeights, SystemContractsFilter, TechnicalCommitteeInstance, - TechnicalCommitteeMembershipInstance, TimeStampedPrice, TipPerWeightStep, BNC, KAR, KBTC, KINT, KSM, KUSD, LKSM, - PHA, RENBTC, VSKSM, + FinancialCouncilInstance, FinancialCouncilMembershipInstance, GasToWeight, GeneralCouncilInstance, + GeneralCouncilMembershipInstance, HomaCouncilInstance, HomaCouncilMembershipInstance, MaxTipsOfPriority, + OperationalFeeMultiplier, OperatorMembershipInstanceAcala, Price, ProxyType, Rate, Ratio, RuntimeBlockLength, + RuntimeBlockWeights, SystemContractsFilter, TechnicalCommitteeInstance, TechnicalCommitteeMembershipInstance, + TimeStampedPrice, TipPerWeightStep, BNC, KAR, KBTC, KINT, KSM, KUSD, LKSM, PHA, RENBTC, VSKSM, }; pub use xcm::latest::prelude::*; @@ -158,7 +156,6 @@ parameter_types! { pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub const HonzonTreasuryPalletId: PalletId = PalletId(*b"aca/hztr"); pub const HomaPalletId: PalletId = PalletId(*b"aca/homa"); - pub const HomaTreasuryPalletId: PalletId = PalletId(*b"aca/hmtr"); pub const IncentivesPalletId: PalletId = PalletId(*b"aca/inct"); pub const CollatorPotId: PalletId = PalletId(*b"aca/cpot"); pub const HonzonBridgePalletId: PalletId = PalletId(*b"aca/hzbg"); @@ -181,7 +178,6 @@ pub fn get_all_module_accounts() -> Vec { CollatorPotId::get().into_account_truncating(), DEXPalletId::get().into_account_truncating(), HomaPalletId::get().into_account_truncating(), - HomaTreasuryPalletId::get().into_account_truncating(), HonzonTreasuryPalletId::get().into_account_truncating(), IncentivesPalletId::get().into_account_truncating(), TreasuryPalletId::get().into_account_truncating(), @@ -189,6 +185,15 @@ pub fn get_all_module_accounts() -> Vec { UnreleasedNativeVaultAccountId::get(), StableAssetPalletId::get().into_account_truncating(), HonzonBridgePalletId::get().into_account_truncating(), + // treasury pools and incentive pools + runtime_common::NetworkTreasuryPool::get(), + runtime_common::HonzonTreasuryPool::get(), + runtime_common::HomaTreasuryPool::get(), + runtime_common::HonzonInsuranceRewardPool::get(), + runtime_common::HonzonLiquitationRewardPool::get(), + runtime_common::StakingRewardPool::get(), + runtime_common::CollatorsRewardPool::get(), + runtime_common::EcosystemRewardPool::get(), ] } @@ -1070,6 +1075,7 @@ impl module_cdp_engine::Config for Runtime { type Currency = Currencies; type DEX = Dex; type Swap = AcalaSwap; + type OnFeeDeposit = Fees; type WeightInfo = weights::module_cdp_engine::WeightInfo; } @@ -1174,27 +1180,13 @@ parameter_types! { pub const AlternativeFeeSurplus: Percent = Percent::from_percent(25); } -type NegativeImbalance = >::NegativeImbalance; -pub struct DealWithFees; -impl OnUnbalanced for DealWithFees { - fn on_unbalanceds(mut fees_then_tips: impl Iterator) { - if let Some(mut fees) = fees_then_tips.next() { - if let Some(tips) = fees_then_tips.next() { - tips.merge_into(&mut fees); - } - // for fees and tips, 100% to treasury - Treasury::on_unbalanced(fees); - } - } -} - impl module_transaction_payment::Config for Runtime { type Event = Event; type Call = Call; type NativeCurrencyId = GetNativeCurrencyId; type Currency = Balances; type MultiCurrency = Currencies; - type OnTransactionPayment = DealWithFees; + type OnTransactionPayment = module_fees::DistributeTxFees; type AlternativeFeeSwapDeposit = NativeTokenExistentialDeposit; type OperationalFeeMultiplier = OperationalFeeMultiplier; type TipPerWeightStep = TipPerWeightStep; @@ -1472,7 +1464,6 @@ impl cumulus_pallet_aura_ext::Config for Runtime {} parameter_types! { pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10); - pub HomaTreasuryAccount: AccountId = HomaTreasuryPalletId::get().into_account_truncating(); pub ActiveSubAccountsIndexList: Vec = vec![ 0, // HTAeD1dokCVs9MwnC1q9s2a7d2kQ52TAjrxE1y5mj5MFLLA 1, // FDVu3RdH5WsE2yTdXN3QMq6v1XVDK8GKjhq5oFjXe8wZYpL @@ -1489,7 +1480,6 @@ impl module_homa::Config for Runtime { type StakingCurrencyId = GetStakingCurrencyId; type LiquidCurrencyId = GetLiquidCurrencyId; type PalletId = HomaPalletId; - type TreasuryAccount = HomaTreasuryAccount; type DefaultExchangeRate = DefaultExchangeRate; type ActiveSubAccountsIndexList = ActiveSubAccountsIndexList; type BondingDuration = ConstU32<28>; @@ -1497,6 +1487,7 @@ impl module_homa::Config for Runtime { type RedeemThreshold = RedeemThreshold; type RelayChainBlockNumber = RelaychainBlockNumberProvider; type XcmInterface = XcmInterface; + type OnFeeDeposit = Fees; type WeightInfo = weights::module_homa::WeightInfo; } @@ -1564,6 +1555,22 @@ impl module_idle_scheduler::Config for Runtime { type DisableBlockThreshold = ConstU32<6>; } +parameter_types! { + pub const AllocationPeriod: BlockNumber = 3 * DAYS; +} + +impl module_fees::Config for Runtime { + type Event = Event; + type WeightInfo = weights::module_fees::WeightInfo; + type UpdateOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type Currency = Balances; + type Currencies = Currencies; + type NativeCurrencyId = GetNativeCurrencyId; + type AllocationPeriod = AllocationPeriod; + type DEX = Dex; + type DexSwapJointList = AlternativeSwapPathJointList; +} + parameter_types! { pub WormholeAUSDCurrencyId: CurrencyId = CurrencyId::Erc20(EvmAddress::from(hex_literal::hex!["0000000000000000000100000000000000000001"])); pub const StableCoinCurrencyId: CurrencyId = KUSD; @@ -1664,6 +1671,7 @@ construct_runtime!( Currencies: module_currencies = 12, Vesting: orml_vesting = 13, TransactionPayment: module_transaction_payment = 14, + Fees: module_fees = 15, // Treasury Treasury: pallet_treasury = 20, @@ -1787,8 +1795,109 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. -pub type Executive = - frame_executive::Executive, Runtime, AllPalletsWithSystem, ()>; +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + FeesMigration, +>; + +use primitives::IncomeSource; + +pub struct FeesMigration; + +impl OnRuntimeUpgrade for FeesMigration { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let incomes = vec![ + ( + IncomeSource::TxFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::XcmFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::DexSwapFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::HonzonStabilityFee, + vec![(runtime_common::HonzonTreasuryPool::get(), 100)], + ), + ( + IncomeSource::HonzonLiquidationFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 30), + (runtime_common::HonzonTreasuryPool::get(), 70), + ], + ), + ( + IncomeSource::HomaStakingRewardFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HomaTreasuryPool::get(), 30), + ], + ), + ]; + let treasuries = vec![ + ( + runtime_common::NetworkTreasuryPool::get(), + 1000 * dollar(KAR), + vec![ + (runtime_common::StakingRewardPool::get(), 70), + (runtime_common::CollatorsRewardPool::get(), 10), + (runtime_common::EcosystemRewardPool::get(), 10), + (KaruraTreasuryAccount::get(), 10), + ], + ), + ( + runtime_common::HonzonTreasuryPool::get(), + 10 * dollar(KAR), + vec![(CDPTreasuryPalletId::get().into_account_truncating(), 100)], + ), + ]; + incomes.iter().for_each(|(income, pools)| { + let pool_rates = module_fees::build_pool_percents::(pools.clone()); + let _ = >::do_set_treasury_rate(*income, pool_rates); + }); + treasuries.iter().for_each(|(treasury, threshold, pools)| { + let pool_rates = module_fees::build_pool_percents::(pools.clone()); + let _ = >::do_set_incentive_rate(treasury.clone(), *threshold, pool_rates); + }); + ::BlockWeights::get().max_block + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + assert!(>::contains_key( + &IncomeSource::TxFee + )); + assert!(>::contains_key( + &IncomeSource::XcmFee + )); + assert!(>::contains_key( + &IncomeSource::HonzonStabilityFee + )); + assert!(>::contains_key( + &IncomeSource::HomaStakingRewardFee + )); + assert!(>::contains_key( + &runtime_common::NetworkTreasuryPool::get() + )); + assert!(>::contains_key( + &runtime_common::HonzonTreasuryPool::get() + )); + Ok(()) + } +} #[cfg(feature = "runtime-benchmarks")] #[macro_use] @@ -1804,6 +1913,7 @@ mod benches { [module_cdp_engine, benchmarking::cdp_engine] [module_emergency_shutdown, benchmarking::emergency_shutdown] [module_evm, benchmarking::evm] + [module_fees, benchmarking::fees] [module_homa, benchmarking::homa] [module_honzon, benchmarking::honzon] [module_cdp_treasury, benchmarking::cdp_treasury] diff --git a/runtime/karura/src/weights/mod.rs b/runtime/karura/src/weights/mod.rs index 19cfb6ed6..121e79d91 100644 --- a/runtime/karura/src/weights/mod.rs +++ b/runtime/karura/src/weights/mod.rs @@ -30,6 +30,7 @@ pub mod module_dex_oracle; pub mod module_emergency_shutdown; pub mod module_evm; pub mod module_evm_accounts; +pub mod module_fees; pub mod module_homa; pub mod module_honzon; pub mod module_honzon_bridge; diff --git a/runtime/karura/src/weights/module_fees.rs b/runtime/karura/src/weights/module_fees.rs new file mode 100644 index 000000000..5a0405f92 --- /dev/null +++ b/runtime/karura/src/weights/module_fees.rs @@ -0,0 +1,72 @@ +// 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 . + +//! Autogenerated weights for module_fees +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-06-20, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("karura-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/acala +// benchmark +// pallet +// --chain=karura-dev +// --steps=50 +// --repeat=20 +// --pallet=module_fees +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=./templates/runtime-weight-template.hbs +// --output=./runtime/karura/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for module_fees. +pub struct WeightInfo(PhantomData); +impl module_fees::WeightInfo for WeightInfo { + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees IncomeToTreasuries (r:1 w:1) + fn set_income_fee() -> Weight { + (14_988_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees TreasuryToIncentives (r:1 w:1) + fn set_treasury_pool() -> Weight { + (15_911_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees TreasuryTokens (r:1 w:0) + // Storage: Fees TreasuryToIncentives (r:1 w:0) + fn force_transfer_to_incentive() -> Weight { + (8_493_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } +} diff --git a/runtime/karura/src/xcm_config.rs b/runtime/karura/src/xcm_config.rs index dfc0e1223..41bd97d7a 100644 --- a/runtime/karura/src/xcm_config.rs +++ b/runtime/karura/src/xcm_config.rs @@ -19,7 +19,7 @@ use super::{ constants::{fee::*, parachains}, AccountId, AssetIdMapping, AssetIdMaps, Balance, Call, Convert, Currencies, CurrencyId, Event, ExistentialDeposits, - FixedRateOfAsset, GetNativeCurrencyId, KaruraTreasuryAccount, NativeTokenExistentialDeposit, Origin, ParachainInfo, + Fees, GetNativeCurrencyId, KaruraTreasuryAccount, NativeTokenExistentialDeposit, Origin, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, UnknownTokens, XcmInterface, XcmpQueue, KAR, KUSD, LKSM, }; use codec::{Decode, Encode}; @@ -32,12 +32,14 @@ pub use frame_support::{ pub use module_asset_registry::{BuyWeightRateOfErc20, BuyWeightRateOfForeignAsset}; use module_support::HomaSubAccountXcm; use module_transaction_payment::BuyWeightRateOfTransactionFeePool; -use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key, MultiCurrency}; +use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use primitives::evm::is_system_contract; -use runtime_common::{native_currency_location, AcalaDropAssets, EnsureRootOrHalfGeneralCouncil}; +use runtime_common::{ + native_currency_location, AcalaDropAssets, EnsureRootOrHalfGeneralCouncil, FixedRateOfAsset, XcmFeeToTreasury, +}; use xcm::latest::prelude::*; pub use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, @@ -97,24 +99,6 @@ pub type Barrier = ( AllowSubscriptionsFrom, ); -pub struct ToTreasury; -impl TakeRevenue for ToTreasury { - fn take_revenue(revenue: MultiAsset) { - if let MultiAsset { - id: Concrete(location), - fun: Fungible(amount), - } = revenue - { - if let Some(currency_id) = CurrencyIdConvert::convert(location) { - // Ensure KaruraTreasuryAccount have ed requirement for native asset, but don't need - // ed requirement for cross-chain asset because it's one of whitelist accounts. - // Ignore the result. - let _ = Currencies::deposit(currency_id, &KaruraTreasuryAccount::get(), amount); - } - } - } -} - parameter_types! { // One XCM operation is 200_000_000 weight, cross-chain transfer ~= 2x of transfer. pub const UnitWeightCost: Weight = 200_000_000; @@ -187,19 +171,21 @@ parameter_types! { pub BaseRate: u128 = kar_per_second(); } +type XcmToTreasury = XcmFeeToTreasury; + pub type Trader = ( - FixedRateOfAsset>, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfAsset>, - FixedRateOfAsset>, + FixedRateOfAsset>, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfAsset>, + FixedRateOfAsset>, ); pub struct XcmConfig; @@ -219,7 +205,7 @@ impl xcm_executor::Config for XcmConfig { type ResponseHandler = PolkadotXcm; type AssetTrap = AcalaDropAssets< PolkadotXcm, - ToTreasury, + XcmToTreasury, CurrencyIdConvert, GetNativeCurrencyId, NativeTokenExistentialDeposit, diff --git a/runtime/mandala/Cargo.toml b/runtime/mandala/Cargo.toml index 5433126b0..4c81a1007 100644 --- a/runtime/mandala/Cargo.toml +++ b/runtime/mandala/Cargo.toml @@ -127,6 +127,7 @@ module-session-manager = { path = "../../modules/session-manager", default-featu module-relaychain = { path = "../../modules/relaychain", default-features = false, features = ["polkadot"]} module-idle-scheduler = { path = "../../modules/idle-scheduler", default-features = false } module-aggregated-dex = { path = "../../modules/aggregated-dex", default-features = false } +module-fees = { path = "../../modules/fees", default-features = false } primitives = { package = "acala-primitives", path = "../../primitives", default-features = false } runtime-common = { path = "../common", default-features = false } @@ -263,6 +264,7 @@ std = [ "module-relaychain/std", "module-idle-scheduler/std", "module-aggregated-dex/std", + "module-fees/std", "primitives/std", "runtime-common/std", diff --git a/runtime/mandala/src/authority.rs b/runtime/mandala/src/authority.rs index 0340e0495..a21fed0f2 100644 --- a/runtime/mandala/src/authority.rs +++ b/runtime/mandala/src/authority.rs @@ -22,8 +22,8 @@ use crate::{ AccountId, AccountIdConversion, AuthoritysOriginId, BadOrigin, BlockNumber, DispatchResult, EnsureRoot, EnsureRootOrHalfFinancialCouncil, EnsureRootOrHalfGeneralCouncil, EnsureRootOrHalfHomaCouncil, EnsureRootOrOneThirdsTechnicalCommittee, EnsureRootOrThreeFourthsGeneralCouncil, - EnsureRootOrTwoThirdsTechnicalCommittee, HomaTreasuryPalletId, HonzonTreasuryPalletId, OneDay, Origin, - OriginCaller, SevenDays, TreasuryPalletId, TreasuryReservePalletId, ZeroDay, HOURS, + EnsureRootOrTwoThirdsTechnicalCommittee, HonzonTreasuryPalletId, OneDay, Origin, OriginCaller, SevenDays, + TreasuryPalletId, TreasuryReservePalletId, ZeroDay, HOURS, }; pub use frame_support::traits::{schedule::Priority, EnsureOrigin, OriginTrait}; use frame_system::ensure_root; @@ -87,9 +87,9 @@ impl orml_authority::AsOriginId for AuthoritysOriginId { .caller() .clone() } - AuthoritysOriginId::HomaTreasury => Origin::signed(HomaTreasuryPalletId::get().into_account_truncating()) - .caller() - .clone(), + AuthoritysOriginId::HomaTreasury => { + Origin::signed(runtime_common::HomaTreasuryPool::get()).caller().clone() + } AuthoritysOriginId::TreasuryReserve => { Origin::signed(TreasuryReservePalletId::get().into_account_truncating()) .caller() diff --git a/runtime/mandala/src/benchmarking/fees.rs b/runtime/mandala/src/benchmarking/fees.rs new file mode 100644 index 000000000..b98491dd8 --- /dev/null +++ b/runtime/mandala/src/benchmarking/fees.rs @@ -0,0 +1,107 @@ +// 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 crate::{Event, Fees, GetNativeCurrencyId, Origin, Runtime, System}; +use frame_system::RawOrigin; +use module_fees::PoolPercent; +use module_support::OnFeeDeposit; +use orml_benchmarking::runtime_benchmarks; +use primitives::{AccountId, Balance, CurrencyId, IncomeSource}; +use sp_runtime::{FixedPointNumber, FixedU128}; +use sp_std::prelude::*; + +const NATIVECOIN: CurrencyId = GetNativeCurrencyId::get(); + +fn assert_last_event(generic_event: Event) { + System::assert_last_event(generic_event.into()); +} + +runtime_benchmarks! { + { Runtime, module_fees } + + set_income_fee { + let pool = PoolPercent { + pool: runtime_common::NetworkTreasuryPool::get(), + rate: FixedU128::saturating_from_rational(1, 1), + }; + let pools = vec![pool]; + }: _(RawOrigin::Root, IncomeSource::TxFee, pools.clone()) + verify { + assert_last_event(module_fees::Event::IncomeFeeSet { + income: IncomeSource::TxFee, + pools, + }.into()); + } + + set_treasury_pool { + let pool = PoolPercent { + pool: runtime_common::NetworkTreasuryPool::get(), + rate: FixedU128::saturating_from_rational(1, 1), + }; + let threshold: Balance = 100; + let treasury: AccountId = runtime_common::NetworkTreasuryPool::get(); + let pools = vec![pool]; + }: _(RawOrigin::Root, treasury.clone(), threshold, pools.clone()) + verify { + assert_last_event(module_fees::Event::TreasuryPoolSet { + treasury, + pools, + }.into()); + } + + force_transfer_to_incentive { + let treasury: AccountId = runtime_common::NetworkTreasuryPool::get(); + let incentive: AccountId = runtime_common::CollatorsRewardPool::get(); + + // set_income_fee: TxFee -> NetworkTreasuryPool + let pool = PoolPercent { + pool: treasury.clone(), + rate: FixedU128::saturating_from_rational(1, 1), + }; + let _ = Fees::set_income_fee(Origin::root(), IncomeSource::TxFee, vec![pool]); + + let treasuries = Fees::income_to_treasuries(IncomeSource::TxFee); + assert_eq!(treasuries.len(), 1); + + // set_treasury_pool: NetworkTreasuryPool -> CollatorsRewardPool + let pool = PoolPercent { + pool: incentive, + rate: FixedU128::saturating_from_rational(1, 1), + }; + let threshold: Balance = 100; + let pools = vec![pool]; + let _ = Fees::set_treasury_pool(Origin::root(), treasury.clone(), threshold, pools.clone()); + + let (store_threshod, incentives) = Fees::treasury_to_incentives(treasury.clone()); + assert_eq!(incentives.len(), 1); + assert_eq!(store_threshod, threshold); + + // distribution fee: TxFee + let _ = >::on_fee_deposit( + IncomeSource::TxFee, NATIVECOIN, 1_000_000_000); + }: _(RawOrigin::Root, treasury.clone()) +} + +#[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 f7ffc34e9..a8e78751e 100644 --- a/runtime/mandala/src/benchmarking/mod.rs +++ b/runtime/mandala/src/benchmarking/mod.rs @@ -38,6 +38,7 @@ pub mod earning; pub mod emergency_shutdown; pub mod evm; pub mod evm_accounts; +pub mod fees; pub mod homa; pub mod honzon; pub mod idle_scheduler; diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index e87b54c2e..ca9b38732 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -38,8 +38,8 @@ pub use frame_support::{ traits::{ ConstBool, ConstU128, ConstU16, ConstU32, Contains, ContainsLengthBound, Currency as PalletCurrency, EnsureOrigin, EqualPrivilegeOnly, Everything, Get, Imbalance, InstanceFilter, IsSubType, IsType, - KeyOwnerProofSystem, LockIdentifier, Nothing, OnRuntimeUpgrade, OnUnbalanced, Randomness, SortedMembers, - U128CurrencyToVote, WithdrawReasons, + KeyOwnerProofSystem, LockIdentifier, Nothing, OnRuntimeUpgrade, Randomness, SortedMembers, U128CurrencyToVote, + WithdrawReasons, }, weights::{ constants::{BlockExecutionWeight, RocksDbWeight, WEIGHT_PER_SECOND}, @@ -163,9 +163,7 @@ parameter_types! { pub const CDPTreasuryPalletId: PalletId = PalletId(*b"aca/cdpt"); pub const HonzonTreasuryPalletId: PalletId = PalletId(*b"aca/hztr"); pub const HomaPalletId: PalletId = PalletId(*b"aca/homa"); - pub const HomaTreasuryPalletId: PalletId = PalletId(*b"aca/hmtr"); pub const IncentivesPalletId: PalletId = PalletId(*b"aca/inct"); - pub const CollatorPotId: PalletId = PalletId(*b"aca/cpot"); // Treasury reserve pub const TreasuryReservePalletId: PalletId = PalletId(*b"aca/reve"); pub const PhragmenElectionPalletId: LockIdentifier = *b"aca/phre"; @@ -189,13 +187,20 @@ pub fn get_all_module_accounts() -> Vec { DEXPalletId::get().into_account_truncating(), CDPTreasuryPalletId::get().into_account_truncating(), HonzonTreasuryPalletId::get().into_account_truncating(), - HomaTreasuryPalletId::get().into_account_truncating(), IncentivesPalletId::get().into_account_truncating(), TreasuryReservePalletId::get().into_account_truncating(), - CollatorPotId::get().into_account_truncating(), StarportPalletId::get().into_account_truncating(), UnreleasedNativeVaultAccountId::get(), StableAssetPalletId::get().into_account_truncating(), + // treasury pools and incentive pools + runtime_common::NetworkTreasuryPool::get(), + runtime_common::HonzonTreasuryPool::get(), + runtime_common::HomaTreasuryPool::get(), + runtime_common::HonzonInsuranceRewardPool::get(), + runtime_common::HonzonLiquitationRewardPool::get(), + runtime_common::StakingRewardPool::get(), + runtime_common::CollatorsRewardPool::get(), + runtime_common::EcosystemRewardPool::get(), ] } @@ -286,7 +291,7 @@ impl module_collator_selection::Config for Runtime { type Currency = Balances; type ValidatorSet = Session; type UpdateOrigin = EnsureRootOrHalfGeneralCouncil; - type PotId = CollatorPotId; + type PotId = runtime_common::CollatorsRewardPoolPalletId; type MinCandidates = ConstU32<5>; type MaxCandidates = ConstU32<200>; type MaxInvulnerables = ConstU32<50>; @@ -1107,6 +1112,7 @@ impl module_cdp_engine::Config for Runtime { type Currency = Currencies; type DEX = Dex; type Swap = AcalaSwap; + type OnFeeDeposit = Fees; type WeightInfo = weights::module_cdp_engine::WeightInfo; } @@ -1215,34 +1221,13 @@ parameter_types! { pub const AlternativeFeeSurplus: Percent = Percent::from_percent(25); } -type NegativeImbalance = >::NegativeImbalance; -pub struct DealWithFees; -impl OnUnbalanced for DealWithFees { - fn on_unbalanceds(mut fees_then_tips: impl Iterator) { - if let Some(mut fees) = fees_then_tips.next() { - if let Some(tips) = fees_then_tips.next() { - tips.merge_into(&mut fees); - } - // for fees and tips, 80% to treasury, 20% to collator-selection pot. - let split = fees.ration(80, 20); - Treasury::on_unbalanced(split.0); - - Balances::resolve_creating(&CollatorSelection::account_id(), split.1); - // Due to performance consideration remove the event. - // let numeric_amount = split.1.peek(); - // let staking_pot = CollatorSelection::account_id(); - // System::deposit_event(pallet_balances::Event::Deposit(staking_pot, numeric_amount)); - } - } -} - impl module_transaction_payment::Config for Runtime { type Event = Event; type Call = Call; type NativeCurrencyId = GetNativeCurrencyId; type Currency = Balances; type MultiCurrency = Currencies; - type OnTransactionPayment = DealWithFees; + type OnTransactionPayment = module_fees::DistributeTxFees; type AlternativeFeeSwapDeposit = NativeTokenExistentialDeposit; type OperationalFeeMultiplier = OperationalFeeMultiplier; type TipPerWeightStep = TipPerWeightStep; @@ -1345,7 +1330,6 @@ pub fn create_x2_parachain_multilocation(index: u16) -> MultiLocation { } parameter_types! { - pub HomaTreasuryAccount: AccountId = HomaTreasuryPalletId::get().into_account_truncating(); pub ActiveSubAccountsIndexList: Vec = vec![ 0, // 15sr8Dvq3AT3Z2Z1y8FnQ4VipekAHhmQnrkgzegUr1tNgbcn ]; @@ -1360,7 +1344,6 @@ impl module_homa::Config for Runtime { type StakingCurrencyId = GetStakingCurrencyId; type LiquidCurrencyId = GetLiquidCurrencyId; type PalletId = HomaPalletId; - type TreasuryAccount = HomaTreasuryAccount; type DefaultExchangeRate = DefaultExchangeRate; type ActiveSubAccountsIndexList = ActiveSubAccountsIndexList; type BondingDuration = ConstU32<28>; @@ -1368,6 +1351,7 @@ impl module_homa::Config for Runtime { type RedeemThreshold = RedeemThreshold; type RelayChainBlockNumber = RelaychainBlockNumberProvider; type XcmInterface = XcmInterface; + type OnFeeDeposit = Fees; type WeightInfo = weights::module_homa::WeightInfo; } @@ -1777,6 +1761,22 @@ impl module_idle_scheduler::Config for Runtime { type DisableBlockThreshold = ConstU32<6>; } +parameter_types! { + pub const AllocationPeriod: BlockNumber = 10 * MINUTES; +} + +impl module_fees::Config for Runtime { + type Event = Event; + type WeightInfo = weights::module_fees::WeightInfo; + type UpdateOrigin = EnsureRootOrThreeFourthsGeneralCouncil; + type Currency = Balances; + type Currencies = Currencies; + type NativeCurrencyId = GetNativeCurrencyId; + type AllocationPeriod = AllocationPeriod; + type DEX = Dex; + type DexSwapJointList = AlternativeSwapPathJointList; +} + impl cumulus_pallet_aura_ext::Config for Runtime {} #[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)] @@ -1896,8 +1896,119 @@ pub type SignedPayload = generic::SignedPayload; /// Extrinsic type that has already been checked. pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. -pub type Executive = - frame_executive::Executive, Runtime, AllPalletsWithSystem, ()>; +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + FeesMigration, +>; + +use primitives::IncomeSource; + +pub struct FeesMigration; + +impl OnRuntimeUpgrade for FeesMigration { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let incomes = vec![ + ( + IncomeSource::TxFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 80), + (runtime_common::CollatorsRewardPool::get(), 20), + ], + ), + ( + IncomeSource::XcmFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::DexSwapFee, + vec![(runtime_common::NetworkTreasuryPool::get(), 100)], + ), + ( + IncomeSource::HonzonStabilityFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HonzonTreasuryPool::get(), 30), + ], + ), + ( + IncomeSource::HonzonLiquidationFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 30), + (runtime_common::HonzonTreasuryPool::get(), 70), + ], + ), + ( + IncomeSource::HomaStakingRewardFee, + vec![ + (runtime_common::NetworkTreasuryPool::get(), 70), + (runtime_common::HomaTreasuryPool::get(), 30), + ], + ), + ]; + let treasuries = vec![ + ( + runtime_common::NetworkTreasuryPool::get(), + 1000 * dollar(ACA), + vec![ + (runtime_common::StakingRewardPool::get(), 70), + (runtime_common::CollatorsRewardPool::get(), 10), + (runtime_common::EcosystemRewardPool::get(), 10), + (TreasuryAccount::get(), 10), + ], + ), + ( + runtime_common::HonzonTreasuryPool::get(), + 1000 * dollar(ACA), + vec![ + (runtime_common::HonzonInsuranceRewardPool::get(), 30), + (runtime_common::HonzonLiquitationRewardPool::get(), 70), + ], + ), + ]; + incomes.iter().for_each(|(income, pools)| { + let pool_rates = module_fees::build_pool_percents::(pools.clone()); + let _ = >::do_set_treasury_rate(*income, pool_rates); + }); + treasuries.iter().for_each(|(treasury, threshold, pools)| { + let pool_rates = module_fees::build_pool_percents::(pools.clone()); + let _ = >::do_set_incentive_rate(treasury.clone(), *threshold, pool_rates); + }); + + ::BlockWeights::get().max_block + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + assert!(>::contains_key( + &IncomeSource::TxFee + )); + assert!(>::contains_key( + &IncomeSource::XcmFee + )); + assert!(>::contains_key( + &IncomeSource::HonzonStabilityFee + )); + assert!(>::contains_key( + &IncomeSource::HomaStakingRewardFee + )); + assert!(>::contains_key( + &runtime_common::NetworkTreasuryPool::get() + )); + assert!(>::contains_key( + &runtime_common::HonzonTreasuryPool::get() + )); + Ok(()) + } +} construct_runtime!( pub enum Runtime where @@ -1918,6 +2029,7 @@ construct_runtime!( Currencies: module_currencies = 12, Vesting: orml_vesting = 13, TransactionPayment: module_transaction_payment = 14, + Fees: module_fees = 15, // Treasury Treasury: pallet_treasury = 20, @@ -2043,6 +2155,7 @@ mod benches { [module_earning, benchmarking::earning] [module_emergency_shutdown, benchmarking::emergency_shutdown] [module_evm, benchmarking::evm] + [module_fees, benchmarking::fees] [module_homa, benchmarking::homa] [module_honzon, benchmarking::honzon] [module_cdp_treasury, benchmarking::cdp_treasury] diff --git a/runtime/mandala/src/weights/mod.rs b/runtime/mandala/src/weights/mod.rs index ce9c0da45..7844975b5 100644 --- a/runtime/mandala/src/weights/mod.rs +++ b/runtime/mandala/src/weights/mod.rs @@ -30,6 +30,7 @@ pub mod module_dex_oracle; pub mod module_emergency_shutdown; pub mod module_evm; pub mod module_evm_accounts; +pub mod module_fees; pub mod module_homa; pub mod module_honzon; pub mod module_incentives; diff --git a/runtime/mandala/src/weights/module_fees.rs b/runtime/mandala/src/weights/module_fees.rs new file mode 100644 index 000000000..51a1aa99c --- /dev/null +++ b/runtime/mandala/src/weights/module_fees.rs @@ -0,0 +1,74 @@ +// 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 . + +//! Autogenerated weights for module_fees +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-06-20, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/acala +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_fees +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=./templates/runtime-weight-template.hbs +// --output=./runtime/mandala/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for module_fees. +pub struct WeightInfo(PhantomData); +impl module_fees::WeightInfo for WeightInfo { + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees IncomeToTreasuries (r:1 w:1) + fn set_income_fee() -> Weight { + (16_072_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees TreasuryToIncentives (r:1 w:1) + fn set_treasury_pool() -> Weight { + (16_821_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Fees TreasuryTokens (r:1 w:0) + // Storage: Fees TreasuryToIncentives (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: EvmAccounts EvmAddresses (r:1 w:0) + fn force_transfer_to_incentive() -> Weight { + (47_115_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } +} diff --git a/runtime/mandala/src/xcm_config.rs b/runtime/mandala/src/xcm_config.rs index fee946d99..1b3ea7448 100644 --- a/runtime/mandala/src/xcm_config.rs +++ b/runtime/mandala/src/xcm_config.rs @@ -18,8 +18,8 @@ use super::{ constants::fee::*, AccountId, AssetIdMapping, AssetIdMaps, Balance, Call, Convert, Currencies, CurrencyId, Event, - ExistentialDeposits, GetNativeCurrencyId, NativeTokenExistentialDeposit, Origin, ParachainInfo, ParachainSystem, - PolkadotXcm, Runtime, TreasuryAccount, UnknownTokens, XcmpQueue, ACA, + ExistentialDeposits, Fees, GetNativeCurrencyId, NativeTokenExistentialDeposit, Origin, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, TreasuryAccount, UnknownTokens, XcmpQueue, ACA, }; use codec::{Decode, Encode}; pub use cumulus_primitives_core::ParaId; @@ -30,12 +30,14 @@ pub use frame_support::{ }; use module_asset_registry::{BuyWeightRateOfErc20, BuyWeightRateOfForeignAsset}; use module_transaction_payment::BuyWeightRateOfTransactionFeePool; -use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key, MultiCurrency}; +use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use primitives::evm::is_system_contract; -use runtime_common::{native_currency_location, AcalaDropAssets, EnsureRootOrHalfGeneralCouncil, FixedRateOfAsset}; +use runtime_common::{ + native_currency_location, AcalaDropAssets, EnsureRootOrHalfGeneralCouncil, FixedRateOfAsset, XcmFeeToTreasury, +}; use xcm::latest::prelude::*; pub use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, @@ -95,24 +97,6 @@ pub type Barrier = ( AllowSubscriptionsFrom, ); -pub struct ToTreasury; -impl TakeRevenue for ToTreasury { - fn take_revenue(revenue: MultiAsset) { - if let MultiAsset { - id: Concrete(location), - fun: Fungible(amount), - } = revenue - { - if let Some(currency_id) = CurrencyIdConvert::convert(location) { - // Ensure TreasuryAccount have ed requirement for native asset, but don't need - // ed requirement for cross-chain asset because it's one of whitelist accounts. - // Ignore the result. - let _ = Currencies::deposit(currency_id, &TreasuryAccount::get(), amount); - } - } - } -} - parameter_types! { // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = 1_000_000; @@ -128,12 +112,14 @@ parameter_types! { pub BaseRate: u128 = aca_per_second(); } +type XcmToTreasury = XcmFeeToTreasury; + pub type Trader = ( - FixedRateOfAsset>, - FixedRateOfFungible, - FixedRateOfFungible, - FixedRateOfAsset>, - FixedRateOfAsset>, + FixedRateOfAsset>, + FixedRateOfFungible, + FixedRateOfFungible, + FixedRateOfAsset>, + FixedRateOfAsset>, ); pub struct XcmConfig; @@ -154,7 +140,7 @@ impl xcm_executor::Config for XcmConfig { type ResponseHandler = (); // Don't handle responses for now. type AssetTrap = AcalaDropAssets< PolkadotXcm, - ToTreasury, + XcmToTreasury, CurrencyIdConvert, GetNativeCurrencyId, NativeTokenExistentialDeposit,