diff --git a/Cargo.lock b/Cargo.lock index 8fdd65d2dd2..27a02d8d031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6718,6 +6718,7 @@ dependencies = [ "pallet-gear-gas", "pallet-gear-messenger", "pallet-session", + "pallet-treasury", "sp-runtime", "sp-std 8.0.0", ] diff --git a/pallets/gear-bank/src/lib.rs b/pallets/gear-bank/src/lib.rs index c51945cd7fe..773599944f9 100644 --- a/pallets/gear-bank/src/lib.rs +++ b/pallets/gear-bank/src/lib.rs @@ -46,6 +46,8 @@ macro_rules! impl_config { type Currency = Balances; type BankAddress = BankAddress; type GasMultiplier = GasMultiplier; + type SplitGasFeeRatio = SplitGasFeeRatio; + type SplitTxFeeRatio = SplitTxFeeRatio; } }; } @@ -78,7 +80,7 @@ pub mod pallet { use pallet_authorship::Pallet as Authorship; use parity_scale_codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use scale_info::TypeInfo; - use sp_runtime::traits::Zero; + use sp_runtime::{traits::Zero, Perbill}; // Funds pallet struct itself. #[pallet::pallet] @@ -100,6 +102,11 @@ pub mod pallet { #[pallet::constant] /// Gas price converter. type GasMultiplier: Get>; + + type SplitGasFeeRatio: Get)>>; + + /// The ratio of how much of the tx fees goes to the treasury + type SplitTxFeeRatio: Get>; } // Funds pallets error. @@ -218,10 +225,28 @@ pub mod pallet { while let Some((account_id, value)) = OnFinalizeTransfers::::drain().next() { total = total.saturating_add(value); - if let Err(e) = Self::withdraw(&account_id, value) { - log::error!( - "Block #{bn:?} ended with unreachable error while performing on-finalize transfer to {account_id:?}: {e:?}" - ); + if let Some((gas_split, split_dest)) = T::SplitGasFeeRatio::get() { + // split value by `SplitGasFeeRatio`. + let to_split = gas_split.mul_floor(value); + let to_user = value - to_split; + + // Withdraw value to user. + if let Err(e) = Self::withdraw(&account_id, to_user) { + log::error!( + "Block #{bn:?} ended with unreachable error while performing on-finalize transfer to {account_id:?}: {e:?}" + ); + } + + // Withdraw value to `SplitGasFeeRatio` destination. + if let Err(e) = Self::withdraw(&split_dest, to_split) { + log::error!( + "Block #{bn:?} ended with unreachable error while performing on-finalize transfer to {account_id:?}: {e:?}" + ); + } + } else { + let _ = Self::withdraw(&account_id, value).map_err(|e| log::error!( + "Block #{bn:?} ended with unreachable error while performing on-finalize transfer to {account_id:?}: {e:?}" + )); } } @@ -541,7 +566,7 @@ pub mod pallet { let err_msg = format!( "pallet_gear_bank::withdraw_value: withdraw failed. \ Receiver - {account_id:?}, value - {value:?}, receiver reducible balance - {receiver_balance:?}, \ - bank reducible balance - {bank_balance:?}, unused value - {unused_value:?}, + bank reducible balance - {bank_balance:?}, unused value - {unused_value:?}, on finalize value - {on_finalize_value:?}. \ Got error - {e:?}" ); diff --git a/pallets/gear-bank/src/mock.rs b/pallets/gear-bank/src/mock.rs index c0c8cee17d7..f88b1ec286a 100644 --- a/pallets/gear-bank/src/mock.rs +++ b/pallets/gear-bank/src/mock.rs @@ -21,12 +21,13 @@ use frame_support::{ construct_runtime, parameter_types, traits::{ConstU32, FindAuthor}, weights::constants::RocksDbWeight, + PalletId, }; use primitive_types::H256; use sp_io::TestExternalities; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, + BuildStorage, Perbill, }; pub type AccountId = u8; @@ -64,6 +65,9 @@ parameter_types! { pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(VALUE_PER_GAS); pub const BlockHashCount: BlockNumber = 250; pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; + // TODO: Issue #4058 + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = Some((Perbill::from_percent(50), PalletId(*b"py/trsry").into_account_truncating())); + pub SplitTxFeeRatio: Option = None; } construct_runtime!( diff --git a/pallets/gear-bank/src/tests.rs b/pallets/gear-bank/src/tests.rs index ecbdf1f8a0e..5b3dcb4aaf9 100644 --- a/pallets/gear-bank/src/tests.rs +++ b/pallets/gear-bank/src/tests.rs @@ -499,7 +499,9 @@ fn spend_gas_all_balance_validator_account_deleted() { assert_bank_balance(0, 0); - assert_balance(&BLOCK_AUTHOR, gas_price(GAS_AMOUNT)); + // mul ceil GAS_AMOUNT because of gas fee split. + let (gas_split, _) = SplitGasFeeRatio::get().unwrap(); + assert_balance(&BLOCK_AUTHOR, gas_split.mul_ceil(gas_price(GAS_AMOUNT))); assert_alice_dec(gas_price(GAS_AMOUNT)); assert_gas_value(&ALICE, 0, 0); @@ -1636,7 +1638,12 @@ mod utils { // Asserts block author balance inc. #[track_caller] pub fn assert_block_author_inc(diff: Balance) { - assert_balance(&BLOCK_AUTHOR, EXISTENTIAL_DEPOSIT + diff) + // mul ceil diff because of gas fee split. + let (gas_split, _) = SplitGasFeeRatio::get().unwrap(); + assert_balance( + &BLOCK_AUTHOR, + EXISTENTIAL_DEPOSIT + gas_split.mul_ceil(diff), + ) } // Asserts Charlie balance inc. diff --git a/pallets/gear-builtin/src/mock.rs b/pallets/gear-builtin/src/mock.rs index cc3d91664ab..98ce908b8b7 100644 --- a/pallets/gear-builtin/src/mock.rs +++ b/pallets/gear-builtin/src/mock.rs @@ -113,6 +113,8 @@ parameter_types! { pub const PerformanceMultiplier: u32 = 100; pub const BankAddress: AccountId = 15082001; pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(25); + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = None; + pub SplitTxFeeRatio: Option = None; } pallet_gear_bank::impl_config!(Test); diff --git a/pallets/gear-builtin/src/tests/bad_builtin_ids.rs b/pallets/gear-builtin/src/tests/bad_builtin_ids.rs index af1f58552c6..bef2567d7d2 100644 --- a/pallets/gear-builtin/src/tests/bad_builtin_ids.rs +++ b/pallets/gear-builtin/src/tests/bad_builtin_ids.rs @@ -82,6 +82,8 @@ parameter_types! { pub const PerformanceMultiplier: u32 = 100; pub const BankAddress: AccountId = 15082001; pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(25); + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = None; + pub SplitTxFeeRatio: Option = None; } pallet_gear_bank::impl_config!(Test); diff --git a/pallets/gear-builtin/src/tests/staking.rs b/pallets/gear-builtin/src/tests/staking.rs index bd806206ef5..2390851e076 100644 --- a/pallets/gear-builtin/src/tests/staking.rs +++ b/pallets/gear-builtin/src/tests/staking.rs @@ -704,6 +704,8 @@ mod util { pub const PerformanceMultiplier: u32 = 100; pub const BankAddress: AccountId = 15082001; pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(25); + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = None; + pub SplitTxFeeRatio: Option = None; } pub struct TestSessionHandler; diff --git a/pallets/gear-debug/src/mock.rs b/pallets/gear-debug/src/mock.rs index 4bcc6808f77..e82fa44f19f 100644 --- a/pallets/gear-debug/src/mock.rs +++ b/pallets/gear-debug/src/mock.rs @@ -32,7 +32,7 @@ use primitive_types::H256; use sp_core::ConstBool; use sp_runtime::{ traits::{BlakeTwo256, ConstU64, IdentityLookup}, - BuildStorage, + BuildStorage, Perbill, }; use sp_std::convert::{TryFrom, TryInto}; @@ -70,6 +70,8 @@ parameter_types! { pub ResumeSessionDuration: BlockNumber = 1_000; pub const BankAddress: AccountId = 15082001; pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(25); + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = None; + pub SplitTxFeeRatio: Option = None; pub ReserveThreshold: BlockNumber = 1; } diff --git a/pallets/gear-eth-bridge/src/mock.rs b/pallets/gear-eth-bridge/src/mock.rs index 3f93bc0cec9..cf5fbb3bce6 100644 --- a/pallets/gear-eth-bridge/src/mock.rs +++ b/pallets/gear-eth-bridge/src/mock.rs @@ -30,7 +30,7 @@ use sp_core::{ed25519::Public, H256}; use sp_runtime::{ impl_opaque_keys, traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + BuildStorage, Perbill, }; use sp_std::convert::{TryFrom, TryInto}; @@ -160,6 +160,8 @@ parameter_types! { pub const PerformanceMultiplier: u32 = 100; pub const BankAddress: AccountId = 15082001; pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(25); + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = None; + pub SplitTxFeeRatio: Option = None; } pallet_gear_bank::impl_config!(Test); diff --git a/pallets/gear-scheduler/src/mock.rs b/pallets/gear-scheduler/src/mock.rs index 609ac8cbcc8..80eb50d7ef2 100644 --- a/pallets/gear-scheduler/src/mock.rs +++ b/pallets/gear-scheduler/src/mock.rs @@ -32,7 +32,7 @@ use pallet_gear::GasAllowanceOf; use sp_core::{ConstBool, H256}; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + BuildStorage, Perbill, }; use sp_std::convert::{TryFrom, TryInto}; @@ -94,6 +94,8 @@ parameter_types! { pub ResumeSessionDuration: BlockNumber = 1_000; pub const BankAddress: AccountId = 15082001; pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(25); + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = None; + pub SplitTxFeeRatio: Option = None; } // Build genesis storage according to the mock runtime. diff --git a/pallets/gear/src/mock.rs b/pallets/gear/src/mock.rs index 7be27b58b61..a66c04f9e07 100644 --- a/pallets/gear/src/mock.rs +++ b/pallets/gear/src/mock.rs @@ -31,7 +31,7 @@ use frame_system::{self as system, limits::BlockWeights, mocking, pallet_prelude use sp_core::{ConstU8, H256}; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + BuildStorage, Perbill, }; use sp_std::{ cell::RefCell, @@ -173,6 +173,8 @@ impl Drop for DynamicScheduleReset { parameter_types! { pub const BankAddress: AccountId = 15082001; pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(1); + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = None; + pub SplitTxFeeRatio: Option = None; pub const MinVoucherDuration: BlockNumber = 5; pub const MaxVoucherDuration: BlockNumber = 100_000_000; } diff --git a/pallets/payment/src/mock.rs b/pallets/payment/src/mock.rs index 6a0929ae290..1c9424600ab 100644 --- a/pallets/payment/src/mock.rs +++ b/pallets/payment/src/mock.rs @@ -35,7 +35,7 @@ use primitive_types::H256; use sp_runtime::{ testing::TestXt, traits::{BlakeTwo256, ConstBool, ConstU64, IdentityLookup}, - BuildStorage, + BuildStorage, Perbill, }; use sp_std::{ convert::{TryFrom, TryInto}, @@ -119,6 +119,8 @@ parameter_types! { pub ResumeSessionDuration: BlockNumber = 1_000; pub const BankAddress: AccountId = 15082001; pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(25); + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = None; + pub SplitTxFeeRatio: Option = None; } type NegativeImbalance = >::NegativeImbalance; diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 478a32646fa..314c526c36a 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -17,6 +17,7 @@ frame-system.workspace = true pallet-authorship.workspace = true pallet-balances.workspace = true pallet-session.workspace = true +pallet-treasury.workspace = true sp-runtime.workspace = true sp-std.workspace = true diff --git a/runtime/common/src/constants.rs b/runtime/common/src/constants.rs index 029b6d85541..9ba5b9ae6ac 100644 --- a/runtime/common/src/constants.rs +++ b/runtime/common/src/constants.rs @@ -42,3 +42,9 @@ pub const RENT_FREE_PERIOD_MONTH_FACTOR: BlockNumber = 6; /// The amount of blocks on which tasks of pausing program shifted /// in a case of disabled program rent logic, represented as a factor of weeks. pub const RENT_DISABLED_DELTA_WEEK_FACTOR: BlockNumber = 1; + +/// The percentage of the transaction fee that will go to the treasury +pub const SPLIT_TX_FEE_PERCENT: u32 = 0; + +/// The percentage of the gas fee that will go to the specified destination +pub const SPLIT_GAS_PERCENT: u32 = 0; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index ab14d5c643d..ac8259048d4 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -22,10 +22,12 @@ mod apis; pub mod constants; pub mod weights; +use sp_runtime::traits::Get; + use frame_support::{ pallet_prelude::DispatchClass, parameter_types, - traits::{Currency, OnUnbalanced}, + traits::{Currency, Imbalance, OnUnbalanced}, weights::{ constants::{BlockExecutionWeight, ExtrinsicBaseWeight}, Weight, @@ -85,22 +87,54 @@ pub type NegativeImbalance = as Currency< ::AccountId, >>::NegativeImbalance; +/// Logic for the author to get a portion of fees. +pub struct ToAuthor(sp_std::marker::PhantomData); +impl OnUnbalanced> for ToAuthor +where + R: pallet_balances::Config + pallet_authorship::Config, + ::AccountId: From, + ::AccountId: Into, +{ + fn on_nonzero_unbalanced(amount: NegativeImbalance) { + if let Some(author) = >::author() { + >::resolve_creating(&author, amount); + } + } +} + pub struct DealWithFees(sp_std::marker::PhantomData); impl OnUnbalanced> for DealWithFees where - R: pallet_balances::Config + pallet_authorship::Config, + R: pallet_balances::Config + + pallet_treasury::Config + + pallet_authorship::Config + + pallet_gear_bank::Config, + pallet_treasury::Pallet: OnUnbalanced>, ::AccountId: From, ::AccountId: Into, { fn on_unbalanceds(mut fees_then_tips: impl Iterator>) { + use pallet_treasury::Pallet as Treasury; + if let Some(fees) = fees_then_tips.next() { - if let Some(author) = >::author() { - >::resolve_creating(&author, fees); - } - if let Some(tips) = fees_then_tips.next() { - if let Some(author) = >::author() { - >::resolve_creating(&author, tips); + let split_tx_fee_ratio = R::SplitTxFeeRatio::get(); + if let Some(split_tx_fee_ratio) = split_tx_fee_ratio { + // for fees, SplitTxFeeRatio to treasury else to author + let (mut to_author, to_treasury) = + fees.ration(100 - split_tx_fee_ratio, split_tx_fee_ratio); + if let Some(tips) = fees_then_tips.next() { + // for tips, if any, 100% to author + tips.merge_into(&mut to_author); + } + as OnUnbalanced<_>>::on_unbalanced(to_treasury); + as OnUnbalanced<_>>::on_unbalanced(to_author); + } else { + let mut to_author = fees; + if let Some(tips) = fees_then_tips.next() { + // for tips, if any, 100% to author + tips.merge_into(&mut to_author); } + as OnUnbalanced<_>>::on_unbalanced(to_author); } } } diff --git a/runtime/vara/src/integration_tests.rs b/runtime/vara/src/integration_tests.rs index 419b2fce430..9b5a790ae94 100644 --- a/runtime/vara/src/integration_tests.rs +++ b/runtime/vara/src/integration_tests.rs @@ -861,3 +861,48 @@ fn fungible_api_works() { ); }); } + +#[test] +fn test_fees_and_tip_split() { + init_logger(); + + let alice = AccountKeyring::Alice; + let charlie = AccountKeyring::Charlie; + + ExtBuilder::default() + .initial_authorities(vec![( + alice.into(), + charlie.into(), + alice.public(), + ed25519::Pair::from_string("//Alice", None) + .unwrap() + .public(), + alice.public(), + alice.public(), + )]) + .stash(STASH) + .endowment(ENDOWMENT) + .endowed_accounts(vec![]) + .root(alice.into()) + .build() + .execute_with(|| { + let fee = Balances::issue(10); + let tip = Balances::issue(20); + + assert_eq!( + Balances::free_balance(Treasury::account_id()), + EXISTENTIAL_DEPOSIT + ); + assert_eq!(Balances::free_balance(alice.to_account_id()), STASH); + + DealWithFees::on_unbalanceds(vec![fee, tip].into_iter()); + + // Author gets 100% of the tip and 100% of the fee = 30 + assert_eq!(Balances::free_balance(alice.to_account_id()), STASH + 30); + // Treasury gets 0% of the fee + assert_eq!( + Balances::free_balance(Treasury::account_id()), + EXISTENTIAL_DEPOSIT + ); + }); +} diff --git a/runtime/vara/src/lib.rs b/runtime/vara/src/lib.rs index 39ec44a3970..14dd2784fa4 100644 --- a/runtime/vara/src/lib.rs +++ b/runtime/vara/src/lib.rs @@ -1102,12 +1102,16 @@ parameter_types! { pub Schedule: pallet_gear::Schedule = Default::default(); pub BankAddress: AccountId = BANK_ADDRESS.into(); pub const GasMultiplier: common::GasMultiplier = common::GasMultiplier::ValuePerGas(VALUE_PER_GAS); + pub SplitGasFeeRatio: Option<(Perbill, AccountId)> = None; + pub SplitTxFeeRatio: Option = None; } impl pallet_gear_bank::Config for Runtime { type Currency = Balances; type BankAddress = BankAddress; type GasMultiplier = GasMultiplier; + type SplitGasFeeRatio = SplitGasFeeRatio; + type SplitTxFeeRatio = SplitTxFeeRatio; } impl pallet_gear::Config for Runtime {