difftreelog
fix evm fees burn & test (#1079)
in: master
* fix evm fees burn & test * fix tips handling
3 files changed
js-packages/tests/creditFeesToTreasury.seqtest.tsdiffbeforeafterboth--- a/js-packages/tests/creditFeesToTreasury.seqtest.ts
+++ b/js-packages/tests/creditFeesToTreasury.seqtest.ts
@@ -18,6 +18,8 @@
import {ApiPromise} from '@polkadot/api';
import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js';
import type {u32} from '@polkadot/types-codec';
+import { itEth } from '@unique/test-utils/eth/util.js';
+import { ITransactionResult } from '@unique-nft/playgrounds/types';
const TREASURY = '5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z';
const saneMinimumFee = 0.05;
@@ -163,4 +165,36 @@
expect(Math.abs(fee - expectedTransferFee)).to.be.lessThan(tolerance);
});
+
+ itEth('Evm Transactions send fees to Treasury', async ({helper}) => {
+ const value = helper.balance.getOneTokenNominal();
+ const gasPrice = await helper.getWeb3().eth.getGasPrice();
+ let result = null;
+
+ const lambda = async () => {
+ result = await helper.executeExtrinsic(alice, 'api.tx.evm.call', [
+ helper.address.substrateToEth(alice.address),
+ helper.address.substrateToEth(bob.address),
+ '0x',
+ value,
+ 25_000,
+ gasPrice,
+ null,
+ null,
+ [],
+ ]);
+ };
+
+ const totalPaid = await helper.arrange.calculcateFee({ Substrate: alice.address }, lambda);
+ const evmFees = totalPaid - value;
+
+ const treasuryDepoosited = (result as unknown as ITransactionResult).result.events
+ .filter(({ event: { method, section } }) => section == 'treasury' && method == 'Deposit')
+ .map(({ event: { data } }) => data[0].toJSON());
+
+ const deposit = BigInt(treasuryDepoosited[0]);
+
+ expect(deposit).to.be.equal(evmFees);
+ });
+
});
pallets/evm-transaction-payment/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![doc = include_str!("../README.md")]18#![cfg_attr(not(feature = "std"), no_std)]19#![deny(missing_docs)]2021use core::marker::PhantomData;2223use fp_evm::{CheckEvmTransaction, FeeCalculator, TransactionValidationError, WithdrawReason};24use frame_support::{25 storage::with_transaction,26 traits::{Currency, Imbalance, IsSubType, OnUnbalanced},27};28pub use pallet::*;29use pallet_evm::{30 account::CrossAccountId, EnsureAddressOrigin, NegativeImbalanceOf, OnChargeEVMTransaction,31 OnCheckEvmTransaction,32};33use sp_core::{H160, U256};34use sp_runtime::{traits::UniqueSaturatedInto, DispatchError, TransactionOutcome};35use up_sponsorship::SponsorshipHandler;3637#[frame_support::pallet]38pub mod pallet {39 use sp_std::vec::Vec;4041 use super::*;4243 /// Contains call data44 pub struct CallContext {45 /// Contract address46 pub contract_address: H160,47 /// Transaction data48 pub input: Vec<u8>,49 /// Max fee for transaction - gasLimit * gasPrice50 pub max_fee: U256,51 }5253 #[pallet::config]54 pub trait Config: frame_system::Config + pallet_evm::Config {55 /// Loosly-coupled handlers for evm call sponsoring56 type EvmSponsorshipHandler: SponsorshipHandler<Self::CrossAccountId, CallContext>;57 }5859 #[pallet::pallet]60 pub struct Pallet<T>(_);61}6263fn who_pays_fee<T: Config>(64 origin: H160,65 max_fee: U256,66 reason: &WithdrawReason,67) -> Option<T::CrossAccountId> {68 match reason {69 WithdrawReason::Call { target, input, .. } => {70 let origin_sub = T::CrossAccountId::from_eth(origin);71 let call_context = CallContext {72 contract_address: *target,73 input: input.clone(),74 max_fee,75 };76 T::EvmSponsorshipHandler::get_sponsor(&origin_sub, &call_context)77 }78 _ => None,79 }80}8182fn get_sponsor<T: Config>(83 source: H160,84 max_fee_per_gas: Option<U256>,85 gas_limit: u64,86 reason: &WithdrawReason,87 is_transactional: bool,88 is_check: bool,89) -> Option<T::CrossAccountId> {90 let accept_gas_fee = |gas_fee| {91 let (base_fee, _) = T::FeeCalculator::min_gas_price();92 // Metamask specifies base fee twice as much as chain reported minGasPrice93 // But we allow further leeway (why?), sponsored base_fee to be 2.1*minGasPrice, thus 21/10.94 base_fee <= gas_fee && gas_fee <= base_fee * 21 / 1095 };96 let (max_fee_per_gas, may_sponsor) = match (max_fee_per_gas, is_transactional) {97 (Some(max_fee_per_gas), _) => (max_fee_per_gas, accept_gas_fee(max_fee_per_gas)),98 // Gas price check is skipped for non-transactional calls that don't99 // define a `max_fee_per_gas` input.100 (None, false) => (Default::default(), true),101 _ => return None,102 };103104 let max_fee = max_fee_per_gas.saturating_mul(gas_limit.into());105106 // #[cfg(feature = "debug-logging")]107 // log::trace!(target: "sponsoring", "checking who will pay fee for {:?} {:?}", source, reason);108 with_transaction(|| {109 let result = may_sponsor110 .then(|| who_pays_fee::<T>(source, max_fee, reason))111 .flatten();112 if is_check {113 TransactionOutcome::Rollback(Ok::<_, DispatchError>(result))114 } else {115 TransactionOutcome::Commit(Ok(result))116 }117 })118 .ok()119 .flatten()120}121/// Implements sponsoring for evm calls performed from pallet-evm (via api.tx.ethereum.transact/api.tx.evm.call)122pub struct BridgeSponsorshipHandler<T>(PhantomData<T>);123impl<T, C> SponsorshipHandler<T::AccountId, C> for BridgeSponsorshipHandler<T>124where125 T: Config + pallet_evm::Config,126 C: IsSubType<pallet_evm::Call<T>>,127{128 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {129 match call.is_sub_type()? {130 pallet_evm::Call::call {131 source,132 target,133 input,134 gas_limit,135 max_fee_per_gas,136 ..137 } => {138 let _ = T::CallOrigin::ensure_address_origin(139 source,140 <frame_system::RawOrigin<T::AccountId>>::Signed(who.clone()).into(),141 )142 .ok()?;143 let who = T::CrossAccountId::from_sub(who.clone());144 let max_fee = max_fee_per_gas.saturating_mul((*gas_limit).into());145 let call_context = CallContext {146 contract_address: *target,147 input: input.clone(),148 max_fee,149 };150 // Effects from EvmSponsorshipHandler are applied by pallet_evm::runner151 // TODO: Should we implement simulation mode (test, but do not apply effects) in `up-sponsorship`?152 let sponsor = frame_support::storage::with_transaction(|| {153 TransactionOutcome::Rollback(Ok::<_, DispatchError>(154 T::EvmSponsorshipHandler::get_sponsor(&who, &call_context),155 ))156 })157 // FIXME: it may fail with DispatchError in case of depth limit158 .ok()??;159 Some(sponsor.as_sub().clone())160 }161 _ => None,162 }163 }164}165166/// Set transaction sponsor if available and enough balance.167pub struct TransactionValidity<T>(PhantomData<T>);168impl<T: Config> OnCheckEvmTransaction<T> for TransactionValidity<T> {169 fn on_check_evm_transaction(170 v: &mut CheckEvmTransaction,171 origin: &T::CrossAccountId,172 ) -> Result<(), TransactionValidationError> {173 let who = &v.who;174 let max_fee_per_gas = v.transaction_fee_input()?.0;175 let gas_limit = v.transaction.gas_limit.low_u64();176 let reason = if let Some(to) = v.transaction.to {177 WithdrawReason::Call {178 target: to,179 input: v.transaction.input.clone(),180 max_fee_per_gas: Some(max_fee_per_gas),181 gas_limit,182 is_transactional: v.config.is_transactional,183 is_check: true,184 }185 } else {186 WithdrawReason::Create187 };188 let sponsor = get_sponsor::<T>(189 *origin.as_eth(),190 Some(max_fee_per_gas),191 gas_limit,192 &reason,193 v.config.is_transactional,194 true,195 )196 .as_ref()197 .map(pallet_evm::Pallet::<T>::account_basic_by_id)198 .map(|v| v.0);199 let fee = max_fee_per_gas.saturating_mul(v.transaction.gas_limit);200 if let Some(sponsor) = sponsor.as_ref() {201 if who.balance < v.transaction.value || sponsor.balance < fee {202 return Err(TransactionValidationError::BalanceTooLow);203 }204 } else {205 let total_payment = v.transaction.value.saturating_add(fee);206 if who.balance < total_payment {207 return Err(TransactionValidationError::BalanceTooLow);208 }209 }210211 let who = sponsor.unwrap_or_else(|| v.who.clone());212 v.who.balance = who.balance;213 Ok(())214 }215}216217/// Implements the transaction payment for a pallet implementing the `Currency`218/// trait (eg. the pallet_balances) using an unbalance handler (implementing219/// `OnUnbalanced`).220/// Similar to `CurrencyAdapter` of `pallet_transaction_payment`221pub struct WrappedEVMCurrencyAdapter<C, OU>(sp_std::marker::PhantomData<(C, OU)>);222impl<T, C, OU> OnChargeEVMTransaction<T> for WrappedEVMCurrencyAdapter<C, OU>223where224 T: Config,225 C: Currency<<T as frame_system::Config>::AccountId>,226 C::PositiveImbalance: Imbalance<227 <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,228 Opposite = C::NegativeImbalance,229 >,230 C::NegativeImbalance: Imbalance<231 <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,232 Opposite = C::PositiveImbalance,233 >,234 OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,235 U256: UniqueSaturatedInto<<C as Currency<<T as frame_system::Config>::AccountId>>::Balance>,236{237 // Kept type as Option to satisfy bound of Default238 type LiquidityInfo = (Option<NegativeImbalanceOf<C, T>>, Option<T::CrossAccountId>);239240 fn withdraw_fee(241 who: &T::CrossAccountId,242 reason: WithdrawReason,243 fee: U256,244 ) -> Result<Self::LiquidityInfo, pallet_evm::Error<T>> {245 let sponsor = match reason {246 WithdrawReason::Call {247 max_fee_per_gas,248 gas_limit,249 is_transactional,250 is_check,251 ..252 } => get_sponsor::<T>(253 *who.as_eth(),254 max_fee_per_gas,255 gas_limit,256 &reason,257 is_transactional,258 is_check,259 ),260 _ => None,261 };262263 let who = sponsor.as_ref().unwrap_or(who);264 <pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::withdraw_fee(265 who, reason, fee,266 )267 .map(|li| (li, sponsor))268 }269270 fn correct_and_deposit_fee(271 who: &T::CrossAccountId,272 corrected_fee: U256,273 base_fee: U256,274 already_withdrawn: Self::LiquidityInfo,275 ) -> Self::LiquidityInfo {276 let (already_withdrawn, sponsor) = already_withdrawn;277 let who = sponsor.as_ref().unwrap_or(who);278 (279 <pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::correct_and_deposit_fee(280 who,281 corrected_fee,282 base_fee,283 already_withdrawn,284 ),285 None286 )287 }288289 fn pay_priority_fee(tip: Self::LiquidityInfo) {290 <pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::pay_priority_fee(291 tip.0,292 )293 }294}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![doc = include_str!("../README.md")]18#![cfg_attr(not(feature = "std"), no_std)]19#![deny(missing_docs)]2021use core::marker::PhantomData;2223use fp_evm::{CheckEvmTransaction, FeeCalculator, TransactionValidationError, WithdrawReason};24use frame_support::{25 storage::with_transaction,26 traits::{Currency, Imbalance, IsSubType, OnUnbalanced},27};28pub use pallet::*;29use pallet_evm::{30 account::CrossAccountId, EnsureAddressOrigin, NegativeImbalanceOf, OnChargeEVMTransaction,31 OnCheckEvmTransaction,32};33use sp_core::{H160, U256};34use sp_runtime::{traits::UniqueSaturatedInto, DispatchError, TransactionOutcome};35use up_sponsorship::SponsorshipHandler;3637#[frame_support::pallet]38pub mod pallet {39 use sp_std::vec::Vec;4041 use super::*;4243 /// Contains call data44 pub struct CallContext {45 /// Contract address46 pub contract_address: H160,47 /// Transaction data48 pub input: Vec<u8>,49 /// Max fee for transaction - gasLimit * gasPrice50 pub max_fee: U256,51 }5253 #[pallet::config]54 pub trait Config: frame_system::Config + pallet_evm::Config {55 /// Loosly-coupled handlers for evm call sponsoring56 type EvmSponsorshipHandler: SponsorshipHandler<Self::CrossAccountId, CallContext>;57 }5859 #[pallet::pallet]60 pub struct Pallet<T>(_);61}6263fn who_pays_fee<T: Config>(64 origin: H160,65 max_fee: U256,66 reason: &WithdrawReason,67) -> Option<T::CrossAccountId> {68 match reason {69 WithdrawReason::Call { target, input, .. } => {70 let origin_sub = T::CrossAccountId::from_eth(origin);71 let call_context = CallContext {72 contract_address: *target,73 input: input.clone(),74 max_fee,75 };76 T::EvmSponsorshipHandler::get_sponsor(&origin_sub, &call_context)77 }78 _ => None,79 }80}8182fn get_sponsor<T: Config>(83 source: H160,84 max_fee_per_gas: Option<U256>,85 gas_limit: u64,86 reason: &WithdrawReason,87 is_transactional: bool,88 is_check: bool,89) -> Option<T::CrossAccountId> {90 let accept_gas_fee = |gas_fee| {91 let (base_fee, _) = T::FeeCalculator::min_gas_price();92 // Metamask specifies base fee twice as much as chain reported minGasPrice93 // But we allow further leeway (why?), sponsored base_fee to be 2.1*minGasPrice, thus 21/10.94 base_fee <= gas_fee && gas_fee <= base_fee * 21 / 1095 };96 let (max_fee_per_gas, may_sponsor) = match (max_fee_per_gas, is_transactional) {97 (Some(max_fee_per_gas), _) => (max_fee_per_gas, accept_gas_fee(max_fee_per_gas)),98 // Gas price check is skipped for non-transactional calls that don't99 // define a `max_fee_per_gas` input.100 (None, false) => (Default::default(), true),101 _ => return None,102 };103104 let max_fee = max_fee_per_gas.saturating_mul(gas_limit.into());105106 // #[cfg(feature = "debug-logging")]107 // log::trace!(target: "sponsoring", "checking who will pay fee for {:?} {:?}", source, reason);108 with_transaction(|| {109 let result = may_sponsor110 .then(|| who_pays_fee::<T>(source, max_fee, reason))111 .flatten();112 if is_check {113 TransactionOutcome::Rollback(Ok::<_, DispatchError>(result))114 } else {115 TransactionOutcome::Commit(Ok(result))116 }117 })118 .ok()119 .flatten()120}121/// Implements sponsoring for evm calls performed from pallet-evm (via api.tx.ethereum.transact/api.tx.evm.call)122pub struct BridgeSponsorshipHandler<T>(PhantomData<T>);123impl<T, C> SponsorshipHandler<T::AccountId, C> for BridgeSponsorshipHandler<T>124where125 T: Config + pallet_evm::Config,126 C: IsSubType<pallet_evm::Call<T>>,127{128 fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {129 match call.is_sub_type()? {130 pallet_evm::Call::call {131 source,132 target,133 input,134 gas_limit,135 max_fee_per_gas,136 ..137 } => {138 let _ = T::CallOrigin::ensure_address_origin(139 source,140 <frame_system::RawOrigin<T::AccountId>>::Signed(who.clone()).into(),141 )142 .ok()?;143 let who = T::CrossAccountId::from_sub(who.clone());144 let max_fee = max_fee_per_gas.saturating_mul((*gas_limit).into());145 let call_context = CallContext {146 contract_address: *target,147 input: input.clone(),148 max_fee,149 };150 // Effects from EvmSponsorshipHandler are applied by pallet_evm::runner151 // TODO: Should we implement simulation mode (test, but do not apply effects) in `up-sponsorship`?152 let sponsor = frame_support::storage::with_transaction(|| {153 TransactionOutcome::Rollback(Ok::<_, DispatchError>(154 T::EvmSponsorshipHandler::get_sponsor(&who, &call_context),155 ))156 })157 // FIXME: it may fail with DispatchError in case of depth limit158 .ok()??;159 Some(sponsor.as_sub().clone())160 }161 _ => None,162 }163 }164}165166/// Set transaction sponsor if available and enough balance.167pub struct TransactionValidity<T>(PhantomData<T>);168impl<T: Config> OnCheckEvmTransaction<T> for TransactionValidity<T> {169 fn on_check_evm_transaction(170 v: &mut CheckEvmTransaction,171 origin: &T::CrossAccountId,172 ) -> Result<(), TransactionValidationError> {173 let who = &v.who;174 let max_fee_per_gas = v.transaction_fee_input()?.0;175 let gas_limit = v.transaction.gas_limit.low_u64();176 let reason = if let Some(to) = v.transaction.to {177 WithdrawReason::Call {178 target: to,179 input: v.transaction.input.clone(),180 max_fee_per_gas: Some(max_fee_per_gas),181 gas_limit,182 is_transactional: v.config.is_transactional,183 is_check: true,184 }185 } else {186 WithdrawReason::Create187 };188 let sponsor = get_sponsor::<T>(189 *origin.as_eth(),190 Some(max_fee_per_gas),191 gas_limit,192 &reason,193 v.config.is_transactional,194 true,195 )196 .as_ref()197 .map(pallet_evm::Pallet::<T>::account_basic_by_id)198 .map(|v| v.0);199 let fee = max_fee_per_gas.saturating_mul(v.transaction.gas_limit);200 if let Some(sponsor) = sponsor.as_ref() {201 if who.balance < v.transaction.value || sponsor.balance < fee {202 return Err(TransactionValidationError::BalanceTooLow);203 }204 } else {205 let total_payment = v.transaction.value.saturating_add(fee);206 if who.balance < total_payment {207 return Err(TransactionValidationError::BalanceTooLow);208 }209 }210211 let who = sponsor.unwrap_or_else(|| v.who.clone());212 v.who.balance = who.balance;213 Ok(())214 }215}216217/// Implements the transaction payment for a pallet implementing the `Currency`218/// trait (eg. the pallet_balances) using an unbalance handler (implementing219/// `OnUnbalanced`).220/// Similar to `CurrencyAdapter` of `pallet_transaction_payment`221pub struct WrappedEVMCurrencyAdapter<C, OU>(sp_std::marker::PhantomData<(C, OU)>);222impl<T, C, OU> OnChargeEVMTransaction<T> for WrappedEVMCurrencyAdapter<C, OU>223where224 T: Config,225 C: Currency<<T as frame_system::Config>::AccountId>,226 C::PositiveImbalance: Imbalance<227 <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,228 Opposite = C::NegativeImbalance,229 >,230 C::NegativeImbalance: Imbalance<231 <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,232 Opposite = C::PositiveImbalance,233 >,234 OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,235 U256: UniqueSaturatedInto<<C as Currency<<T as frame_system::Config>::AccountId>>::Balance>,236{237 // Kept type as Option to satisfy bound of Default238 type LiquidityInfo = (Option<NegativeImbalanceOf<C, T>>, Option<T::CrossAccountId>);239240 fn withdraw_fee(241 who: &T::CrossAccountId,242 reason: WithdrawReason,243 fee: U256,244 ) -> Result<Self::LiquidityInfo, pallet_evm::Error<T>> {245 let sponsor = match reason {246 WithdrawReason::Call {247 max_fee_per_gas,248 gas_limit,249 is_transactional,250 is_check,251 ..252 } => get_sponsor::<T>(253 *who.as_eth(),254 max_fee_per_gas,255 gas_limit,256 &reason,257 is_transactional,258 is_check,259 ),260 _ => None,261 };262263 let who = sponsor.as_ref().unwrap_or(who);264 <pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::withdraw_fee(265 who, reason, fee,266 )267 .map(|li| (li, sponsor))268 }269270 fn correct_and_deposit_fee(271 who: &T::CrossAccountId,272 corrected_fee: U256,273 base_fee: U256,274 already_withdrawn: Self::LiquidityInfo,275 ) -> Self::LiquidityInfo {276 let (already_withdrawn, sponsor) = already_withdrawn;277 let who = sponsor.as_ref().unwrap_or(who);278 (279 <pallet_evm::EVMCurrencyAdapter<C, OU> as OnChargeEVMTransaction<T>>::correct_and_deposit_fee(280 who,281 corrected_fee,282 base_fee,283 already_withdrawn,284 ),285 None286 )287 }288289 fn pay_priority_fee(tip: Self::LiquidityInfo) {290 let Some(imbalance) = tip.0 else { return };291 OU::on_unbalanced(imbalance)292 }293}runtime/common/mod.rsdiffbeforeafterboth--- a/runtime/common/mod.rs
+++ b/runtime/common/mod.rs
@@ -107,21 +107,7 @@
type NegativeImbalance = <Balances as Currency<AccountId>>::NegativeImbalance;
-pub struct DealWithFees;
-impl OnUnbalanced<NegativeImbalance> for DealWithFees {
- fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
- if let Some(fees) = fees_then_tips.next() {
- // for fees, 100% to treasury
- let mut split = fees.ration(100, 0);
- if let Some(tips) = fees_then_tips.next() {
- // for tips, if any, 100% to treasury
- tips.ration_merge_into(100, 0, &mut split);
- }
- Treasury::on_unbalanced(split.0);
- // Author::on_unbalanced(split.1);
- }
- }
-}
+pub(crate) type DealWithFees = Treasury;
pub struct RelayChainBlockNumberProvider<T>(sp_std::marker::PhantomData<T>);