diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index cc53c2c616a82..7ef4c44e4d18a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -89,7 +89,7 @@ use sp_runtime::{ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use testnet_parachains_constants::westend::{ - consensus::*, currency::*, fee::WeightToFee, snowbridge::EthereumNetwork, time::*, + consensus::*, currency::*, snowbridge::EthereumNetwork, time::*, }; use westend_runtime_constants::time::DAYS as RC_DAYS; use xcm_config::{ @@ -247,6 +247,18 @@ parameter_types! { pub const TransactionByteFee: Balance = MILLICENTS; } +/// `pallet_revive` requires this specific `WeightToFee` implementation. +/// +/// This is needed because we make certain assumptions about how weight +/// is mapped to fees. Enforced at compile time. +type WeightToFee = pallet_revive::evm::fees::BlockRatioFee< + // p + CENTS, + // q + { 100 * ExtrinsicBaseWeight::get().ref_time() as u128 }, + Runtime, +>; + impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = @@ -1169,7 +1181,6 @@ impl pallet_revive::Config for Runtime { type RuntimeCall = RuntimeCall; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; - type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_revive::weights::SubstrateWeight; type Precompiles = ( ERC20, TrustBackedAssetsInstance>, @@ -1190,9 +1201,8 @@ impl pallet_revive::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type ChainId = ConstU64<420_420_421>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. - type EthGasEncoder = (); type FindAuthor = ::FindAuthor; - type DepositSource = (); + type FeeInfo = pallet_revive::evm::fees::Info; } parameter_types! { diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 6d3898696d567..500a8b18b59c8 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -441,7 +441,7 @@ parameter_types! { impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; - type WeightToFee = WeightToFee; + type WeightToFee = pallet_revive::evm::fees::BlockRatioFee<1, 1, Self>; type LengthToFee = ConstantMultiplier; type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; type OperationalFeeMultiplier = ConstU8<5>; @@ -816,7 +816,6 @@ impl pallet_revive::Config for Runtime { type RuntimeCall = RuntimeCall; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; - type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_revive::weights::SubstrateWeight; type Precompiles = (); type AddressMapper = pallet_revive::AccountId32Mapper; @@ -830,9 +829,8 @@ impl pallet_revive::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type ChainId = ConstU64<420_420_999>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. - type EthGasEncoder = (); type FindAuthor = ::FindAuthor; - type DepositSource = (); + type FeeInfo = pallet_revive::evm::fees::Info; } impl pallet_sudo::Config for Runtime { @@ -908,7 +906,11 @@ mod benches { ); } -impl_runtime_apis! { +pallet_revive::impl_runtime_apis_plus_revive!( + Runtime, + Executive, + EthExtraImpl, + impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) @@ -1213,7 +1215,7 @@ impl_runtime_apis! { ConsensusHook::can_build_upon(included_hash, slot) } } -} +); cumulus_pallet_parachain_system::register_validate_block! { Runtime = Runtime, diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 62a632a592971..ef267fb599a1d 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -66,7 +66,7 @@ use frame_support::{ constants::{ BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, }, - ConstantMultiplier, IdentityFee, Weight, + ConstantMultiplier, Weight, }, BoundedVec, PalletId, }; @@ -614,7 +614,7 @@ impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OnChargeTransaction = CurrencyAdapter; type OperationalFeeMultiplier = OperationalFeeMultiplier; - type WeightToFee = IdentityFee; + type WeightToFee = pallet_revive::evm::fees::BlockRatioFee<1, 1, Self>; type LengthToFee = ConstantMultiplier; type FeeMultiplierUpdate = TargetedFeeAdjustment< Self, @@ -1475,7 +1475,6 @@ impl pallet_revive::Config for Runtime { type RuntimeCall = RuntimeCall; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; - type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_revive::weights::SubstrateWeight; type Precompiles = (ERC20, Instance1>, ERC20, Instance2>); @@ -1489,10 +1488,9 @@ impl pallet_revive::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. - type EthGasEncoder = (); type FindAuthor = ::FindAuthor; type AllowEVMBytecode = ConstBool; - type DepositSource = (); + type FeeInfo = pallet_revive::evm::fees::Info; } impl pallet_sudo::Config for Runtime { diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index 3b5f144689e86..690e890dae5f2 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -19,6 +19,7 @@ polkadot-sdk = { workspace = true, features = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "parachains-common", + "polkadot-runtime-common", "runtime", "with-tracing", ] } diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index fcfc12afc66e7..13bf408adf3fa 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -30,7 +30,13 @@ use frame_support::weights::{ Weight, }; use frame_system::limits::BlockWeights; -use pallet_revive::{evm::runtime::EthExtra, AccountId32Mapper}; +use pallet_revive::{ + evm::{ + fees::{BlockRatioFee, Info as FeeInfo}, + runtime::EthExtra, + }, + AccountId32Mapper, +}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use polkadot_sdk::{ polkadot_sdk_frame::{ @@ -39,7 +45,7 @@ use polkadot_sdk::{ }, *, }; -use sp_weights::{ConstantMultiplier, IdentityFee}; +use sp_weights::ConstantMultiplier; pub use polkadot_sdk::{ parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}, @@ -48,9 +54,9 @@ pub use polkadot_sdk::{ pub mod currency { use super::Balance; - pub const MILLICENTS: Balance = 1_000_000_000; - pub const CENTS: Balance = 1_000 * MILLICENTS; - pub const DOLLARS: Balance = 100 * CENTS; + pub const DOLLARS: Balance = 1_000_000_000_000; + pub const CENTS: Balance = DOLLARS / 100; + pub const MILLICENTS: Balance = CENTS / 1_000; } /// Provides getters for genesis configuration presets. @@ -284,7 +290,7 @@ impl frame_system::Config for Runtime { } parameter_types! { - pub const ExistentialDeposit: Balance = DOLLARS; + pub const ExistentialDeposit: Balance = CENTS; } // Implements the types required for the balances pallet. @@ -311,8 +317,9 @@ parameter_types! { #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for Runtime { type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; - type WeightToFee = IdentityFee; + type WeightToFee = BlockRatioFee<1, 1, Self>; type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = polkadot_sdk::polkadot_runtime_common::SlowAdjustingFeeUpdate; } parameter_types! { @@ -329,6 +336,7 @@ impl pallet_revive::Config for Runtime { type UploadOrigin = EnsureSigned; type InstantiateOrigin = EnsureSigned; type Time = Timestamp; + type FeeInfo = FeeInfo; } pallet_revive::impl_runtime_apis_plus_revive!( diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index f97003b65816e..324bbfddd86de 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -183,7 +183,7 @@ impl TransactionBuilder { .with_context(|| "send_raw_transaction failed")?; Ok(SubmittedTransaction { - tx: GenericTransaction::from_signed(signed_tx, gas_price, Some(from)), + tx: GenericTransaction::from_signed(signed_tx, Some(from)), hash, client, }) diff --git a/substrate/frame/revive/rpc/src/receipt_extractor.rs b/substrate/frame/revive/rpc/src/receipt_extractor.rs index 0e5273d4f7501..100ff226c948e 100644 --- a/substrate/frame/revive/rpc/src/receipt_extractor.rs +++ b/substrate/frame/revive/rpc/src/receipt_extractor.rs @@ -122,9 +122,8 @@ impl ReceiptExtractor { })?; let base_gas_price = (self.fetch_gas_price)(block_hash).await?; - let tx_info = - GenericTransaction::from_signed(signed_tx.clone(), base_gas_price, Some(from)); - let gas_price = tx_info.gas_price.unwrap_or_default(); + let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from)); + let gas_price = tx_info.effective_gas_price(base_gas_price).unwrap_or_default(); let gas_used = U256::from(tx_fees.tip.saturating_add(tx_fees.actual_fee)) .saturating_mul(self.native_to_eth_ratio.into()) .checked_div(gas_price) diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index 69293bdf96e77..5c7a83f8fb2f7 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -20,7 +20,6 @@ #![cfg(feature = "runtime-benchmarks")] use crate::{ call_builder::{caller_funding, default_deposit_limit, CallSetup, Contract, VmBinaryModule}, - evm::runtime::GAS_PRICE, exec::{Key, MomentOf, PrecompileExt}, limits, precompiles::{ @@ -299,13 +298,13 @@ mod benchmarks { assert!(AccountInfoOf::::get(&deployer).is_none()); - let hold_reason = T::DepositSource::get(); + let hold_reason = T::FeeInfo::deposit_source(); if let Some(hold_reason) = hold_reason.as_ref() { T::Currency::set_balance_on_hold(hold_reason, &caller, caller_funding::()).unwrap(); } #[extrinsic_call] - _(origin, evm_value, Weight::MAX, storage_deposit, code, input); + _(origin, evm_value, Weight::MAX, storage_deposit, code, input, 0u32.into()); let deposit = T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id); @@ -453,7 +452,7 @@ mod benchmarks { let before = Pallet::::evm_balance(&instance.address); let storage_deposit = default_deposit_limit::(); #[extrinsic_call] - _(origin, instance.address, evm_value, Weight::MAX, storage_deposit, data); + _(origin, instance.address, evm_value, Weight::MAX, storage_deposit, data, 0u32.into()); let deposit = T::Currency::balance_on_hold( &HoldReason::StorageDepositReserve.into(), &instance.account_id, @@ -951,7 +950,7 @@ mod benchmarks { { result = runtime.bench_gas_price(memory.as_mut_slice()); } - assert_eq!(result.unwrap(), u64::from(GAS_PRICE)); + assert_eq!(U256::from(result.unwrap()), >::evm_gas_price()); } #[benchmark(pov_mode = Measured)] @@ -1064,24 +1063,6 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().now()); } - #[benchmark(pov_mode = Measured)] - fn seal_weight_to_fee() { - build_runtime!(runtime, memory: [[0u8;32], ]); - let weight = Weight::from_parts(500_000, 300_000); - let result; - #[block] - { - result = runtime.bench_weight_to_fee( - memory.as_mut_slice(), - weight.ref_time(), - weight.proof_size(), - 0, - ); - } - assert_ok!(result); - assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().get_weight_price(weight)); - } - #[benchmark(pov_mode = Measured)] fn seal_copy_to_contract(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { let mut setup = CallSetup::::default(); diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index f340474f472e0..5635888a8bd7d 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -21,7 +21,8 @@ mod api; pub use api::*; mod tracing; pub use tracing::*; -mod gas_encoder; -pub use gas_encoder::*; +pub mod fees; pub mod runtime; pub use alloy_core::sol_types::decode_revert_reason; + +type OnChargeTransactionBalanceOf = <::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction>::Balance; diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index 09b9f67271504..768c641d902ed 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -204,17 +204,23 @@ fn logs_bloom_works() { } impl GenericTransaction { + /// The gas price that is actually paid (including priority fee). + pub fn effective_gas_price(&self, base_gas_price: U256) -> Option { + if let Some(prio_price) = self.max_priority_fee_per_gas { + let max_price = self.max_fee_per_gas?; + Some(max_price.min(base_gas_price.saturating_add(prio_price))) + } else { + self.gas_price + } + } + /// Create a new [`GenericTransaction`] from a signed transaction. - pub fn from_signed(tx: TransactionSigned, base_gas_price: U256, from: Option) -> Self { - Self::from_unsigned(tx.into(), base_gas_price, from) + pub fn from_signed(tx: TransactionSigned, from: Option) -> Self { + Self::from_unsigned(tx.into(), from) } /// Create a new [`GenericTransaction`] from a unsigned transaction. - pub fn from_unsigned( - tx: TransactionUnsigned, - base_gas_price: U256, - from: Option, - ) -> Self { + pub fn from_unsigned(tx: TransactionUnsigned, from: Option) -> Self { use TransactionUnsigned::*; match tx { TransactionLegacyUnsigned(tx) => GenericTransaction { @@ -238,11 +244,6 @@ impl GenericTransaction { value: Some(tx.value), to: Some(tx.to), gas: Some(tx.gas), - gas_price: Some( - base_gas_price - .saturating_add(tx.max_priority_fee_per_gas) - .min(tx.max_fee_per_blob_gas), - ), access_list: Some(tx.access_list), blob_versioned_hashes: tx.blob_versioned_hashes, max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), @@ -259,11 +260,6 @@ impl GenericTransaction { value: Some(tx.value), to: tx.to, gas: Some(tx.gas), - gas_price: Some( - base_gas_price - .saturating_add(tx.max_priority_fee_per_gas) - .min(tx.max_fee_per_gas), - ), access_list: Some(tx.access_list), max_fee_per_gas: Some(tx.max_fee_per_gas), max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), @@ -291,11 +287,6 @@ impl GenericTransaction { value: Some(tx.value), to: tx.to, gas: Some(tx.gas), - gas_price: Some( - base_gas_price - .saturating_add(tx.max_priority_fee_per_gas) - .min(tx.max_fee_per_gas), - ), access_list: Some(tx.access_list), authorization_list: tx.authorization_list, max_fee_per_gas: Some(tx.max_fee_per_gas), @@ -327,7 +318,6 @@ impl GenericTransaction { value: self.value.unwrap_or_default(), to: self.to, gas: self.gas.unwrap_or_default(), - gas_price: self.max_fee_per_gas.unwrap_or_default(), access_list: self.access_list.unwrap_or_default(), max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(), max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(), @@ -368,7 +358,6 @@ impl GenericTransaction { value: self.value.unwrap_or_default(), to: self.to, gas: self.gas.unwrap_or_default(), - gas_price: self.max_fee_per_gas.unwrap_or_default(), max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(), max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(), access_list: self.access_list.unwrap_or_default(), @@ -394,8 +383,9 @@ fn from_unsigned_works_for_legacy() { ..Default::default() }); - let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None); + let generic = GenericTransaction::from_unsigned(tx.clone(), None); assert_eq!(generic.gas_price, Some(U256::from(11))); + assert_eq!(generic.effective_gas_price(base_gas_price), Some(U256::from(11))); let tx2 = generic.try_into_unsigned().unwrap(); assert_eq!(tx, tx2); @@ -411,14 +401,14 @@ fn from_unsigned_works_for_1559() { value: U256::from(1), to: Some(H160::zero()), gas: U256::from(1), - gas_price: U256::from(20), max_fee_per_gas: U256::from(20), - max_priority_fee_per_gas: U256::from(1), + max_priority_fee_per_gas: U256::from(30), ..Default::default() }); - let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None); - assert_eq!(generic.gas_price, Some(U256::from(11))); + let generic = GenericTransaction::from_unsigned(tx.clone(), None); + assert_eq!(generic.gas_price, None); + assert_eq!(generic.effective_gas_price(base_gas_price), Some(U256::from(20))); let tx2 = generic.try_into_unsigned().unwrap(); assert_eq!(tx, tx2); @@ -434,7 +424,6 @@ fn from_unsigned_works_for_7702() { value: U256::from(1), to: Some(H160::zero()), gas: U256::from(1), - gas_price: U256::from(20), max_fee_per_gas: U256::from(20), max_priority_fee_per_gas: U256::from(1), authorization_list: vec![AuthorizationListEntry { @@ -448,8 +437,9 @@ fn from_unsigned_works_for_7702() { ..Default::default() }); - let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None); - assert_eq!(generic.gas_price, Some(U256::from(11))); + let generic = GenericTransaction::from_unsigned(tx.clone(), None); + assert_eq!(generic.gas_price, None); + assert_eq!(generic.effective_gas_price(base_gas_price), Some(U256::from(11))); let tx2 = generic.try_into_unsigned().unwrap(); assert_eq!(tx, tx2); diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index 7ca087ff53fe4..18ccbe1e9967f 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -529,11 +529,6 @@ pub struct Transaction1559Unsigned { pub chain_id: U256, /// gas limit pub gas: U256, - /// gas price - /// The effective gas price paid by the sender in wei. For transactions not yet included in a - /// block, this value should be set equal to the max fee per gas. This field is DEPRECATED, - /// please transition to using effectiveGasPrice in the receipt object going forward. - pub gas_price: U256, /// input data pub input: Bytes, /// max fee per gas @@ -659,11 +654,6 @@ pub struct Transaction7702Unsigned { pub chain_id: U256, /// gas limit pub gas: U256, - /// gas price - /// The effective gas price paid by the sender in wei. For transactions not yet included in a - /// block, this value should be set equal to the max fee per gas. This field is DEPRECATED, - /// please transition to using effectiveGasPrice in the receipt object going forward. - pub gas_price: U256, /// input data pub input: Bytes, /// max fee per gas @@ -718,26 +708,6 @@ impl Default for TransactionSigned { } } -impl TransactionSigned { - /// Get the effective gas price. - pub fn effective_gas_price(&self, base_gas_price: U256) -> U256 { - match &self { - TransactionSigned::TransactionLegacySigned(tx) => - tx.transaction_legacy_unsigned.gas_price, - TransactionSigned::Transaction7702Signed(tx) => base_gas_price - .saturating_add(tx.transaction_7702_unsigned.max_priority_fee_per_gas) - .min(tx.transaction_7702_unsigned.max_fee_per_gas), - TransactionSigned::Transaction4844Signed(tx) => base_gas_price - .saturating_add(tx.transaction_4844_unsigned.max_priority_fee_per_gas) - .min(tx.transaction_4844_unsigned.max_fee_per_blob_gas), - TransactionSigned::Transaction1559Signed(tx) => base_gas_price - .saturating_add(tx.transaction_1559_unsigned.max_priority_fee_per_gas) - .min(tx.transaction_1559_unsigned.max_fee_per_gas), - TransactionSigned::Transaction2930Signed(tx) => tx.transaction_2930_unsigned.gas_price, - } - } -} - /// Validator withdrawal #[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] diff --git a/substrate/frame/revive/src/evm/fees.rs b/substrate/frame/revive/src/evm/fees.rs new file mode 100644 index 0000000000000..e1a035832e240 --- /dev/null +++ b/substrate/frame/revive/src/evm/fees.rs @@ -0,0 +1,254 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contains the fee types that need to be configured for `pallet-transaction-payment`. + +use crate::{ + evm::{runtime::EthExtra, OnChargeTransactionBalanceOf}, + BalanceOf, Config, IsType, +}; +use codec::Encode; +use core::marker::PhantomData; +use frame_support::{ + dispatch::{DispatchInfo, GetDispatchInfo}, + pallet_prelude::Weight, + traits::Get, + weights::WeightToFee, +}; +use frame_system::Config as SysConfig; +use num_traits::Zero; +use pallet_transaction_payment::{Config as TxConfig, MultiplierUpdate, NextFeeMultiplier}; +use sp_runtime::{ + generic::UncheckedExtrinsic, + traits::{Block as BlockT, Dispatchable, TransactionExtension, UniqueSaturatedFrom}, + FixedPointNumber, FixedU128, SaturatedConversion, Saturating, +}; + +/// The only [`WeightToFee`] implementation that is supported by this pallet. +/// +/// `P,Q`: Rational number that defines the ref_time to fee mapping. +/// +/// This enforces a ration of ref_time and proof_time that is proportional +/// to their distribution in the block limits. We enforce the usage of this fee +/// structure because our gas mapping depends on it. +/// +/// # Panics +/// +/// If either `P` or `Q` is zero. +pub struct BlockRatioFee(PhantomData); + +/// The only [`InfoT`] implementation valid for [`Config::FeeInfo`]. +/// +/// The reason for this type is to avoid coupling the rest of pallet_revive to +/// pallet_transaction_payment. This way we bundle all the trait bounds in once place. +pub struct Info(PhantomData<(Address, Signature, Extra)>); + +/// A that signals that [`BlockRatioFee`] is used by the runtime. +/// +/// This trait is sealed. Use [`BlockRatioFee`]. +pub trait BlockRatioWeightToFee: seal::Sealed { + /// The runtime. + type T: Config; + /// The ref_time to fee coefficient. + const REF_TIME_TO_FEE: FixedU128; + + /// The proof_size to fee coefficient. + fn proof_size_to_fee() -> FixedU128 { + let max_weight = ::BlockWeights::get().max_block; + let ratio = + FixedU128::from_rational(max_weight.ref_time().into(), max_weight.proof_size().into()); + Self::REF_TIME_TO_FEE.saturating_mul(ratio) + } +} + +/// A trait that exposes all the transaction payment details to `pallet_revive`. +/// +/// This trait is sealed. Use [`Info`]. +pub trait InfoT: seal::Sealed { + /// Check that the fee configuration of the chain is valid. + /// + /// This is being called by the pallets `integrity_check`. + fn integrity_test() {} + + /// Exposes the current fee multiplier of the chain. + fn next_fee_multiplier() -> FixedU128 { + FixedU128::from_rational(1, 1) + } + + /// The reciprocal of the next fee multiplier. + /// + /// Needed when deviding a fee by the multiplier before presenting + /// it to the eth wallet as gas. Needed because the wallet will multiply + /// it with the gas_price which includes this multiplicator. + fn next_fee_multiplier_reciprocal() -> FixedU128 { + Self::next_fee_multiplier() + .reciprocal() + .expect("The minimum multiplier is not 0. We check that in `integrity_test`; qed") + } + + /// Calculate the fee of a transaction without adjusting it using the next fee multiplier. + /// + /// This also devides the length fee and the base fee by the next fee multiplier + /// for presentation to the eth wallet. + fn unadjusted_tx_fee( + _eth_transact_call: ::RuntimeCall, + _dispatch_call: ::RuntimeCall, + ) -> BalanceOf { + Zero::zero() + } + + /// Calculate the fee of a transaction including the next fee multiplier adjustment. + fn tx_fee(_len: u32, _dispatch_info: &DispatchInfo) -> BalanceOf { + Zero::zero() + } + + /// Convert a weight to an unadjusted fee. + fn weight_to_fee(_weight: Weight) -> BalanceOf { + Zero::zero() + } + + /// Convert an unadjusted fee back to a weight. + fn fee_to_weight(_fee: BalanceOf) -> Weight { + Zero::zero() + } + + /// Convert the length of a transaction to an unadjusted weight. + fn length_to_fee(_len: u32) -> BalanceOf { + Zero::zero() + } + + /// The hold that storage deposits are collected from when `eth_*` transactions are used. + fn deposit_source() -> Option { + None + } +} + +impl BlockRatioWeightToFee for BlockRatioFee { + type T = T; + const REF_TIME_TO_FEE: FixedU128 = FixedU128::from_rational(P, Q); +} + +impl WeightToFee for BlockRatioFee { + type Balance = BalanceOf; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + let ref_time_fee = Self::REF_TIME_TO_FEE + .saturating_mul_int(Self::Balance::saturated_from(weight.ref_time())); + let proof_size_fee = Self::proof_size_to_fee() + .saturating_mul_int(Self::Balance::saturated_from(weight.proof_size())); + ref_time_fee.max(proof_size_fee) + } +} + +impl InfoT for Info +where + BalanceOf: From>, + ::RuntimeCall: Dispatchable, + <::Block as BlockT>::Extrinsic: From< + UncheckedExtrinsic::RuntimeCall, Signature, E::Extension>, + >, + ::RuntimeCall: IsType<::RuntimeCall>, + ::RuntimeHoldReason: From, + ::WeightToFee: BlockRatioWeightToFee, + u64: UniqueSaturatedFrom>, +{ + fn integrity_test() { + let min_multiplier = ::FeeMultiplierUpdate::min(); + assert!(!min_multiplier.is_zero(), "The multiplier is never allowed to be zero."); + assert!( + min_multiplier.saturating_mul_int(::NativeToEthRatio::get()) > 0, + "The gas price needs to be greater zero." + ); + assert!( + !::WeightToFee::REF_TIME_TO_FEE.is_zero(), + "ref_time to fee is not allowed to be zero." + ); + assert!( + !::WeightToFee::proof_size_to_fee().is_zero(), + "proof_size to fee is not allowed to be zero." + ); + } + + fn next_fee_multiplier() -> FixedU128 { + >::get() + } + + fn unadjusted_tx_fee( + eth_transact_call: ::RuntimeCall, + dispatch_call: ::RuntimeCall, + ) -> BalanceOf { + // Get the dispatch info of the actual call dispatched + let mut dispatch_info = dispatch_call.get_dispatch_info(); + dispatch_info.extension_weight = + E::get_eth_extension(0u32.into(), 0u32.into()).weight(dispatch_call.into_ref()); + + // Build the extrinsic + let uxt: <::Block as BlockT>::Extrinsic = + UncheckedExtrinsic::new_bare(eth_transact_call).into(); + + // Compute the fee of the extrinsic + pallet_transaction_payment::Pallet::::compute_unadjusted_fee( + uxt.encoded_size() as u32, + &dispatch_info, + ) + .unwrap() + .into() + } + + fn tx_fee(len: u32, dispatch_info: &DispatchInfo) -> BalanceOf { + pallet_transaction_payment::Pallet::::compute_fee( + len, + dispatch_info, + 0u32.into(), + ) + .into() + } + + fn weight_to_fee(weight: Weight) -> BalanceOf { + pallet_transaction_payment::Pallet::::weight_to_fee(weight).into() + } + + /// Convert an unadjusted fee back to a weight. + fn fee_to_weight(fee: BalanceOf) -> Weight { + let ref_time = ::WeightToFee::REF_TIME_TO_FEE + .reciprocal() + .expect("This is not zero. Enforced in `integrity_test`; qed") + .saturating_mul_int(fee); + let proof_size = ::WeightToFee::proof_size_to_fee() + .reciprocal() + .expect("This is not zero. Enforced in `integrity_test`; qed") + .saturating_mul_int(fee); + Weight::from_parts(ref_time.saturated_into(), proof_size.saturated_into()) + } + + fn length_to_fee(len: u32) -> BalanceOf { + pallet_transaction_payment::Pallet::::length_to_fee(len).into() + } + + fn deposit_source() -> Option<::RuntimeHoldReason> { + Some(pallet_transaction_payment::HoldReason::Payment.into()) + } +} + +impl InfoT for () {} + +mod seal { + pub trait Sealed {} + impl Sealed for super::BlockRatioFee {} + impl Sealed for super::Info {} + impl Sealed for () {} +} diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs deleted file mode 100644 index 4ea822f306962..0000000000000 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ /dev/null @@ -1,216 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//! Encodes/Decodes EVM gas values. - -use crate::Weight; -use core::ops::{Div, Rem}; -use frame_support::pallet_prelude::CheckedShl; -use sp_arithmetic::traits::{One, Zero}; -use sp_core::U256; - -// We use 3 digits to store each component. -const SCALE: u128 = 100; - -/// Rounds up the given value to the nearest multiple of the mask. -/// -/// # Panics -/// Panics if the `mask` is zero. -fn round_up(value: T, mask: T) -> T -where - T: One + Zero + Copy + Rem + Div, - ::Output: PartialEq, -{ - let rest = if value % mask == T::zero() { T::zero() } else { T::one() }; - value / mask + rest -} - -/// Rounds up the log2 of the given value to the nearest integer. -fn log2_round_up(val: T) -> u128 -where - T: Into, -{ - let val = val.into(); - val.checked_ilog2() - .map(|v| if 1u128 << v == val { v } else { v + 1 }) - .unwrap_or(0) as u128 -} - -mod private { - pub trait Sealed {} - impl Sealed for () {} -} - -/// Encodes/Decodes EVM gas values. -/// -/// # Note -/// -/// This is defined as a trait rather than standalone functions to allow -/// it to be added as an associated type to [`crate::Config`]. This way, -/// it can be invoked without requiring the implementation bounds to be -/// explicitly specified. -/// -/// This trait is sealed and cannot be implemented by downstream crates. -pub trait GasEncoder: private::Sealed { - /// Encodes all components (deposit limit, weight reference time, and proof size) into a single - /// gas value. - fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256; - - /// Decodes the weight and deposit from the encoded gas value. - /// Returns `None` if the gas value is invalid - fn decode(gas: U256) -> Option<(Weight, Balance)>; - - /// Returns the encoded values of the specified weight and deposit. - fn as_encoded_values(weight: Weight, deposit: Balance) -> (Weight, Balance) { - let encoded = Self::encode(U256::zero(), weight, deposit); - Self::decode(encoded).expect("encoded values should be decodable; qed") - } -} - -impl GasEncoder for () -where - Balance: Zero + One + CheckedShl + Into, -{ - /// The encoding follows the pattern `g...grrppdd`, where: - /// - `dd`: log2 Deposit value, encoded in the lowest 2 digits. - /// - `pp`: log2 Proof size, encoded in the next 2 digits. - /// - `rr`: log2 Reference time, encoded in the next 2 digits. - /// - `g...g`: Gas limit, encoded in the highest digits. - /// - /// # Note - /// - The deposit value is maxed by 2^99 for u128 balance, and 2^63 for u64 balance. - fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256 { - let deposit: u128 = deposit.into(); - let deposit_component = log2_round_up(deposit); - - let proof_size = weight.proof_size(); - let proof_size_component = SCALE * log2_round_up(proof_size); - - let ref_time = weight.ref_time(); - let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time); - - let components = U256::from(deposit_component + proof_size_component + ref_time_component); - - let raw_gas_mask = U256::from(SCALE).pow(3.into()); - let raw_gas_component = if gas_limit <= components { - U256::zero() - } else { - round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask) - }; - - components.saturating_add(raw_gas_component) - } - - fn decode(gas: U256) -> Option<(Weight, Balance)> { - let deposit = gas % SCALE; - - // Casting with as_u32 is safe since all values are maxed by `SCALE`. - let deposit = deposit.as_u32(); - let proof_time = ((gas / SCALE) % SCALE).as_u32(); - let ref_time = ((gas / SCALE.pow(2)) % SCALE).as_u32(); - - let ref_weight = match ref_time { - 0 => 0, - 64 => u64::MAX, - _ => 1u64.checked_shl(ref_time)?, - }; - - let proof_weight = match proof_time { - 0 => 0, - 64 => u64::MAX, - _ => 1u64.checked_shl(proof_time)?, - }; - - let weight = Weight::from_parts(ref_weight, proof_weight); - - let deposit = match deposit { - 0 => Balance::zero(), - _ => Balance::one().checked_shl(deposit)?, - }; - - Some((weight, deposit)) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_gas_encoding_decoding_works() { - let raw_gas_limit = 111_111_999_999_999u128; - let weight = Weight::from_parts(222_999_999, 333_999_999); - let deposit = 444_999_999u64; - - let encoded_gas = <() as GasEncoder>::encode(raw_gas_limit.into(), weight, deposit); - assert_eq!(encoded_gas, U256::from(111_112_000_282_929u128)); - assert!(encoded_gas > raw_gas_limit.into()); - - let (decoded_weight, decoded_deposit) = - <() as GasEncoder>::decode(encoded_gas).unwrap(); - assert!(decoded_weight.all_gte(weight)); - assert!(weight.mul(2).all_gte(weight)); - - assert!(decoded_deposit >= deposit); - assert!(deposit * 2 >= decoded_deposit); - - assert_eq!( - (decoded_weight, decoded_deposit), - <() as GasEncoder>::as_encoded_values(weight, deposit) - ); - } - - #[test] - fn test_encoding_zero_values_work() { - let encoded_gas = <() as GasEncoder>::encode( - Default::default(), - Default::default(), - Default::default(), - ); - - assert_eq!(encoded_gas, U256::from(0)); - - let (decoded_weight, decoded_deposit) = - <() as GasEncoder>::decode(encoded_gas).unwrap(); - assert_eq!(Weight::default(), decoded_weight); - assert_eq!(0u64, decoded_deposit); - - let encoded_gas = - <() as GasEncoder>::encode(U256::from(1), Default::default(), Default::default()); - assert_eq!(encoded_gas, U256::from(1000000)); - } - - #[test] - fn test_encoding_max_values_work() { - let max_weight = Weight::from_parts(u64::MAX, u64::MAX); - let max_deposit = 1u64 << 63; - let encoded_gas = - <() as GasEncoder>::encode(Default::default(), max_weight, max_deposit); - - assert_eq!(encoded_gas, U256::from(646463)); - - let (decoded_weight, decoded_deposit) = - <() as GasEncoder>::decode(encoded_gas).unwrap(); - assert_eq!(max_weight, decoded_weight); - assert_eq!(max_deposit, decoded_deposit); - } - - #[test] - fn test_overflow() { - assert_eq!(None, <() as GasEncoder>::decode(65_00u128.into()), "Invalid proof size"); - assert_eq!(None, <() as GasEncoder>::decode(65_00_00u128.into()), "Invalid ref_time"); - } -} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index f75c1f599e0d1..a7242499dcbbe 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -18,19 +18,21 @@ use crate::{ evm::{ api::{GenericTransaction, TransactionSigned}, - GasEncoder, + fees::InfoT, }, vm::pvm::extract_code_and_data, - AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, OnChargeTransactionBalanceOf, Pallet, + AccountIdOf, AddressMapper, BalanceOf, Config, DispatchClass, MomentOf, Pallet, Zero, LOG_TARGET, RUNTIME_PALLETS_ADDR, }; use alloc::vec::Vec; use codec::{Decode, DecodeLimit, DecodeWithMemTracking, Encode}; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo}, - traits::{InherentBuilder, IsSubType, SignedTransactionBuilder}, + traits::{fungible::MutateHold, InherentBuilder, IsSubType, SignedTransactionBuilder}, MAX_EXTRINSIC_DEPTH, }; +use num_traits::Bounded; +use pallet_transaction_payment::Config as TxConfig; use scale_info::{StaticTypeInfo, TypeInfo}; use sp_core::{Get, H256, U256}; use sp_runtime::{ @@ -40,23 +42,16 @@ use sp_runtime::{ TransactionExtension, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, - OpaqueExtrinsic, RuntimeDebug, + FixedPointNumber, FixedU128, OpaqueExtrinsic, RuntimeDebug, SaturatedConversion, Weight, }; type CallOf = ::RuntimeCall; -/// The EVM gas price. -/// This constant is used by the proxy to advertise it via the eth_gas_price RPC. -/// -/// We use a fixed value for the gas price. -/// This let us calculate the gas estimate for a transaction with the formula: -/// `estimate_gas = substrate_fee / gas_price`. -/// -/// The chosen constant value is: -/// - Not too high, ensuring the gas value is large enough (at least 7 digits) to encode the -/// ref_time, proof_size, and deposit into the less significant (6 lower) digits of the gas value. -/// - Not too low, enabling users to adjust the gas price to define a tip. -pub(crate) const GAS_PRICE: u64 = 1_000u64; +/// Used to set the weight limit argument of a `eth_call` or `eth_instantiate_with_code` call. +pub trait SetWeightLimit { + /// Set the weight limit of this call. + fn set_weight_limit(&mut self, weight_limit: Weight); +} /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. @@ -125,13 +120,11 @@ where E: EthExtra, Self: Encode, ::Nonce: TryFrom, - ::RuntimeCall: Dispatchable, - OnChargeTransactionBalanceOf: Into>, BalanceOf: Into + TryFrom, MomentOf: Into, - CallOf: From> + IsSubType>, + CallOf: + From> + IsSubType> + SetWeightLimit, ::Hash: frame_support::traits::IsType, - // required by Checkable for `generic::UncheckedExtrinsic` generic::UncheckedExtrinsic, Signature, E::Extension>: Checkable< @@ -246,7 +239,7 @@ where /// EthExtra convert an unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`]. pub trait EthExtra { /// The Runtime configuration. - type Config: Config + pallet_transaction_payment::Config; + type Config: Config + TxConfig; /// The Runtime's transaction extension. /// It should include at least: @@ -285,9 +278,7 @@ pub trait EthExtra { ::Nonce: TryFrom, BalanceOf: Into + TryFrom, MomentOf: Into, - ::RuntimeCall: Dispatchable, - OnChargeTransactionBalanceOf: Into>, - CallOf: From>, + CallOf: From> + SetWeightLimit, ::Hash: frame_support::traits::IsType, { let tx = TransactionSigned::decode(&payload).map_err(|err| { @@ -317,30 +308,40 @@ pub trait EthExtra { InvalidTransaction::BadProof })?; + let base_fee = >::evm_gas_price(); + let signer = ::AddressMapper::to_fallback_account_id(&signer_addr); - let GenericTransaction { nonce, chain_id, to, value, input, gas, gas_price, .. } = - GenericTransaction::from_signed(tx, crate::GAS_PRICE.into(), None); + let tx = GenericTransaction::from_signed(tx, None); - let Some(gas) = gas else { + let Some(gas) = tx.gas else { log::debug!(target: LOG_TARGET, "No gas provided"); return Err(InvalidTransaction::Call); }; - if chain_id.unwrap_or_default() != ::ChainId::get().into() { + let Some(effective_gas_price) = tx.effective_gas_price(base_fee) else { + log::debug!(target: LOG_TARGET, "No gas_price provided"); + return Err(InvalidTransaction::Payment); + }; + + let chain_id = tx.chain_id.unwrap_or_default(); + + if chain_id != ::ChainId::get().into() { log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}"); return Err(InvalidTransaction::Call); } - let value = value.unwrap_or_default(); - let data = input.to_vec(); + if effective_gas_price < base_fee { + log::debug!( + target: LOG_TARGET, + "Specified gas_price is too low. effective_gas_price={effective_gas_price} base_fee={base_fee}" + ); + return Err(InvalidTransaction::Payment); + } - let (gas_limit, storage_deposit_limit) = - ::EthGasEncoder::decode(gas).ok_or_else(|| { - log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); - InvalidTransaction::Call - })?; + let value = tx.value.unwrap_or_default(); + let data = tx.input.to_vec(); - let call = if let Some(dest) = to { + let mut call = if let Some(dest) = tx.to { if dest == RUNTIME_PALLETS_ADDR { let call = CallOf::::decode_all_with_depth_limit( MAX_EXTRINSIC_DEPTH, @@ -358,14 +359,16 @@ pub trait EthExtra { call } else { - crate::Call::eth_call:: { + let call = crate::Call::eth_call:: { dest, value, - gas_limit, - storage_deposit_limit, + gas_limit: Zero::zero(), + storage_deposit_limit: BalanceOf::::max_value(), data, + effective_gas_price, } - .into() + .into(); + call } } else { let (code, data) = if data.starts_with(&polkavm_common::program::BLOB_MAGIC) { @@ -378,57 +381,106 @@ pub trait EthExtra { (data, Default::default()) }; - crate::Call::eth_instantiate_with_code:: { + let call = crate::Call::eth_instantiate_with_code:: { value, - gas_limit, - storage_deposit_limit, + gas_limit: Zero::zero(), + storage_deposit_limit: BalanceOf::::max_value(), code, data, + effective_gas_price, } - .into() + .into(); + + call }; let mut info = call.get_dispatch_info(); - let nonce = nonce.unwrap_or_default().try_into().map_err(|_| { + let nonce = tx.nonce.unwrap_or_default().try_into().map_err(|_| { log::debug!(target: LOG_TARGET, "Failed to convert nonce"); InvalidTransaction::Call })?; - let gas_price = gas_price.unwrap_or_default(); - let eth_fee = Pallet::::evm_gas_to_fee(gas, gas_price) - .map_err(|_| InvalidTransaction::Call)?; - - // Fees calculated from the extrinsic, without the tip. info.extension_weight = Self::get_eth_extension(nonce, 0u32.into()).weight(&call); - let actual_fee: BalanceOf = - pallet_transaction_payment::Pallet::::compute_fee( - encoded_len as u32, - &info, - Default::default(), - ) - .into(); - log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: gas_price: {gas_price:?}, encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); + let extrinsic_fee = ::FeeInfo::tx_fee(encoded_len as u32, &info); - // The fees from the Ethereum transaction should be greater or equal to the actual fees paid - // by the account. - if eth_fee < actual_fee { - log::debug!(target: LOG_TARGET, "eth fees {eth_fee:?} too low, actual fees: {actual_fee:?}"); - return Err(InvalidTransaction::Payment.into()) - } + // the fee as signed off by the eth wallet. we cannot consume more. + let eth_fee = effective_gas_price.saturating_mul(gas) / + ::NativeToEthRatio::get(); + + // this is the fee left after accounting for the extrinsic itself + // the rest if for the weight and storage deposit limit + let remaining_fee = eth_fee.checked_sub(extrinsic_fee.into()).ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover the extrinsic base fee. eth_fee={eth_fee:?} extrinsic_fee={extrinsic_fee:?}"); + InvalidTransaction::Payment + })?; - let tip = - Pallet::::evm_gas_to_fee(gas, gas_price.saturating_sub(GAS_PRICE.into())) - .unwrap_or_default() - .min(actual_fee); + let weight_limit = { + let remaining_unadjusted_fee = + ::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(>::saturated_from(remaining_fee)); + let weight_limit = + ::FeeInfo::fee_to_weight(remaining_unadjusted_fee); + call.set_weight_limit(weight_limit); + let factor = FixedU128::from_rational(3, 4); + let max_weight = ::BlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic + .unwrap_or_else(|| { + ::BlockWeights::get().max_block + }); + let max_weight = Weight::from_parts( + factor.saturating_mul_int(max_weight.ref_time()), + factor.saturating_mul_int(max_weight.proof_size()), + ); + let mut info = call.get_dispatch_info(); + info.extension_weight = Self::get_eth_extension(nonce, 0u32.into()).weight(&call); + let overweight_by = info.total_weight().saturating_sub(max_weight); + let capped_weight = weight_limit.saturating_sub(overweight_by); + call.set_weight_limit(capped_weight); + capped_weight + }; + + // the overall fee of the extrinsic including the gas limit + let mut info = call.get_dispatch_info(); + info.extension_weight = Self::get_eth_extension(nonce, 0u32.into()).weight(&call); + let tx_fee = ::FeeInfo::tx_fee(encoded_len as u32, &info); + + // the leftover we make available to the deposit collection system + let deposit_source = ::FeeInfo::deposit_source().ok_or_else(|| { + log::debug!(target: LOG_TARGET, "You need to supply a proper T::FeeInfo implemention. This is a bug."); + InvalidTransaction::Payment + })?; + let storage_deposit = eth_fee.saturating_sub(tx_fee.into()).saturated_into(); + ::Currency::hold(&deposit_source, &signer, storage_deposit) + .map_err(|_| { + log::debug!(target: LOG_TARGET, "Failed to hold storage deposit"); + InvalidTransaction::Call + })?; crate::tracing::if_tracing(|tracer| { tracer.watch_address(&Pallet::::block_author().unwrap_or_default()); tracer.watch_address(&signer_addr); }); - log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce: {nonce:?} and tip: {tip:?}"); + log::debug!(target: LOG_TARGET, "\ + Created checked Ethereum transaction with: \ + gas={gas} \ + extrinsic_fee={extrinsic_fee:?} \ + weight_limit={weight_limit} \ + additional_storage_deposit_held={storage_deposit:?} \ + effective_gas_price={effective_gas_price} \ + base_fee={base_fee} \ + nonce={nonce:?} + " + ); + + // We can't calculate a tip because it needs to be based on the actual gas used which we + // cannot know pre-dispatch. Hence we never supply a tip here or it would be way too high. Ok(CheckedExtrinsic { - format: ExtrinsicFormat::Signed(signer.into(), Self::get_eth_extension(nonce, tip)), + format: ExtrinsicFormat::Signed( + signer.into(), + Self::get_eth_extension(nonce, Zero::zero()), + ), function: call, }) } @@ -440,40 +492,25 @@ mod test { use crate::{ evm::*, test_utils::*, - tests::{ExtBuilder, RuntimeCall, RuntimeOrigin, Test}, + tests::{ + Address, ExtBuilder, RuntimeCall, RuntimeOrigin, SignedExtra, Test, UncheckedExtrinsic, + }, Weight, }; use frame_support::{error::LookupError, traits::fungible::Mutate}; use pallet_revive_fixtures::compile_module; - use sp_runtime::{ - traits::{self, Checkable, DispatchTransaction}, - MultiAddress, MultiSignature, - }; - type AccountIdOf = ::AccountId; - - #[derive(Clone, PartialEq, Eq, Debug)] - pub struct Extra; - type SignedExtra = (frame_system::CheckNonce, ChargeTransactionPayment); + use sp_runtime::traits::{self, Checkable, DispatchTransaction}; - use pallet_transaction_payment::ChargeTransactionPayment; - impl EthExtra for Extra { - type Config = Test; - type Extension = SignedExtra; - - fn get_eth_extension(nonce: u32, tip: BalanceOf) -> Self::Extension { - (frame_system::CheckNonce::from(nonce), ChargeTransactionPayment::from(tip)) - } - } + type AccountIdOf = ::AccountId; - type Ex = UncheckedExtrinsic, MultiSignature, Extra>; struct TestContext; impl traits::Lookup for TestContext { - type Source = MultiAddress; + type Source = Address; type Target = AccountIdOf; fn lookup(&self, s: Self::Source) -> Result { match s { - MultiAddress::Id(id) => Ok(id), + Self::Source::Id(id) => Ok(id), _ => Err(LookupError), } } @@ -493,7 +530,6 @@ mod test { tx: GenericTransaction { from: Some(Account::default().address()), chain_id: Some(::ChainId::get().into()), - gas_price: Some(U256::from(GAS_PRICE)), ..Default::default() }, before_validate: None, @@ -506,26 +542,12 @@ mod test { } fn estimate_gas(&mut self) { - let dry_run = crate::Pallet::::dry_run_eth_transact( - self.tx.clone(), - Weight::MAX, - |eth_call, dispatch_call| { - let mut info = dispatch_call.get_dispatch_info(); - info.extension_weight = - Extra::get_eth_extension(0, 0u32.into()).weight(&dispatch_call); - let uxt: Ex = - sp_runtime::generic::UncheckedExtrinsic::new_bare(eth_call).into(); - pallet_transaction_payment::Pallet::::compute_fee( - uxt.encoded_size() as u32, - &info, - Default::default(), - ) - }, - ); + let dry_run = crate::Pallet::::dry_run_eth_transact(self.tx.clone(), Weight::MAX); + + self.tx.gas_price = Some(>::evm_gas_price()); match dry_run { Ok(dry_run) => { - log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas); self.tx.gas = Some(dry_run.eth_gas); }, Err(err) => { @@ -583,7 +605,7 @@ mod test { let call = RuntimeCall::Contracts(crate::Call::eth_transact { payload }); let encoded_len = call.encoded_size(); - let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); + let uxt: UncheckedExtrinsic = generic::UncheckedExtrinsic::new_bare(call).into(); let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?; let (account_id, extra): (AccountId32, SignedExtra) = match result.format { ExtrinsicFormat::Signed(signer, extra) => (signer, extra), @@ -608,8 +630,7 @@ mod test { fn check_eth_transact_call_works() { let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); let (call, _, tx) = builder.check().unwrap(); - let (gas_limit, storage_deposit_limit) = - <::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); + let effective_gas_price: u32 = ::NativeToEthRatio::get(); assert_eq!( call, @@ -617,8 +638,10 @@ mod test { dest: tx.to.unwrap(), value: tx.value.unwrap_or_default().as_u64().into(), data: tx.input.to_vec(), - gas_limit, - storage_deposit_limit + // its a transfer to a non contract: does not use any gas + gas_limit: Zero::zero(), + storage_deposit_limit: >::max_value(), + effective_gas_price: effective_gas_price.into(), } .into() ); @@ -630,8 +653,7 @@ mod test { let data = vec![]; let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); let (call, _, tx) = builder.check().unwrap(); - let (gas_limit, storage_deposit_limit) = - <::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); + let effective_gas_price: u32 = ::NativeToEthRatio::get(); assert_eq!( call, @@ -639,8 +661,9 @@ mod test { value: tx.value.unwrap_or_default().as_u64().into(), code, data, - gas_limit, - storage_deposit_limit + gas_limit: Weight::from_parts(54753, 0), + storage_deposit_limit: >::max_value(), + effective_gas_price: effective_gas_price.into(), } .into() ); @@ -724,16 +747,14 @@ mod test { fn check_transaction_tip() { let (code, _) = compile_module("dummy").unwrap(); let data = vec![]; - let (_, extra, tx) = + let (_, extra, _tx) = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) .mutate_estimate_and_check(Box::new(|tx| { tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); })) .unwrap(); - let diff = tx.gas_price.unwrap() - U256::from(GAS_PRICE); - let expected_tip = crate::Pallet::::evm_gas_to_fee(tx.gas.unwrap(), diff).unwrap(); - assert_eq!(extra.1.tip(), expected_tip); + assert_eq!(U256::from(extra.1.tip()), 0u32.into()); } #[test] diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 9444e55cfb0f0..f3aa5ea2dece9 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -54,7 +54,7 @@ use sp_core::{ }; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::{ - traits::{BadOrigin, Bounded, Convert, Saturating, Zero}, + traits::{BadOrigin, Bounded, Saturating, Zero}, DispatchError, SaturatedConversion, }; @@ -392,9 +392,6 @@ pub trait PrecompileExt: sealing::Sealed { /// Returns the maximum allowed size of a storage item. fn max_value_size(&self) -> u32; - /// Returns the price for the specified amount of weight. - fn get_weight_price(&self, weight: Weight) -> U256; - /// Get an immutable reference to the nested gas meter. fn gas_meter(&self) -> &GasMeter; @@ -437,6 +434,9 @@ pub trait PrecompileExt: sealing::Sealed { /// - If `code_offset + buf.len()` extends beyond code: Available code copied, remaining bytes /// are filled with zeros fn copy_code_slice(&mut self, buf: &mut [u8], address: &H160, code_offset: usize); + + /// Returns the effective gas price of this transaction. + fn effective_gas_price(&self) -> u64; } /// Describes the different functions that can be exported by an [`Executable`]. @@ -814,6 +814,8 @@ where Err(e) => t.exit_child_span_with_error(e.error.into(), Weight::zero()), }); + log::trace!(target: LOG_TARGET, "call finished with: {result:?}"); + result } } @@ -857,6 +859,7 @@ where Contracts::::deposit_event(Event::Instantiated { deployer, contract }); } } + log::trace!(target: LOG_TARGET, "instantiate finished with: {result:?}"); result } @@ -2092,10 +2095,6 @@ where limits::PAYLOAD_BYTES } - fn get_weight_price(&self, weight: Weight) -> U256 { - T::WeightPrice::convert(weight).into() - } - fn gas_meter(&self) -> &GasMeter { &self.top_frame().nested_gas } @@ -2158,6 +2157,14 @@ where buf[len..].fill(0); } + + fn effective_gas_price(&self) -> u64 { + self.exec_config + .effective_gas_price + .unwrap_or_else(|| >::evm_gas_price()) + .try_into() + .unwrap_or(u64::MAX) + } } mod sealing { diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index 399fd534edd96..ee0e38666babe 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -152,10 +152,6 @@ impl PrecompileExt for MockExt { panic!("MockExt::max_value_size") } - fn get_weight_price(&self, _weight: Weight) -> U256 { - panic!("MockExt::get_weight_price") - } - fn gas_meter(&self) -> &GasMeter { &self.gas_meter } @@ -205,6 +201,10 @@ impl PrecompileExt for MockExt { fn copy_code_slice(&mut self, _buf: &mut [u8], _address: &H160, _code_offset: usize) { panic!("MockExt::copy_code_slice") } + + fn effective_gas_price(&self) -> u64 { + panic!("MockExt::effective_gas_price") + } } impl PrecompileWithInfoExt for MockExt { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 569630a5691fc..387f84bf2bc03 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -45,8 +45,8 @@ pub mod weights; use crate::{ evm::{ - runtime::GAS_PRICE, CallTracer, GasEncoder, GenericTransaction, PrestateTracer, Trace, - Tracer, TracerType, TYPE_EIP1559, + fees::InfoT as FeeInfo, CallTracer, GenericTransaction, PrestateTracer, Trace, Tracer, + TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, gas::GasMeter, @@ -76,11 +76,10 @@ use frame_system::{ pallet_prelude::{BlockNumberFor, OriginFor}, Pallet as System, }; -use pallet_transaction_payment::OnChargeTransaction; use scale_info::TypeInfo; use sp_runtime::{ - traits::{Bounded, Convert, Dispatchable, Saturating}, - AccountId32, DispatchError, + traits::{Bounded, Convert, Dispatchable, Saturating, Zero}, + AccountId32, DispatchError, FixedPointNumber, }; pub use crate::{ @@ -94,7 +93,6 @@ pub use crate::{ pub use codec; pub use frame_support::{self, dispatch::DispatchInfo, weights::Weight}; pub use frame_system::{self, limits::BlockWeights}; -pub use pallet_transaction_payment; pub use primitives::*; pub use sp_core::{H160, H256, U256}; pub use sp_runtime; @@ -107,7 +105,6 @@ pub type BalanceOf = <::Currency as Inspect<::AccountId>>::Balance; type TrieId = BoundedVec>; type ImmutableData = BoundedVec>; -pub(crate) type OnChargeTransactionBalanceOf = <::OnChargeTransaction as OnChargeTransaction>::Balance; /// Used as a sentinel value when reading and writing contract memory. /// @@ -165,11 +162,6 @@ pub mod pallet { #[pallet::no_default_bounds] type RuntimeHoldReason: From; - /// Used to answer contracts' queries regarding the current weight price. This is **not** - /// used to calculate the actual fee and is only for informational purposes. - #[pallet::no_default_bounds] - type WeightPrice: Convert>; - /// Describes the weights of the dispatchables of this module and is also used to /// construct a default cost schedule. type WeightInfo: WeightInfo; @@ -274,19 +266,8 @@ pub mod pallet { #[pallet::constant] type NativeToEthRatio: Get; - /// Encode and decode Ethereum gas values. - /// Only valid value is `()`. See [`GasEncoder`]. #[pallet::no_default_bounds] - type EthGasEncoder: GasEncoder>; - - /// Set to `Some` in order to collect all storage deposits from the specified hold. - /// - /// If `None` the deposits are collected from free balance. In any case, they are - /// collected from the transaction signers native balance. - /// - /// It only applies to eth_* dispatchables. The non eth flavor functions will continue - /// to take from the free balance. - type DepositSource: Get>; + type FeeInfo: FeeInfo; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -300,11 +281,13 @@ pub mod pallet { use sp_core::parameter_types; type Balance = u64; - const UNITS: Balance = 10_000_000_000; - const CENTS: Balance = UNITS / 100; + + pub const DOLLARS: Balance = 1_000_000_000_000; + pub const CENTS: Balance = DOLLARS / 100; + pub const MILLICENTS: Balance = CENTS / 1_000; pub const fn deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS + items as Balance * 20 * CENTS + (bytes as Balance) * MILLICENTS } parameter_types! { @@ -353,14 +336,12 @@ pub mod pallet { type UploadOrigin = EnsureSigned; type InstantiateOrigin = EnsureSigned; type WeightInfo = (); - type WeightPrice = Self; type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; type ChainId = ConstU64<42>; type NativeToEthRatio = ConstU32<1_000_000>; - type EthGasEncoder = (); type FindAuthor = (); - type DepositSource = (); + type FeeInfo = (); } } @@ -681,6 +662,7 @@ pub mod pallet { System::::account_exists(&Pallet::::account_id()); return T::DbWeight::get().reads(1) } + fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { let mut meter = WeightMeter::with_limit(limit); ContractInfo::::process_deletion_queue_batch(&mut meter); @@ -690,6 +672,8 @@ pub mod pallet { fn integrity_test() { assert!(T::ChainId::get() > 0, "ChainId must be greater than 0"); + T::FeeInfo::integrity_test(); + // The memory available in the block building runtime let max_runtime_mem: u32 = T::RuntimeMemory::get(); @@ -832,7 +816,7 @@ pub mod pallet { /// * If no account exists and the call value is not less than `existential_deposit`, /// a regular account will be created and any value will be transferred. #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))] + #[pallet::weight(::WeightInfo::call().saturating_add(*gas_limit))] pub fn call( origin: OriginFor, dest: H160, @@ -856,7 +840,7 @@ pub mod pallet { output.result = Err(>::ContractReverted.into()); } } - dispatch_result(output.result, output.gas_consumed, T::WeightInfo::call()) + dispatch_result(output.result, output.gas_consumed, ::WeightInfo::call()) } /// Instantiates a contract from a previously deployed vm binary. @@ -866,7 +850,7 @@ pub mod pallet { /// must be supplied. #[pallet::call_index(2)] #[pallet::weight( - T::WeightInfo::instantiate(data.len() as u32).saturating_add(*gas_limit) + ::WeightInfo::instantiate(data.len() as u32).saturating_add(*gas_limit) )] pub fn instantiate( origin: OriginFor, @@ -896,7 +880,7 @@ pub mod pallet { dispatch_result( output.result.map(|result| result.result), output.gas_consumed, - T::WeightInfo::instantiate(data_len), + ::WeightInfo::instantiate(data_len), ) } @@ -929,7 +913,7 @@ pub mod pallet { /// - The `deploy` function is executed in the context of the newly-created account. #[pallet::call_index(3)] #[pallet::weight( - T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32) + ::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32) .saturating_add(*gas_limit) )] pub fn instantiate_with_code( @@ -961,7 +945,7 @@ pub mod pallet { dispatch_result( output.result.map(|result| result.result), output.gas_consumed, - T::WeightInfo::instantiate_with_code(code_len, data_len), + ::WeightInfo::instantiate_with_code(code_len, data_len), ) } @@ -974,7 +958,7 @@ pub mod pallet { /// times within a batch call transaction. #[pallet::call_index(10)] #[pallet::weight( - T::WeightInfo::eth_instantiate_with_code(code.len() as u32, data.len() as u32, Pallet::::has_dust(*value).into()) + ::WeightInfo::eth_instantiate_with_code(code.len() as u32, data.len() as u32, Pallet::::has_dust(*value).into()) .saturating_add(*gas_limit) )] pub fn eth_instantiate_with_code( @@ -984,6 +968,7 @@ pub mod pallet { #[pallet::compact] storage_deposit_limit: BalanceOf, code: Vec, data: Vec, + effective_gas_price: U256, ) -> DispatchResultWithPostInfo { let code_len = code.len() as u32; let data_len = data.len() as u32; @@ -995,7 +980,7 @@ pub mod pallet { Code::Upload(code), data, None, - ExecConfig::new_eth_tx(), + ExecConfig::new_eth_tx(effective_gas_price), ); if let Ok(retval) = &output.result { @@ -1006,7 +991,7 @@ pub mod pallet { dispatch_result( output.result.map(|result| result.result), output.gas_consumed, - T::WeightInfo::eth_instantiate_with_code( + ::WeightInfo::eth_instantiate_with_code( code_len, data_len, Pallet::::has_dust(value).into(), @@ -1017,7 +1002,7 @@ pub mod pallet { /// Same as [`Self::call`], but intended to be dispatched **only** /// by an EVM transaction through the EVM compatibility layer. #[pallet::call_index(11)] - #[pallet::weight(T::WeightInfo::eth_call(Pallet::::has_dust(*value).into()).saturating_add(*gas_limit))] + #[pallet::weight(::WeightInfo::eth_call(Pallet::::has_dust(*value).into()).saturating_add(*gas_limit))] pub fn eth_call( origin: OriginFor, dest: H160, @@ -1025,6 +1010,7 @@ pub mod pallet { gas_limit: Weight, #[pallet::compact] storage_deposit_limit: BalanceOf, data: Vec, + effective_gas_price: U256, ) -> DispatchResultWithPostInfo { let mut output = Self::bare_call( origin, @@ -1033,7 +1019,7 @@ pub mod pallet { gas_limit, storage_deposit_limit, data, - ExecConfig::new_eth_tx(), + ExecConfig::new_eth_tx(effective_gas_price), ); if let Ok(return_value) = &output.result { @@ -1044,7 +1030,7 @@ pub mod pallet { dispatch_result( output.result, output.gas_consumed, - T::WeightInfo::eth_call(Pallet::::has_dust(value).into()), + ::WeightInfo::eth_call(Pallet::::has_dust(value).into()), ) } @@ -1063,7 +1049,7 @@ pub mod pallet { /// If the refcount of the code reaches zero after terminating the last contract that /// references this code, the code will be removed automatically. #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::upload_code(code.len() as u32))] + #[pallet::weight(::WeightInfo::upload_code(code.len() as u32))] pub fn upload_code( origin: OriginFor, code: Vec, @@ -1077,7 +1063,7 @@ pub mod pallet { /// A code can only be removed by its original uploader (its owner) and only if it is /// not used by any contract. #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::remove_code())] + #[pallet::weight(::WeightInfo::remove_code())] pub fn remove_code( origin: OriginFor, code_hash: sp_core::H256, @@ -1099,7 +1085,7 @@ pub mod pallet { /// that the contract address is no longer derived from its code hash after calling /// this dispatchable. #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::set_code())] + #[pallet::weight(::WeightInfo::set_code())] pub fn set_code( origin: OriginFor, dest: H160, @@ -1128,7 +1114,7 @@ pub mod pallet { /// This will error if the origin is already mapped or is a eth native `Address20`. It will /// take a deposit that can be released by calling [`Self::unmap_account`]. #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::map_account())] + #[pallet::weight(::WeightInfo::map_account())] pub fn map_account(origin: OriginFor) -> DispatchResult { let origin = ensure_signed(origin)?; T::AddressMapper::map(&origin) @@ -1139,7 +1125,7 @@ pub mod pallet { /// There is no reason to ever call this function other than freeing up the deposit. /// This is only useful when the account should no longer be used. #[pallet::call_index(8)] - #[pallet::weight(T::WeightInfo::unmap_account())] + #[pallet::weight(::WeightInfo::unmap_account())] pub fn unmap_account(origin: OriginFor) -> DispatchResult { let origin = ensure_signed(origin)?; T::AddressMapper::unmap(&origin) @@ -1154,7 +1140,7 @@ pub mod pallet { #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( - T::WeightInfo::dispatch_as_fallback_account().saturating_add(dispatch_info.call_weight), + ::WeightInfo::dispatch_as_fallback_account().saturating_add(dispatch_info.call_weight), dispatch_info.class ) })] @@ -1331,26 +1317,21 @@ where pub fn dry_run_eth_transact( mut tx: GenericTransaction, gas_limit: Weight, - tx_fee: impl Fn(::RuntimeCall, ::RuntimeCall) -> BalanceOf, ) -> Result>, EthTransactError> where - ::RuntimeCall: - Dispatchable, - T: pallet_transaction_payment::Config, - OnChargeTransactionBalanceOf: Into>, ::RuntimeCall: From>, - ::RuntimeCall: Encode, T::Nonce: Into, T::Hash: frame_support::traits::IsType, { - log::trace!(target: LOG_TARGET, "dry_run_eth_transact: {tx:?} gas_limit: {gas_limit:?}"); + log::debug!(target: LOG_TARGET, "dry_run_eth_transact: {tx:?}"); let from = tx.from.unwrap_or_default(); let origin = T::AddressMapper::to_account_id(&from); Self::prepare_dry_run(&origin); - let storage_deposit_limit = BalanceOf::::max_value(); - let mut exec_config = ExecConfig::new_eth_tx(); + let base_fee = Self::evm_gas_price(); + let effective_gas_price = tx.effective_gas_price(base_fee).unwrap_or_default(); + let mut exec_config = ExecConfig::new_eth_tx(effective_gas_price); exec_config.unsafe_skip_transfers = tx.gas.is_none(); if tx.nonce.is_none() { @@ -1360,13 +1341,13 @@ where tx.chain_id = Some(T::ChainId::get().into()); } if tx.gas_price.is_none() { - tx.gas_price = Some(GAS_PRICE.into()); + tx.gas_price = Some(base_fee); } if tx.max_priority_fee_per_gas.is_none() { - tx.max_priority_fee_per_gas = Some(GAS_PRICE.into()); + tx.max_priority_fee_per_gas = Some(base_fee); } if tx.max_fee_per_gas.is_none() { - tx.max_fee_per_gas = Some(GAS_PRICE.into()); + tx.max_fee_per_gas = Some(base_fee); } if tx.gas.is_none() { tx.gas = Some(Self::evm_block_gas_limit()); @@ -1397,7 +1378,7 @@ where }; // Dry run the call - let (mut result, dispatch_call) = match tx.to { + let (weight_limit, mut result, dispatch_call) = match tx.to { // A contract call. Some(dest) => { if dest == RUNTIME_PALLETS_ADDR { @@ -1421,7 +1402,7 @@ where ..Default::default() }; - (result, dispatch_call) + (Zero::zero(), result, dispatch_call) } else { // Dry run the call. let result = crate::Pallet::::bare_call( @@ -1429,7 +1410,7 @@ where dest, value, gas_limit, - storage_deposit_limit, + BalanceOf::::max_value(), input.clone(), exec_config, ); @@ -1453,20 +1434,16 @@ where data, eth_gas: Default::default(), }; - - let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( - result.gas_required, - result.storage_deposit, - ); let dispatch_call: ::RuntimeCall = crate::Call::::eth_call { dest, value, - gas_limit, - storage_deposit_limit, + gas_limit: Zero::zero(), + storage_deposit_limit: Zero::zero(), data: input.clone(), + effective_gas_price, } .into(); - (result, dispatch_call) + (result.gas_required, result, dispatch_call) } }, // A contract deployment @@ -1483,7 +1460,7 @@ where T::RuntimeOrigin::signed(origin), value, gas_limit, - storage_deposit_limit, + BalanceOf::::max_value(), Code::Upload(code.clone()), data.clone(), None, @@ -1509,22 +1486,17 @@ where data: returned_data, eth_gas: Default::default(), }; - - // Get the dispatch info of the call. - let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( - result.gas_required, - result.storage_deposit, - ); let dispatch_call: ::RuntimeCall = crate::Call::::eth_instantiate_with_code { value, - gas_limit, - storage_deposit_limit, + gas_limit: Zero::zero(), + storage_deposit_limit: Zero::zero(), code, data, + effective_gas_price, } .into(); - (result, dispatch_call) + (result.gas_required, result, dispatch_call) }, }; @@ -1534,12 +1506,27 @@ where let eth_transact_call = crate::Call::::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; - let fee = tx_fee(eth_transact_call.into(), dispatch_call); - let raw_gas = Self::evm_fee_to_gas(fee); - let eth_gas = - T::EthGasEncoder::encode(raw_gas, result.gas_required, result.storage_deposit); + let extrinsic_fee: U256 = + T::FeeInfo::unadjusted_tx_fee(eth_transact_call.into(), dispatch_call).into(); + let gas_limit = Self::evm_gas_from_weight(weight_limit); + let storage_deposit = + T::FeeInfo::next_fee_multiplier_reciprocal().saturating_mul_int(result.storage_deposit); + + // It is important that we add the gas_limit here and not calculate it via the extrinsic. + // Otherwise it is possible that gas_limit does not influence the weight of the extrinsic + // and hence yields zero when decoding it. + let eth_gas: U256 = extrinsic_fee + .saturating_add(gas_limit) + .saturating_add(storage_deposit.into()) + .into(); + + log::debug!(target: LOG_TARGET, "\ + dry_run_eth_transact: \ + weight_limit={weight_limit:?}: \ + (extrinsic_fee={extrinsic_fee:?}) + (gas_limit={gas_limit:?}) + (storage_deposit={storage_deposit:?}) = (eth_gas={eth_gas:?})\ + ", - log::trace!(target: LOG_TARGET, "bare_eth_call: raw_gas: {raw_gas:?} eth_gas: {eth_gas:?}"); + ); result.eth_gas = eth_gas; Ok(result) } @@ -1580,56 +1567,23 @@ where System::::account_nonce(account).into() } - /// Convert a substrate fee into a gas value, using the fixed `GAS_PRICE`. - /// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. - pub fn evm_fee_to_gas(fee: BalanceOf) -> U256 { - let fee = Self::convert_native_to_evm(fee); - let gas_price = GAS_PRICE.into(); - let (quotient, remainder) = fee.div_mod(gas_price); - if remainder.is_zero() { - quotient - } else { - quotient + U256::one() - } - } - - /// Convert a gas value into a substrate fee - fn evm_gas_to_fee(gas: U256, gas_price: U256) -> Result, Error> { - let fee = gas.saturating_mul(gas_price); - let value = BalanceWithDust::>::from_value::(fee)?; - Ok(value.into_rounded_balance()) - } - - /// Convert a weight to a gas value. - pub fn evm_gas_from_weight(weight: Weight) -> U256 { - let fee = T::WeightPrice::convert(weight); - Self::evm_fee_to_gas(fee) - } - /// Get the block gas limit. - pub fn evm_block_gas_limit() -> U256 - where - ::RuntimeCall: - Dispatchable, - T: pallet_transaction_payment::Config, - OnChargeTransactionBalanceOf: Into>, - { + pub fn evm_block_gas_limit() -> U256 { let max_block_weight = T::BlockWeights::get() .get(DispatchClass::Normal) .max_total .unwrap_or_else(|| T::BlockWeights::get().max_block); - let length_fee = pallet_transaction_payment::Pallet::::length_to_fee( - 5 * 1024 * 1024, // 5 MB - ); + let length_fee = + T::FeeInfo::length_to_fee(*T::BlockLength::get().max.get(DispatchClass::Normal)); - Self::evm_gas_from_weight(max_block_weight) - .saturating_add(Self::evm_fee_to_gas(length_fee.into())) + Self::evm_gas_from_weight(max_block_weight).saturating_add(length_fee.into()) } - /// Get the gas price. + /// Get the base gas price. pub fn evm_gas_price() -> U256 { - GAS_PRICE.into() + let multiplier = T::FeeInfo::next_fee_multiplier(); + multiplier.saturating_mul_int::(T::NativeToEthRatio::get().into()).into() } /// Build an EVM tracer from the given tracer type. @@ -1688,6 +1642,15 @@ where Ok(maybe_value) } + /// Convert a native balance to EVM balance. + pub fn convert_native_to_evm(value: impl Into>>) -> U256 { + let (value, dust) = value.into().deconstruct(); + value + .into() + .saturating_mul(T::NativeToEthRatio::get().into()) + .saturating_add(dust.into()) + } + /// Uploads new code and returns the Vm binary contract blob and deposit amount collected. fn try_upload_pvm_code( origin: T::AccountId, @@ -1720,13 +1683,9 @@ where }) } - /// Convert a native balance to EVM balance. - pub fn convert_native_to_evm(value: impl Into>>) -> U256 { - let (value, dust) = value.into().deconstruct(); - value - .into() - .saturating_mul(T::NativeToEthRatio::get().into()) - .saturating_add(dust.into()) + /// Convert a weight to a gas value. + fn evm_gas_from_weight(weight: Weight) -> U256 { + T::FeeInfo::weight_to_fee(weight).into() } } @@ -1738,26 +1697,6 @@ impl Pallet { PalletId(*b"py/reviv").into_account_truncating() } - /// Returns true if the evm value carries dust. - fn has_dust(value: U256) -> bool { - value % U256::from(::NativeToEthRatio::get()) != U256::zero() - } - - /// Returns true if the evm value carries balance. - fn has_balance(value: U256) -> bool { - value >= U256::from(::NativeToEthRatio::get()) - } - - /// Return the existential deposit of [`Config::Currency`]. - fn min_balance() -> BalanceOf { - >>::minimum_balance() - } - - /// Deposit a pallet revive event. - fn deposit_event(event: Event) { - >::deposit_event(::RuntimeEvent::from(event)) - } - /// The address of the validator that produced the current block. pub fn block_author() -> Option { use frame_support::traits::FindAuthor; @@ -1797,7 +1736,7 @@ impl Pallet { use frame_support::traits::tokens::{Fortitude, Precision, Preservation, Restriction}; let deposit_source = exec_config .collect_deposit_from_hold - .then_some(T::DepositSource::get()) + .then_some(T::FeeInfo::deposit_source()) .flatten(); match (deposit_source, hold_reason) { (Some(deposit_source), hold_reason) => { @@ -1831,6 +1770,26 @@ impl Pallet { } Ok(()) } + + /// Returns true if the evm value carries dust. + fn has_dust(value: U256) -> bool { + value % U256::from(::NativeToEthRatio::get()) != U256::zero() + } + + /// Returns true if the evm value carries balance. + fn has_balance(value: U256) -> bool { + value >= U256::from(::NativeToEthRatio::get()) + } + + /// Return the existential deposit of [`Config::Currency`]. + fn min_balance() -> BalanceOf { + >>::minimum_balance() + } + + /// Deposit a pallet contracts event. + fn deposit_event(event: Event) { + >::deposit_event(::RuntimeEvent::from(event)) + } } /// The address used to call the runtime's pallets dispatchables @@ -1977,6 +1936,19 @@ sp_api::decl_runtime_apis! { macro_rules! impl_runtime_apis_plus_revive { ($Runtime: ty, $Executive: ty, $EthExtra: ty, $($rest:tt)*) => { + impl $crate::evm::runtime::SetWeightLimit for RuntimeCall { + fn set_weight_limit(&mut self, weight_limit: Weight) { + use $crate::pallet::Call as ReviveCall; + match self { + Self::Revive( + ReviveCall::eth_call{ gas_limit, .. } | + ReviveCall::eth_instantiate_with_code{ gas_limit, .. } + ) => *gas_limit = weight_limit, + _ => (), + } + } + } + impl_runtime_apis! { $($rest)* @@ -2017,29 +1989,9 @@ macro_rules! impl_runtime_apis_plus_revive { sp_runtime::traits::Block as BlockT }; - let tx_fee = |call: ::RuntimeCall, dispatch_call: ::RuntimeCall| { - use $crate::frame_support::dispatch::GetDispatchInfo; - - // Get the dispatch info of the actual call dispatched - let mut dispatch_info = dispatch_call.get_dispatch_info(); - dispatch_info.extension_weight = - <$EthExtra>::get_eth_extension(0, 0u32.into()).weight(&dispatch_call); - - // Build the extrinsic - let uxt: ::Extrinsic = - $crate::sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into(); - - // Compute the fee of the extrinsic - $crate::pallet_transaction_payment::Pallet::::compute_fee( - uxt.encoded_size() as u32, - &dispatch_info, - 0u32.into(), - ) - }; - let blockweights: $crate::BlockWeights = ::BlockWeights::get(); - $crate::Pallet::::dry_run_eth_transact(tx, blockweights.max_block, tx_fee) + $crate::Pallet::::dry_run_eth_transact(tx, blockweights.max_block) } fn call( diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index e28071f563e5e..08f90e8eaed77 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -326,11 +326,8 @@ pub struct ExecConfig { /// This does not apply to contract initiated instantatiations. Those will always bump the /// instantiating contract's nonce. pub bump_nonce: bool, - /// If set to `true` deposits will be collected from [`Config::DepositSource`] instead of + /// If set to `true` deposits will be collected from [`T::FeeInfo::deposit_source`] instead of /// free_balance. - /// - /// Only applies if [`Config::DepositSource`] is `Some`. Otherwise it will fall back to collect - /// from fee balance. pub collect_deposit_from_hold: bool, /// Skip all transfers (deposits, contract instantiation, value transfer). /// @@ -338,17 +335,31 @@ pub struct ExecConfig { /// Using it for on-chain code is unsafe as it will allow execution without taking any money /// from the origin, pub unsafe_skip_transfers: bool, + /// The gas price that was chosen for this transaction. + /// + /// It is determined when transforming `eth_transact` into a proper extrinsic. + pub effective_gas_price: Option, } impl ExecConfig { /// Create a default config appropriate when the call originated from a subtrate tx. pub fn new_substrate_tx() -> Self { - Self { bump_nonce: true, collect_deposit_from_hold: false, unsafe_skip_transfers: false } + Self { + bump_nonce: true, + collect_deposit_from_hold: false, + unsafe_skip_transfers: false, + effective_gas_price: None, + } } /// Create a default config appropriate when the call originated from a ethereum tx. - pub fn new_eth_tx() -> Self { - Self { bump_nonce: false, collect_deposit_from_hold: true, unsafe_skip_transfers: false } + pub fn new_eth_tx(effective_gas_price: U256) -> Self { + Self { + bump_nonce: false, + collect_deposit_from_hold: true, + unsafe_skip_transfers: false, + effective_gas_price: Some(effective_gas_price), + } } } diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index 9c4825025231c..868775ed8829f 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -244,6 +244,7 @@ builder!( gas_limit: Weight, storage_deposit_limit: BalanceOf, data: Vec, + effective_gas_price: U256, ) -> DispatchResultWithPostInfo; /// Create a [`EthCallBuilder`] with default values. @@ -255,6 +256,7 @@ builder!( gas_limit: GAS_LIMIT, storage_deposit_limit: deposit_limit::(), data: vec![], + effective_gas_price: 0u32.into(), } } ); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 277c629a204f8..2003c81b6adeb 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -22,9 +22,13 @@ mod sol; use crate::{ self as pallet_revive, + evm::{ + fees::{BlockRatioFee, Info as FeeInfo}, + runtime::{EthExtra, SetWeightLimit}, + }, genesis::{Account, ContractData}, test_utils::*, - AccountId32Mapper, AddressMapper, BalanceOf, BalanceWithDust, CodeInfoOf, Config, + AccountId32Mapper, AddressMapper, BalanceOf, BalanceWithDust, Call, CodeInfoOf, Config, GenesisConfig, Origin, Pallet, PristineCode, }; use frame_support::{ @@ -32,18 +36,36 @@ use frame_support::{ pallet_prelude::EnsureOrigin, parameter_types, traits::{ConstU32, ConstU64, FindAuthor, StorageVersion}, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, IdentityFee, Weight}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, Weight}, }; use pallet_revive_fixtures::compile_module; -use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; +use pallet_transaction_payment::{ChargeTransactionPayment, ConstFeeMultiplier, Multiplier}; use sp_core::U256; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ + generic::Header, traits::{BlakeTwo256, Convert, IdentityLookup, One}, - AccountId32, BuildStorage, Perbill, + AccountId32, BuildStorage, MultiAddress, MultiSignature, Perbill, }; -type Block = frame_system::mocking::MockBlock; +pub type Address = MultiAddress; +pub type Block = sp_runtime::generic::Block, UncheckedExtrinsic>; +pub type Signature = MultiSignature; +pub type SignedExtra = (frame_system::CheckNonce, ChargeTransactionPayment); +pub type UncheckedExtrinsic = + crate::evm::runtime::UncheckedExtrinsic; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct EthExtraImpl; + +impl EthExtra for EthExtraImpl { + type Config = Test; + type Extension = SignedExtra; + + fn get_eth_extension(nonce: u32, tip: BalanceOf) -> Self::Extension { + (frame_system::CheckNonce::from(nonce), ChargeTransactionPayment::from(tip)) + } +} frame_support::construct_runtime!( pub enum Test @@ -227,7 +249,7 @@ impl Test { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - Weight::from_parts(2 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + Weight::from_parts(2 * WEIGHT_REF_TIME_PER_SECOND, 10 * 1024 * 1024), ); pub static ExistentialDeposit: u64 = 1; } @@ -281,7 +303,7 @@ parameter_types! { #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for Test { type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; - type WeightToFee = IdentityFee<::Balance>; + type WeightToFee = BlockRatioFee<1, 1, Self>; type LengthToFee = FixedFee<100, ::Balance>; type FeeMultiplierUpdate = ConstFeeMultiplier; } @@ -332,7 +354,6 @@ parameter_types! { pub static UnstableInterface: bool = true; pub static AllowEvmBytecode: bool = true; pub CheckingAccount: AccountId32 = BOB.clone(); - pub DepositSource: Option = Some(pallet_transaction_payment::HoldReason::Payment.into()); } impl FindAuthor<::AccountId> for Test { @@ -359,10 +380,10 @@ impl Config for Test { type ChainId = ChainId; type FindAuthor = Test; type Precompiles = (precompiles::WithInfo, precompiles::NoInfo); - type DepositSource = DepositSource; + type FeeInfo = FeeInfo; } -impl TryFrom for crate::Call { +impl TryFrom for Call { type Error = (); fn try_from(value: RuntimeCall) -> Result { @@ -373,6 +394,18 @@ impl TryFrom for crate::Call { } } +impl SetWeightLimit for RuntimeCall { + fn set_weight_limit(&mut self, weight_limit: Weight) { + match self { + Self::Contracts( + Call::eth_call { gas_limit, .. } | + Call::eth_instantiate_with_code { gas_limit, .. }, + ) => *gas_limit = weight_limit, + _ => (), + } + } +} + pub struct ExtBuilder { existential_deposit: u64, storage_version: Option, diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 907713a9a2f48..31ae6186fd10a 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -24,7 +24,7 @@ use super::{ use crate::{ address::{create1, create2, AddressMapper}, assert_refcount, assert_return_code, - evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction}, + evm::{fees::InfoT, CallTrace, CallTracer, CallType, GenericTransaction}, exec::Key, limits, storage::DeletionQueueManager, @@ -3353,7 +3353,10 @@ fn gas_price_api_works() { // Call the contract: It echoes back the value returned by the gas price API. let received = builder::bare_call(addr).build_and_unwrap_result(); assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(u64::from_le_bytes(received.data[..].try_into().unwrap()), u64::from(GAS_PRICE)); + assert_eq!( + u64::from_le_bytes(received.data[..].try_into().unwrap()), + u64::try_from(>::evm_gas_price()).unwrap(), + ); }); } @@ -3840,7 +3843,6 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_, _| 0u64, ), EthTransactError::Message(format!( "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" @@ -3855,7 +3857,6 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_, _| 0u64, )); let Contract { addr, .. } = @@ -3875,7 +3876,6 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_, _| 0u64, ), EthTransactError::Message(format!( "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" @@ -3895,7 +3895,6 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_, _| 0u64, ) .is_err(),); @@ -3908,7 +3907,6 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_, _| 0u64, )); // call through contract works when transfers are skipped @@ -3920,7 +3918,6 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_, _| 0u64, )); // works with transfers enabled if we don't incur a storage cost @@ -3934,7 +3931,6 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_, _| 0u64, )); // fails when trying to increase the storage item size @@ -3947,7 +3943,6 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_, _| 0u64, ) .is_err()); }); @@ -4918,7 +4913,7 @@ fn storage_deposit_from_hold_works() { let (binary, code_hash) = compile_module("dummy").unwrap(); ExtBuilder::default().existential_deposit(ed).build().execute_with(|| { let hold_initial = 500_000; - let reason = DepositSource::get().unwrap(); + let reason = ::FeeInfo::deposit_source().unwrap(); ::Currency::set_balance(&ALICE, 1_000_000); ::Currency::set_on_hold(&reason, &ALICE, hold_initial).unwrap(); let mut exec_config = ExecConfig::new_substrate_tx(); diff --git a/substrate/frame/revive/src/tests/sol/tx_info.rs b/substrate/frame/revive/src/tests/sol/tx_info.rs index d1e38c26ac837..520224aff516f 100644 --- a/substrate/frame/revive/src/tests/sol/tx_info.rs +++ b/substrate/frame/revive/src/tests/sol/tx_info.rs @@ -18,10 +18,10 @@ //! The pallet-revive shared VM integration test suite. use crate::{ - evm::runtime::GAS_PRICE, test_utils::{builder::Contract, ALICE, ALICE_ADDR}, tests::{builder, ExtBuilder, Test}, - Code, Config, + vm::evm::U256Converter, + Code, Config, Pallet, }; use alloy_core::{primitives::U256, sol_types::SolInterface}; @@ -49,7 +49,7 @@ fn gasprice_works() { ) .build_and_unwrap_result(); assert_eq!( - U256::from(GAS_PRICE), + >::evm_gas_price().into_revm_u256(), U256::from_be_bytes::<32>(result.data.try_into().unwrap()) ); }); diff --git a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs index 3060a4e4f83a5..5167d91bb9c01 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{address::AddressMapper, evm::runtime::GAS_PRICE, vm::RuntimeCosts}; +use crate::{address::AddressMapper, vm::RuntimeCosts}; use revm::primitives::{Address, U256}; use super::Context; @@ -26,7 +26,7 @@ use crate::{vm::Ext, Config}; /// Gets the gas price of the originating transaction. pub fn gasprice<'ext, E: Ext>(context: Context<'_, 'ext, E>) { gas!(context.interpreter, RuntimeCosts::GasPrice); - push!(context.interpreter, U256::from(GAS_PRICE)); + push!(context.interpreter, U256::from(context.interpreter.extend.effective_gas_price())); } /// Implements the ORIGIN instruction. diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index 79902ef69b20e..560c0a733422f 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -23,7 +23,6 @@ pub mod env; pub use env::SyscallDoc; use crate::{ - evm::runtime::GAS_PRICE, exec::{ExecError, ExecResult, Ext, Key}, gas::ChargedAmount, limits, diff --git a/substrate/frame/revive/src/vm/pvm/env.rs b/substrate/frame/revive/src/vm/pvm/env.rs index fe0572126856f..0a5581612a610 100644 --- a/substrate/frame/revive/src/vm/pvm/env.rs +++ b/substrate/frame/revive/src/vm/pvm/env.rs @@ -548,27 +548,6 @@ pub mod env { )?) } - /// Stores the price for the specified amount of weight into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::weight_to_fee`]. - #[stable] - fn weight_to_fee( - &mut self, - memory: &mut M, - ref_time_limit: u64, - proof_size_limit: u64, - out_ptr: u32, - ) -> Result<(), TrapReason> { - let weight = Weight::from_parts(ref_time_limit, proof_size_limit); - self.charge_gas(RuntimeCosts::WeightToFee)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.get_weight_price(weight).encode(), - false, - already_charged, - )?) - } - /// Stores the immutable data into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::get_immutable_data`]. #[stable] @@ -674,7 +653,7 @@ pub mod env { #[stable] fn gas_price(&mut self, memory: &mut M) -> Result { self.charge_gas(RuntimeCosts::GasPrice)?; - Ok(GAS_PRICE.into()) + Ok(self.ext.effective_gas_price()) } /// Returns the simulated ethereum `BASEFEE` value. diff --git a/substrate/frame/revive/src/vm/runtime_costs.rs b/substrate/frame/revive/src/vm/runtime_costs.rs index 18e9b622f82fc..486ae2e45bf4e 100644 --- a/substrate/frame/revive/src/vm/runtime_costs.rs +++ b/substrate/frame/revive/src/vm/runtime_costs.rs @@ -92,8 +92,6 @@ pub enum RuntimeCosts { Now, /// Weight of calling `seal_gas_limit`. GasLimit, - /// Weight of calling `seal_weight_to_fee`. - WeightToFee, /// Weight of calling `seal_terminate`. Terminate { code_removed: bool }, /// Weight of calling `seal_deposit_event` with the given number of topics and event size. @@ -256,7 +254,6 @@ impl Token for RuntimeCosts { BaseFee => T::WeightInfo::seal_base_fee(), Now => T::WeightInfo::seal_now(), GasLimit => T::WeightInfo::seal_gas_limit(), - WeightToFee => T::WeightInfo::seal_weight_to_fee(), Terminate { code_removed } => T::WeightInfo::seal_terminate(code_removed.into()), DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), SetStorage { new_bytes, old_bytes } => { diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 8ae133fb2b7be..88ae1b8e89e52 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -114,7 +114,6 @@ pub trait WeightInfo { fn seal_block_author() -> Weight; fn seal_block_hash() -> Weight; fn seal_now() -> Weight; - fn seal_weight_to_fee() -> Weight; fn seal_copy_to_contract(n: u32, ) -> Weight; fn seal_call_data_load() -> Weight; fn seal_call_data_copy(n: u32, ) -> Weight; @@ -722,14 +721,7 @@ impl WeightInfo for SubstrateWeight { // Minimum execution time: 239_000 picoseconds. Weight::from_parts(276_000, 0) } - fn seal_weight_to_fee() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 1_595_000 picoseconds. - Weight::from_parts(1_665_000, 0) - } - /// The range of component `n` is `[0, 1048572]`. + /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -1875,14 +1867,7 @@ impl WeightInfo for () { // Minimum execution time: 239_000 picoseconds. Weight::from_parts(276_000, 0) } - fn seal_weight_to_fee() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 1_595_000 picoseconds. - Weight::from_parts(1_665_000, 0) - } - /// The range of component `n` is `[0, 1048572]`. + /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index c7684bd8cece4..5a337691bed4c 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -397,15 +397,6 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the transferred value. fn value_transferred(output: &mut [u8; 32]); - /// Stores the price for the specified amount of gas into the supplied buffer. - /// - /// # Parameters - /// - /// - `ref_time_limit`: The *ref_time* Weight limit to query the price for. - /// - `proof_size_limit`: The *proof_size* Weight limit to query the price for. - /// - `output`: A reference to the output data buffer to write the price. - fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]); - /// Returns the size of the returned data of the last contract call or instantiation. fn return_data_size() -> u64; diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index b0cc4987e2cb7..dc164b19bafe7 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -101,7 +101,6 @@ mod sys { pub fn caller_is_origin() -> ReturnCode; pub fn caller_is_root() -> ReturnCode; pub fn address(out_ptr: *mut u8); - pub fn weight_to_fee(ref_time: u64, proof_size: u64, out_ptr: *mut u8); pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); pub fn ref_time_left() -> u64; pub fn get_immutable_data(out_ptr: *mut u8, out_len_ptr: *mut u32); @@ -399,10 +398,6 @@ impl HostFn for HostFnImpl { unsafe { sys::block_author(output.as_mut_ptr()) } } - fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]) { - unsafe { sys::weight_to_fee(ref_time_limit, proof_size_limit, output.as_mut_ptr()) }; - } - fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]) { unsafe { sys::hash_keccak_256(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } } diff --git a/substrate/frame/transaction-payment/src/lib.rs b/substrate/frame/transaction-payment/src/lib.rs index 87c84907dcf1f..b9065f3838a2f 100644 --- a/substrate/frame/transaction-payment/src/lib.rs +++ b/substrate/frame/transaction-payment/src/lib.rs @@ -606,6 +606,29 @@ impl Pallet { Self::compute_fee_details(len, info, tip).final_fee() } + /// Compute the fee before adjusting for chain utilization. + /// + /// This is used by pallet_revive to calculate the ethereum gas. + pub fn compute_unadjusted_fee( + len: u32, + info: &DispatchInfoOf, + ) -> Option> + where + T::RuntimeCall: Dispatchable, + { + let reverser = NextFeeMultiplier::::get().reciprocal()?; + let (fee_details, unadjusted_weight_fee) = + Self::compute_fee_raw(len, info.total_weight(), 0u32.into(), info.pays_fee, info.class); + let tip = fee_details.tip; + let reversed_fee = if let Some(inclusion_fee) = fee_details.inclusion_fee { + let fee = inclusion_fee.base_fee.saturating_add(inclusion_fee.len_fee); + reverser.saturating_mul_int(fee) + } else { + 0u32.into() + }; + Some(reversed_fee.saturating_add(unadjusted_weight_fee).saturating_add(tip)) + } + /// Compute the fee details for a particular transaction. pub fn compute_fee_details( len: u32, @@ -615,7 +638,7 @@ impl Pallet { where T::RuntimeCall: Dispatchable, { - Self::compute_fee_raw(len, info.total_weight(), tip, info.pays_fee, info.class) + Self::compute_fee_raw(len, info.total_weight(), tip, info.pays_fee, info.class).0 } /// Compute the actual post dispatch fee for a particular transaction. @@ -651,15 +674,19 @@ impl Pallet { post_info.pays_fee(info), info.class, ) + .0 } + /// Internal function that does the actual computation. + /// + /// Returns the fee details and the unadjusted weight fee. fn compute_fee_raw( len: u32, weight: Weight, tip: BalanceOf, pays_fee: Pays, class: DispatchClass, - ) -> FeeDetails> { + ) -> (FeeDetails>, BalanceOf) { if pays_fee == Pays::Yes { // the adjustable part of the fee. let unadjusted_weight_fee = Self::weight_to_fee(weight); @@ -671,12 +698,15 @@ impl Pallet { let len_fee = Self::length_to_fee(len); let base_fee = Self::weight_to_fee(T::BlockWeights::get().get(class).base_extrinsic); - FeeDetails { - inclusion_fee: Some(InclusionFee { base_fee, len_fee, adjusted_weight_fee }), - tip, - } + ( + FeeDetails { + inclusion_fee: Some(InclusionFee { base_fee, len_fee, adjusted_weight_fee }), + tip, + }, + unadjusted_weight_fee, + ) } else { - FeeDetails { inclusion_fee: None, tip } + (FeeDetails { inclusion_fee: None, tip }, 0u32.into()) } }