diff --git a/primitives/src/evm.rs b/primitives/src/evm.rs
index 2a4d848c4..29874ab19 100644
--- a/primitives/src/evm.rs
+++ b/primitives/src/evm.rs
@@ -18,11 +18,18 @@
use crate::{AccountId, AssetId};
-use frame_support::ensure;
-use pallet_evm::{AddressMapping, HashedAddressMapping};
+use frame_support::{
+ ensure,
+ traits::{
+ fungible::{Balanced, Credit},
+ tokens::{fungible::Inspect, imbalance::OnUnbalanced},
+ },
+};
+use pallet_evm::{AddressMapping, HashedAddressMapping, OnChargeEVMTransaction};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
-use sp_core::{Hasher, H160, H256};
+use sp_core::{Hasher, H160, H256, U256};
+use sp_runtime::traits::UniqueSaturatedInto;
use sp_std::marker::PhantomData;
use pallet_assets::AssetsCallback;
@@ -139,3 +146,52 @@ impl
UnifiedAddress {
}
}
}
+
+/// Wrapper around the `EvmFungibleAdapter` from the `pallet-evm`.
+///
+/// While it provides most of the functionality we need,
+/// it doesn't allow the tip to be deposited into an arbitrary account.
+/// This adapter allows us to do that.
+///
+/// Two separate `OnUnbalanced` handers are used:
+/// - `UOF` for the fee
+/// - `OUT` for the tip
+pub struct EVMFungibleAdapterWrapper(
+ core::marker::PhantomData<(F, FeeHandler, TipHandler)>,
+);
+impl OnChargeEVMTransaction
+ for EVMFungibleAdapterWrapper
+where
+ T: pallet_evm::Config,
+ F: Balanced,
+ FeeHandler: OnUnbalanced>,
+ TipHandler: OnUnbalanced>,
+ U256: UniqueSaturatedInto<::AccountId>>::Balance>,
+{
+ // Kept type as Option to satisfy bound of Default
+ type LiquidityInfo = Option>;
+
+ fn withdraw_fee(who: &H160, fee: U256) -> Result> {
+ pallet_evm::EVMFungibleAdapter::::withdraw_fee(who, fee)
+ }
+
+ fn correct_and_deposit_fee(
+ who: &H160,
+ corrected_fee: U256,
+ base_fee: U256,
+ already_withdrawn: Self::LiquidityInfo,
+ ) -> Self::LiquidityInfo {
+ as OnChargeEVMTransaction>::correct_and_deposit_fee(
+ who,
+ corrected_fee,
+ base_fee,
+ already_withdrawn,
+ )
+ }
+
+ fn pay_priority_fee(tip: Self::LiquidityInfo) {
+ if let Some(tip) = tip {
+ TipHandler::on_unbalanceds(Some(tip).into_iter());
+ }
+ }
+}
diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs
index 17a6e0f09..1fad5c74b 100644
--- a/runtime/astar/src/lib.rs
+++ b/runtime/astar/src/lib.rs
@@ -84,7 +84,7 @@ use astar_primitives::{
AccountCheck as DappStakingAccountCheck, CycleConfiguration, DAppId, EraNumber,
PeriodNumber, RankedTier, SmartContract, StandardTierSlots,
},
- evm::EvmRevertCodeHandler,
+ evm::{EVMFungibleAdapterWrapper, EvmRevertCodeHandler},
governance::{
CommunityCouncilCollectiveInst, CommunityCouncilMembershipInst, CommunityTreasuryInst,
EnsureRootOrAllMainCouncil, EnsureRootOrAllTechnicalCommittee,
@@ -474,7 +474,7 @@ impl pallet_inflation::PayoutPerBlock> for Inflation
}
fn collators(reward: Credit) {
- ToStakingPot::on_unbalanced(reward);
+ CollatorRewardPot::on_unbalanced(reward);
}
}
@@ -614,8 +614,8 @@ parameter_types! {
pub TreasuryAccountId: AccountId = TreasuryPalletId::get().into_account_truncating();
}
-pub struct ToStakingPot;
-impl OnUnbalanced> for ToStakingPot {
+pub struct CollatorRewardPot;
+impl OnUnbalanced> for CollatorRewardPot {
fn on_nonzero_unbalanced(amount: Credit) {
let staking_pot = PotId::get().into_account_truncating();
let _ = Balances::resolve(&staking_pot, amount);
@@ -838,9 +838,13 @@ impl OnUnbalanced> for DealWithFees {
drop(to_burn);
// pay fees to collator
- >::on_unbalanced(collator);
+ >::on_unbalanced(collator);
}
}
+
+ fn on_unbalanced(amount: Credit) {
+ Self::on_unbalanceds(Some(amount).into_iter());
+ }
}
impl pallet_transaction_payment::Config for Runtime {
@@ -946,7 +950,7 @@ impl pallet_evm::Config for Runtime {
type PrecompilesType = Precompiles;
type PrecompilesValue = PrecompilesValue;
type ChainId = ChainId;
- type OnChargeTransaction = pallet_evm::EVMFungibleAdapter;
+ type OnChargeTransaction = EVMFungibleAdapterWrapper;
type BlockGasLimit = BlockGasLimit;
type Timestamp = Timestamp;
type OnCreate = ();
@@ -1026,7 +1030,7 @@ pub enum ProxyType {
/// Allows all runtime calls for proxy account
Any,
/// Allows only NonTransfer runtime calls for proxy account
- /// To know exact calls check InstanceFilter inmplementation for ProxyTypes
+ /// To know exact calls check InstanceFilter implementation for ProxyTypes
NonTransfer,
/// All Runtime calls from Pallet Balances allowed for proxy account
Balances,
@@ -1062,26 +1066,15 @@ impl InstanceFilter for ProxyType {
c,
RuntimeCall::System(..)
| RuntimeCall::Identity(..)
- | RuntimeCall::Timestamp(..)
| RuntimeCall::Multisig(..)
| RuntimeCall::Proxy(..)
- | RuntimeCall::ParachainSystem(..)
- | RuntimeCall::ParachainInfo(..)
- // Skip entire Balances pallet
- | RuntimeCall::Vesting(pallet_vesting::Call::vest{..})
- | RuntimeCall::Vesting(pallet_vesting::Call::vest_other{..})
- // Specifically omitting Vesting `vested_transfer`, and `force_vested_transfer`
+ | RuntimeCall::Vesting(
+ pallet_vesting::Call::vest { .. }
+ | pallet_vesting::Call::vest_other { .. }
+ )
| RuntimeCall::DappStaking(..)
- // Skip entire Assets pallet
| RuntimeCall::CollatorSelection(..)
- | RuntimeCall::Session(..)
- | RuntimeCall::XcmpQueue(..)
- | RuntimeCall::PolkadotXcm(..)
- | RuntimeCall::CumulusXcm(..)
| RuntimeCall::XcAssetConfig(..)
- // Skip entire EVM pallet
- // Skip entire Ethereum pallet
- | RuntimeCall::DynamicEvmBaseFee(..) // Skip entire Contracts pallet
)
}
ProxyType::Balances => {
diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs
index b290d574a..e75c009ec 100644
--- a/runtime/local/src/lib.rs
+++ b/runtime/local/src/lib.rs
@@ -811,18 +811,12 @@ impl InstanceFilter for ProxyType {
matches!(
c,
RuntimeCall::System(..)
- | RuntimeCall::Timestamp(..)
- | RuntimeCall::Scheduler(..)
| RuntimeCall::Proxy(..)
- | RuntimeCall::Grandpa(..)
- // Skip entire Balances pallet
- | RuntimeCall::Vesting(pallet_vesting::Call::vest{..})
- | RuntimeCall::Vesting(pallet_vesting::Call::vest_other{..})
- // Specifically omitting Vesting `vested_transfer`, and `force_vested_transfer`
+ | RuntimeCall::Vesting(
+ pallet_vesting::Call::vest { .. }
+ | pallet_vesting::Call::vest_other { .. }
+ )
| RuntimeCall::DappStaking(..)
- // Skip entire EVM pallet
- // Skip entire Ethereum pallet
- | RuntimeCall::DynamicEvmBaseFee(..) // Skip entire Contracts pallet
)
}
// All Runtime calls from Pallet Balances allowed for proxy account
diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs
index 9a4b1fffc..7b8a46390 100644
--- a/runtime/shibuya/src/lib.rs
+++ b/runtime/shibuya/src/lib.rs
@@ -83,7 +83,7 @@ use astar_primitives::{
AccountCheck as DappStakingAccountCheck, CycleConfiguration, DAppId, EraNumber,
PeriodNumber, RankedTier, SmartContract, StandardTierSlots,
},
- evm::{EvmRevertCodeHandler, HashedDefaultMappings},
+ evm::{EVMFungibleAdapterWrapper, EvmRevertCodeHandler, HashedDefaultMappings},
governance::{
CommunityCouncilCollectiveInst, CommunityCouncilMembershipInst, CommunityTreasuryInst,
EnsureRootOrAllMainCouncil, EnsureRootOrAllTechnicalCommittee,
@@ -497,7 +497,7 @@ impl pallet_inflation::PayoutPerBlock> for Inflation
}
fn collators(reward: Credit) {
- ToStakingPot::on_unbalanced(reward);
+ CollatorRewardPot::on_unbalanced(reward);
}
}
@@ -638,8 +638,8 @@ parameter_types! {
pub TreasuryAccountId: AccountId = TreasuryPalletId::get().into_account_truncating();
}
-pub struct ToStakingPot;
-impl OnUnbalanced> for ToStakingPot {
+pub struct CollatorRewardPot;
+impl OnUnbalanced> for CollatorRewardPot {
fn on_nonzero_unbalanced(amount: Credit) {
let staking_pot = PotId::get().into_account_truncating();
let _ = Balances::resolve(&staking_pot, amount);
@@ -834,9 +834,13 @@ impl OnUnbalanced> for DealWithFees {
drop(to_burn);
// pay fees to collator
- >::on_unbalanced(collator);
+ >::on_unbalanced(collator);
}
}
+
+ fn on_unbalanced(amount: Credit) {
+ Self::on_unbalanceds(Some(amount).into_iter());
+ }
}
impl pallet_transaction_payment::Config for Runtime {
@@ -947,7 +951,7 @@ impl pallet_evm::Config for Runtime {
// Ethereum-compatible chain_id:
// * Shibuya: 81
type ChainId = EVMChainId;
- type OnChargeTransaction = pallet_evm::EVMFungibleAdapter;
+ type OnChargeTransaction = EVMFungibleAdapterWrapper;
type BlockGasLimit = BlockGasLimit;
type Timestamp = Timestamp;
type OnCreate = ();
@@ -1042,27 +1046,14 @@ impl InstanceFilter for ProxyType {
c,
RuntimeCall::System(..)
| RuntimeCall::Identity(..)
- | RuntimeCall::Timestamp(..)
| RuntimeCall::Multisig(..)
- | RuntimeCall::Scheduler(..)
| RuntimeCall::Proxy(..)
- | RuntimeCall::ParachainSystem(..)
- | RuntimeCall::ParachainInfo(..)
- // Skip entire Balances pallet
- | RuntimeCall::Vesting(pallet_vesting::Call::vest{..})
- | RuntimeCall::Vesting(pallet_vesting::Call::vest_other{..})
- // Specifically omitting Vesting `vested_transfer`, and `force_vested_transfer`
+ | RuntimeCall::Vesting(
+ pallet_vesting::Call::vest { .. }
+ | pallet_vesting::Call::vest_other { .. }
+ )
| RuntimeCall::DappStaking(..)
- // Skip entire Assets pallet
| RuntimeCall::CollatorSelection(..)
- | RuntimeCall::Session(..)
- | RuntimeCall::XcmpQueue(..)
- | RuntimeCall::PolkadotXcm(..)
- | RuntimeCall::CumulusXcm(..)
- | RuntimeCall::XcAssetConfig(..)
- // Skip entire EVM pallet
- // Skip entire Ethereum pallet
- | RuntimeCall::DynamicEvmBaseFee(..) // Skip entire Contracts pallet
)
}
// All Runtime calls from Pallet Balances allowed for proxy account
diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs
index c6165decf..a37b1201c 100644
--- a/runtime/shiden/src/lib.rs
+++ b/runtime/shiden/src/lib.rs
@@ -80,7 +80,7 @@ use astar_primitives::{
AccountCheck as DappStakingAccountCheck, CycleConfiguration, DAppId, EraNumber,
PeriodNumber, RankedTier, SmartContract, TierSlots as TierSlotsFunc,
},
- evm::EvmRevertCodeHandler,
+ evm::{EVMFungibleAdapterWrapper, EvmRevertCodeHandler},
governance::OracleMembershipInst,
oracle::{CurrencyAmount, CurrencyId, DummyCombineData, Price},
xcm::AssetLocationIdConverter,
@@ -462,7 +462,7 @@ impl pallet_inflation::PayoutPerBlock> for Inflation
}
fn collators(reward: Credit) {
- ToStakingPot::on_unbalanced(reward);
+ CollatorRewardPot::on_unbalanced(reward);
}
}
@@ -601,8 +601,8 @@ parameter_types! {
pub TreasuryAccountId: AccountId = TreasuryPalletId::get().into_account_truncating();
}
-pub struct ToStakingPot;
-impl OnUnbalanced> for ToStakingPot {
+pub struct CollatorRewardPot;
+impl OnUnbalanced> for CollatorRewardPot {
fn on_nonzero_unbalanced(amount: Credit) {
let staking_pot = PotId::get().into_account_truncating();
let _ = Balances::resolve(&staking_pot, amount);
@@ -813,9 +813,13 @@ impl OnUnbalanced> for DealWithFees {
drop(to_burn);
// pay fees to collator
- >::on_unbalanced(collator);
+ >::on_unbalanced(collator);
}
}
+
+ fn on_unbalanced(amount: Credit) {
+ Self::on_unbalanceds(Some(amount).into_iter());
+ }
}
impl pallet_transaction_payment::Config for Runtime {
@@ -920,7 +924,7 @@ impl pallet_evm::Config for Runtime {
type PrecompilesType = Precompiles;
type PrecompilesValue = PrecompilesValue;
type ChainId = ChainId;
- type OnChargeTransaction = pallet_evm::EVMFungibleAdapter;
+ type OnChargeTransaction = EVMFungibleAdapterWrapper;
type BlockGasLimit = BlockGasLimit;
type Timestamp = Timestamp;
type OnCreate = ();
@@ -1007,26 +1011,14 @@ impl InstanceFilter for ProxyType {
c,
RuntimeCall::System(..)
| RuntimeCall::Identity(..)
- | RuntimeCall::Timestamp(..)
| RuntimeCall::Multisig(..)
| RuntimeCall::Proxy(..)
- | RuntimeCall::ParachainSystem(..)
- | RuntimeCall::ParachainInfo(..)
- // Skip entire Balances pallet
- | RuntimeCall::Vesting(pallet_vesting::Call::vest{..})
- | RuntimeCall::Vesting(pallet_vesting::Call::vest_other{..})
- // Specifically omitting Vesting `vested_transfer`, and `force_vested_transfer`
+ | RuntimeCall::Vesting(
+ pallet_vesting::Call::vest { .. }
+ | pallet_vesting::Call::vest_other { .. }
+ )
| RuntimeCall::DappStaking(..)
- // Skip entire Assets pallet
| RuntimeCall::CollatorSelection(..)
- | RuntimeCall::Session(..)
- | RuntimeCall::XcmpQueue(..)
- | RuntimeCall::PolkadotXcm(..)
- | RuntimeCall::CumulusXcm(..)
- | RuntimeCall::XcAssetConfig(..)
- // Skip entire EVM pallet
- // Skip entire Ethereum pallet
- | RuntimeCall::DynamicEvmBaseFee(..) // Skip entire Contracts pallet
)
}
ProxyType::Balances => {
diff --git a/tests/integration/src/fees.rs b/tests/integration/src/fees.rs
new file mode 100644
index 000000000..0befc3a6a
--- /dev/null
+++ b/tests/integration/src/fees.rs
@@ -0,0 +1,86 @@
+// This file is part of Astar.
+
+// Copyright (C) Stake Technologies Pte.Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+// Astar 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.
+
+// Astar 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 Astar. If not, see .
+
+use crate::setup::*;
+
+use frame_support::traits::Currency;
+use pallet_evm::{AddressMapping, OnChargeEVMTransaction};
+use sp_core::{H160, U256};
+use sp_runtime::traits::AccountIdConversion;
+
+#[test]
+fn evm_fees_work() {
+ new_test_ext().execute_with(|| {
+ let address = H160::repeat_byte(0xbe);
+ let mapped_address =
+ ::AddressMapping::into_account_id(address);
+ Balances::make_free_balance_be(&mapped_address, 1_000_000_000_000_000);
+
+ type EvmFeeHandler = ::OnChargeTransaction;
+
+ // 0. Define init values
+ let (base_fee, tip, init_fee) = (500, 100, 1000);
+ let corrected_fee = base_fee + tip;
+
+ let pot_account = PotId::get().into_account_truncating();
+ let init_reward_pot = Balances::free_balance(&pot_account);
+ let init_total_issuance = Balances::total_issuance();
+
+ // 1. Withdraw some init fee
+ let result = >::withdraw_fee(
+ &address,
+ U256::from(init_fee),
+ );
+ let already_withdrawn = result.expect("Account is funded, must succeed.");
+
+ // 2. Correct the charged fee
+ let calculated_tip =
+ >::correct_and_deposit_fee(
+ &address,
+ U256::from(corrected_fee),
+ U256::from(base_fee),
+ already_withdrawn,
+ );
+ assert!(calculated_tip.is_some());
+
+ // The expectation is that 20% of the fee was deposited into the reward pot, and the rest was burned.
+ assert_eq!(
+ init_reward_pot + base_fee / 5,
+ Balances::free_balance(&pot_account)
+ );
+ assert_eq!(
+ init_total_issuance - base_fee / 5 * 4,
+ Balances::total_issuance()
+ );
+
+ // 3. Deposit the tip
+ let issuance = Balances::total_issuance();
+ let pot = Balances::free_balance(&pot_account);
+ >::pay_priority_fee(calculated_tip);
+ assert_eq!(
+ issuance,
+ Balances::total_issuance(),
+ "Total issuance should not change since tip isn't burned."
+ );
+ assert_eq!(
+ pot + tip,
+ Balances::free_balance(&pot_account),
+ "Pot should increase by the tip amount."
+ );
+ })
+}
diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs
index f4dfb40e8..198888dac 100644
--- a/tests/integration/src/lib.rs
+++ b/tests/integration/src/lib.rs
@@ -52,3 +52,6 @@ mod governance;
#[cfg(any(feature = "shibuya", feature = "shiden", feature = "astar"))]
mod xcm_api;
+
+#[cfg(any(feature = "shibuya", feature = "shiden", feature = "astar"))]
+mod fees;