From c49a1ee0044cb0293f6092484fb701a9a29aca4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 10 Oct 2025 13:41:25 +0400 Subject: [PATCH 01/69] Remove stale TODOs Fix maxPriorityFee RPC Change the EVM call opcodes to use proper gas for subcalls Update tests-evm.yml Update from github-actions[bot] running command 'prdoc --audience runtime_dev' fix fix [pallet-revive] fix subxt submit & add debug statments (#10016) - Fix subxt submit by default it's using `author_submitAndWatchExtrinsic` even though we just want to fire and forget - Add debug instructions to log the signer & nonce of new eth transactions when the node validate the transaction --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Version bumps and prdocs reordering from stable2509 (#9974) This PR backports regular version bumps and prdocs reordering from the stable2509 branch back to master --------- Co-authored-by: ParityReleases Update .github/workflows/tests-evm.yml [Release|CI/CD] Fix polkadot prod docker image (#9975) This PR introduces a workaround to fix failing polkadot production image flow. The initial issue is that, for some reason, our key that used to sign the deb `InRelease` repo noted as expired on the first `apt update` run. But reimport of the same key fixes is it. Until the reason for this issue is fixed, this work around helps to keep the flow working Introduce `/cmd label` for labelling pull requests (#9915) This allows external contributors to set label for their pull request. Closes: https://github.com/paritytech/polkadot-sdk/issues/9873 Use parity-large-persistent-test for merge queue (#10025) Investigating issue with removing persistent runners from merge queue cc https://github.com/paritytech/devops/issues/3875 pallet_revive: Lower the deposit costs for child trie items (#10027) Fixes https://github.com/paritytech/polkadot-sdk/issues/9246 --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> pallet_revive: Fix incorrect `block.gaslimit` (#10026) Fixes https://github.com/paritytech/contract-issues/issues/112 --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> update tests-evm --- .github/workflows/cmd.yml | 2 +- .github/workflows/tests-evm.yml | 9 +- Cargo.lock | 2 +- .../assets/asset-hub-westend/src/lib.rs | 2 +- .../polkadot-omni-node/lib/src/nodes/mod.rs | 2 +- prdoc/pr_10018.prdoc | 23 +++ substrate/frame/revive/Cargo.toml | 1 + .../revive/fixtures/contracts/Callee.sol | 4 + .../revive/fixtures/contracts/Caller.sol | 20 +++ substrate/frame/revive/rpc/src/client.rs | 4 +- substrate/frame/revive/rpc/src/lib.rs | 2 +- substrate/frame/revive/src/evm/fees.rs | 38 ++--- substrate/frame/revive/src/evm/runtime.rs | 5 +- substrate/frame/revive/src/exec.rs | 140 +++++++++++---- substrate/frame/revive/src/exec/mock_ext.rs | 11 +- substrate/frame/revive/src/exec/tests.rs | 159 ++++-------------- substrate/frame/revive/src/gas.rs | 73 ++++---- substrate/frame/revive/src/lib.rs | 13 +- .../frame/revive/src/tests/precompiles.rs | 5 +- substrate/frame/revive/src/tests/pvm.rs | 10 +- .../frame/revive/src/tests/sol/contract.rs | 135 ++++++++++++++- .../frame/revive/src/tests/sol/system.rs | 4 +- substrate/frame/revive/src/tracing.rs | 2 +- .../src/vm/evm/instructions/contract.rs | 44 ++--- .../evm/instructions/contract/call_helpers.rs | 6 +- .../revive/src/vm/evm/instructions/system.rs | 1 - substrate/frame/revive/src/vm/pvm.rs | 11 +- 27 files changed, 437 insertions(+), 291 deletions(-) create mode 100644 prdoc/pr_10018.prdoc diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 441e0910e1b1e..8d11cdd79c59d 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -268,7 +268,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@db4a51e5b21b6b7c0c69dd6c93c5bf3142490885 # v1.34.1 with: app-id: ${{ secrets.CMD_BOT_APP_ID }} private-key: ${{ secrets.CMD_BOT_APP_KEY }} diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 9dcafac106d60..a958eed12b620 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: paritytech/evm-test-suite - ref: c2422cace2fb8a4337fc1c704c49e458c8a79d6b + ref: 84e536af80513f87bc16f6c7b7dbf796fe9010ab path: evm-test-suite - uses: denoland/setup-deno@v1 @@ -68,6 +68,13 @@ jobs: echo "== Running evm tests ==" START_REVIVE_DEV_NODE=true START_ETH_RPC=true deno task test:evm + - name: Collect tests results + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: evm-test-suite-${{ github.sha }} + path: evm-test-suite/test-logs/matter-labs-tests.log + confirm-required-test-evm-jobs-passed: runs-on: ubuntu-latest name: All test misc tests passed diff --git a/Cargo.lock b/Cargo.lock index ae938b2016643..264b63ab653c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13288,7 +13288,7 @@ dependencies = [ "hex-literal", "humantime-serde", "impl-trait-for-tuples", - "k256", + "itertools 0.11.0", "log", "num-bigint", "num-integer", 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 c39a2dab7fbab..4083c738767ea 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -144,7 +144,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_020_004, + spec_version: 1_020_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs b/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs index a2a897c378cdc..d66387668bc97 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs @@ -19,4 +19,4 @@ pub mod aura; /// The current node version for cumulus official binaries, which takes the basic /// SemVer form `..`. It should correspond to the latest /// `polkadot` version of a stable release. -pub const NODE_VERSION: &'static str = "1.20.1"; +pub const NODE_VERSION: &'static str = "1.20.0"; diff --git a/prdoc/pr_10018.prdoc b/prdoc/pr_10018.prdoc new file mode 100644 index 0000000000000..6fad1e352df74 --- /dev/null +++ b/prdoc/pr_10018.prdoc @@ -0,0 +1,23 @@ +title: 'pallet_revive: Change EVM call opcodes to respect the gas limit passed' +doc: +- audience: Runtime Dev + description: |- + So far the EVM family of call opcodes did ignore the `gas` argument passed to them. The consequence was that we were not able to limit the resource usage of sub contract calls. This PR changes that. **Gas is now fully functional on the EVM backend.** + + The resources of any sub contract call are now effectively limited. This is both true for `Weight` and storage deposit. The algorithm works in a way that if you pass `x%` of the current `GAS` the the `CALL` opcode the sub call will have `x%` of currently available `Weight` and storage deposit available. This allows the caller to always make sure to execute code after retuning from a sub call. + + ### Changes to the gas meter + + I needed to change the gas meter to track `gas_consumed` instead of `gas_left`. Otherwise it is not possible to know the total amount of gas spent for a call stack that is not unwinded, yet. + + ### Followup + - Implement a new PVM syscall that takes the new unified gas instead of `Weight` and storage deposit limit + - Change resolc to use this new syscall + - Enable the test added here to run on resolc +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-eth-rpc + bump: major +- name: pallet-revive-fixtures + bump: major diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 3c655612e490c..516c0a6638651 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -69,6 +69,7 @@ subxt-signer = { workspace = true, optional = true, features = ["unstable-eth"] alloy-consensus = { workspace = true, default-features = true } array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } +itertools = { workspace = true } pretty_assertions = { workspace = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } diff --git a/substrate/frame/revive/fixtures/contracts/Callee.sol b/substrate/frame/revive/fixtures/contracts/Callee.sol index 8407f7962bdb2..eef33113cb444 100644 --- a/substrate/frame/revive/fixtures/contracts/Callee.sol +++ b/substrate/frame/revive/fixtures/contracts/Callee.sol @@ -32,4 +32,8 @@ contract Callee { stop() } } + + function consumeAllReftime() external { + while (true) {} + } } diff --git a/substrate/frame/revive/fixtures/contracts/Caller.sol b/substrate/frame/revive/fixtures/contracts/Caller.sol index 3a4f1c440ff1d..dca1e6861c5e9 100644 --- a/substrate/frame/revive/fixtures/contracts/Caller.sol +++ b/substrate/frame/revive/fixtures/contracts/Caller.sol @@ -9,6 +9,8 @@ contract ChildRevert { } contract Caller { + uint256 public data; + function normal(address _callee, uint64 _value, bytes memory _data, uint64 _gas) external returns (bool success, bytes memory output) @@ -65,4 +67,22 @@ contract Caller { } } } + + function callPartialGas(address _callee, bytes memory _data, uint64 _gasDivisor, uint8 _callType) + external + returns (bool success) + { + uint256 gas = gasleft() / _gasDivisor; + bytes memory output; + if (_callType == 0) { + (success, output) = _callee.call{gas: gas }(_data); + } else if (_callType == 1) { + (success, output) = _callee.staticcall{gas: gas }(_data); + } else if (_callType == 2) { + (success, output) = _callee.delegatecall{gas: gas }(_data); + } else { + revert("unknown call type"); + } + data = 42; + } } diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 8fbe67a4a0bbb..770937fbf1940 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -448,14 +448,14 @@ impl Client { pub async fn submit( &self, call: subxt::tx::DefaultPayload, - ) -> Result { + ) -> Result<(), ClientError> { let ext = self.api.tx().create_unsigned(&call).map_err(ClientError::from)?; let hash: H256 = self .rpc_client .request("author_submitExtrinsic", rpc_params![to_hex(ext.encoded())]) .await?; log::debug!(target: LOG_TARGET, "Submitted transaction with substrate hash: {hash:?}"); - Ok(hash) + Ok(()) } /// Get an EVM transaction receipt by hash. diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index d742ad937f3b9..7117a09263687 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -177,7 +177,7 @@ impl EthRpcServer for EthRpcServerImpl { err })?; - log::trace!(target: LOG_TARGET, "send_raw_transaction ethereum_hash: {hash:?} substrate_hash: {substrate_hash:?}"); + log::debug!(target: LOG_TARGET, "send_raw_transaction ethereum_hash: {hash:?} substrate_hash: {substrate_hash:?}"); // Wait for the transaction to be included in a block if automine is enabled if let Some(mut receiver) = receiver { diff --git a/substrate/frame/revive/src/evm/fees.rs b/substrate/frame/revive/src/evm/fees.rs index 8e3724fe09693..2a4ec341fb7d5 100644 --- a/substrate/frame/revive/src/evm/fees.rs +++ b/substrate/frame/revive/src/evm/fees.rs @@ -130,7 +130,7 @@ pub trait InfoT: seal::Sealed { } /// Convert a weight to an unadjusted fee. - fn weight_to_fee(_weight: &Weight, _combinator: Combinator) -> BalanceOf { + fn weight_to_fee(_weight: &Weight) -> BalanceOf { Zero::zero() } @@ -158,14 +158,6 @@ pub trait InfoT: seal::Sealed { } } -/// Which function to use in order to combine `ref_time` and `proof_size` to a fee. -pub enum Combinator { - /// Minimum function. - Min, - /// Maximum function. - Max, -} - impl BlockRatioFee { const REF_TIME_TO_FEE: FixedU128 = { assert!(P > 0 && Q > 0); @@ -179,26 +171,17 @@ impl BlockRatioFee { FixedU128::from_rational(max_weight.ref_time().into(), max_weight.proof_size().into()); Self::REF_TIME_TO_FEE.saturating_mul(ratio) } - - /// Calculate the fee for a weight. - fn weight_to_fee(weight: &Weight, combinator: Combinator) -> BalanceOf { - let ref_time_fee = Self::REF_TIME_TO_FEE - .saturating_mul_int(BalanceOf::::saturated_from(weight.ref_time())); - let proof_size_fee = Self::proof_size_to_fee() - .saturating_mul_int(BalanceOf::::saturated_from(weight.proof_size())); - - match combinator { - Combinator::Max => ref_time_fee.max(proof_size_fee), - Combinator::Min => ref_time_fee.min(proof_size_fee), - } - } } impl WeightToFee for BlockRatioFee { type Balance = BalanceOf; fn weight_to_fee(weight: &Weight) -> Self::Balance { - Self::weight_to_fee(weight, Combinator::Max) + let ref_time_fee = Self::REF_TIME_TO_FEE + .saturating_mul_int(BalanceOf::::saturated_from(weight.ref_time())); + let proof_size_fee = Self::proof_size_to_fee() + .saturating_mul_int(BalanceOf::::saturated_from(weight.proof_size())); + ref_time_fee.max(proof_size_fee) } } @@ -244,8 +227,8 @@ where /// Calculate the fee using the weight instead of a dispatch info. fn tx_fee_from_weight(encoded_len: u32, weight: &Weight) -> BalanceOf { let fixed_fee = Self::fixed_fee(encoded_len); - let weight_fee = Self::next_fee_multiplier() - .saturating_mul_int(Self::weight_to_fee(weight, Combinator::Max)); + let weight_fee = + Self::next_fee_multiplier().saturating_mul_int(Self::weight_to_fee(weight)); fixed_fee.saturating_add(weight_fee) } @@ -254,7 +237,6 @@ where &::BlockWeights::get() .get(DispatchClass::Normal) .base_extrinsic, - Combinator::Max, ) .saturating_add(Self::length_to_fee(encoded_len)) } @@ -316,8 +298,8 @@ where uxt.encoded_size() as u32 } - fn weight_to_fee(weight: &Weight, combinator: Combinator) -> BalanceOf { - ::WeightToFee::weight_to_fee(&weight, combinator) + fn weight_to_fee(weight: &Weight) -> BalanceOf { + ::WeightToFee::weight_to_fee(weight) } /// Convert an unadjusted fee back to a weight. diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 5674088de4efc..39b346db3f677 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -313,9 +313,8 @@ pub trait EthExtra { InvalidTransaction::Call })?; - log::trace!(target: LOG_TARGET, "Decoded Ethereum transaction with signer: {signer_addr:?} nonce: {nonce:?}"); - let call_info = - create_call::(tx, Some((encoded_len as u32, payload.to_vec())), true)?; + log::debug!(target: LOG_TARGET, "Decoded Ethereum transaction with signer: {signer_addr:?} nonce: {nonce:?}"); + let call_info = create_call::(tx, Some(encoded_len as u32))?; let storage_credit = ::Currency::withdraw( &signer, call_info.storage_deposit, diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 60901419c742d..abd46820d0818 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -62,7 +62,7 @@ use sp_core::{ use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::{ traits::{BadOrigin, Bounded, Saturating, TrailingZeroInput, Zero}, - DispatchError, SaturatedConversion, + DispatchError, FixedPointNumber, FixedU128, SaturatedConversion, }; #[cfg(test)] @@ -192,6 +192,22 @@ impl Origin { } } } + +/// Argument passed by a contact to describe the amount of resources allocated to a cross contact +/// call. +pub enum CallResources { + /// Resources encoded using their actual values. + Precise { weight: Weight, deposit_limit: U256 }, + /// Resources encoded as unified ethereum gas. + Ethereum(U256), +} + +impl Default for CallResources { + fn default() -> Self { + Self::Precise { weight: Default::default(), deposit_limit: Default::default() } + } +} + /// Environment functions only available to host functions. pub trait Ext: PrecompileWithInfoExt { /// Execute code in the current frame. @@ -199,8 +215,7 @@ pub trait Ext: PrecompileWithInfoExt { /// Returns the code size of the called contract. fn delegate_call( &mut self, - gas_limit: Weight, - deposit_limit: U256, + call_resources: &CallResources, address: H160, input_data: Vec, ) -> Result<(), ExecError>; @@ -283,8 +298,7 @@ pub trait PrecompileExt: sealing::Sealed { /// Call (possibly transferring some amount of funds) into the specified account. fn call( &mut self, - gas_limit: Weight, - deposit_limit: U256, + call_resources: &CallResources, to: &H160, value: U256, input_data: Vec, @@ -447,6 +461,7 @@ pub trait PrecompileExt: sealing::Sealed { /// The amount of gas left in eth gas units. fn gas_left(&self) -> u64; + /// Returns the storage entry of the executing account by the given `key`. /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or @@ -819,7 +834,7 @@ where false, value, &input_data, - Weight::zero(), + Default::default(), ); }); @@ -1745,6 +1760,45 @@ where } true } + + /// Calc the limits for a cross contract call. + fn calc_limits(&self, call_resources: &CallResources) -> (Weight, BalanceOf) { + match call_resources { + CallResources::Precise { weight, deposit_limit } => + (*weight, (*deposit_limit).saturated_into()), + CallResources::Ethereum(gas_limit) => { + // the resources of the subcall are relative to the available resources + let available: BalanceOf = self.gas_left().saturated_into(); + let gas_limit = (*gas_limit).saturated_into::>().min(available); + let ratio = FixedU128::from_rational( + gas_limit.saturated_into(), + available.max(1u32.into()).saturated_into(), + ); + + // weight limit is expected to be set to we can just multiply directly + let weight_limit = { + let weight_left = self.top_frame().nested_gas.gas_left(); + Weight::from_parts( + ratio.saturating_mul_int(weight_left.ref_time()), + ratio.saturating_mul_int(weight_left.proof_size()), + ) + }; + + // the gas_limit is in unadjusted fee + let deposit_limit = { + let weight_fee = T::FeeInfo::weight_to_fee(&weight_limit); + gas_limit.saturating_sub(weight_fee).min( + ratio.saturating_mul_int( + T::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(self.top_frame().nested_storage.available()), + ), + ) + }; + + (weight_limit, T::FeeInfo::next_fee_multiplier().saturating_mul_int(deposit_limit)) + }, + } + } } impl<'a, T, E> Ext for Stack<'a, T, E> @@ -1754,8 +1808,7 @@ where { fn delegate_call( &mut self, - gas_limit: Weight, - deposit_limit: U256, + call_resources: &CallResources, address: H160, input_data: Vec, ) -> Result<(), ExecError> { @@ -1763,6 +1816,8 @@ where // This is for example the case for unknown code hashes or creating the frame fails. *self.last_frame_output_mut() = Default::default(); + let (weight, deposit_limit) = self.calc_limits(call_resources); + let top_frame = self.top_frame_mut(); let contract_info = top_frame.contract_info().clone(); let account_id = top_frame.account_id.clone(); @@ -1777,8 +1832,8 @@ where }), }, value, - gas_limit, - deposit_limit.saturated_into::>(), + weight, + deposit_limit, self.is_read_only(), &input_data, )? { @@ -1945,8 +2000,7 @@ where fn call( &mut self, - gas_limit: Weight, - deposit_limit: U256, + call_resources: &CallResources, dest_addr: &H160, value: U256, input_data: Vec, @@ -1962,6 +2016,8 @@ where // This is for example the case for balance transfers or when creating the frame fails. *self.last_frame_output_mut() = Default::default(); + let (weight, deposit_limit) = self.calc_limits(call_resources); + let try_call = || { // Enable read-only access if requested; cannot disable it if already set. let is_read_only = read_only || self.is_read_only(); @@ -1991,8 +2047,8 @@ where if let Some(executable) = self.push_frame( FrameArgs::Call { dest: dest.clone(), cached_info, delegated_call: None }, value, - gas_limit, - deposit_limit.saturated_into::>(), + weight, + deposit_limit, is_read_only, &input_data, )? { @@ -2304,40 +2360,56 @@ where fn gas_left(&self) -> u64 { let frame = self.top_frame(); - if let Some((encoded_len, base_weight)) = self.exec_config.collect_deposit_from_hold { - // when using the txhold we know the overall available fee by looking at the tx credit + + // when using the txhold we know the overall available fee by looking at the tx credit + // we need to use that to limit gas_left because in that case no storage deposit limit is + // set + let max_by_credit_hold = if let Some((encoded_len, base_weight)) = + self.exec_config.collect_deposit_from_hold + { // we work backwards: the gas_left is the overall fee minus what was already consumed + let (deposit_consumed, weight_consumed) = + self.frames.iter().chain(core::iter::once(&self.first_frame)).fold( + (StorageDeposit::default(), Weight::default()), + |(deposit, weight), frame| { + ( + deposit.saturating_add(&frame.nested_storage.consumed()), + weight.saturating_add(frame.nested_gas.gas_consumed()), + ) + }, + ); let weight_fee_consumed = T::FeeInfo::tx_fee_from_weight( encoded_len, - &frame.nested_gas.gas_consumed().saturating_add(base_weight), + &weight_consumed.saturating_add(base_weight), ); let available = T::FeeInfo::remaining_txfee().saturating_sub(weight_fee_consumed); - let deposit_consumed = self - .frames - .iter() - .chain(core::iter::once(&self.first_frame)) - .fold(StorageDeposit::default(), |acc, frame| { - acc.saturating_add(&frame.nested_storage.consumed()) - }); deposit_consumed.available(&available) } else { - // when not using the hold we expect the transaction to contain a limit for the storage - // deposit we work forwards: add up what is left from both meters - // in case no storage limit is set we limit by all the free balance of the signer + BalanceOf::::max_value() + }; + + // this is the actual limit derived from what is set in the meter + let max_by_meter = { use frame_support::traits::tokens::{Fortitude, Preservation}; - let weight_fee_available = - T::FeeInfo::weight_to_fee(&frame.nested_gas.gas_left(), Combinator::Min); - let available_balance = self + let weight_fee_available = T::FeeInfo::weight_to_fee(&frame.nested_gas.gas_left()); + + // in the dry run no deposit limit is set. + // this means its only limited by the origins free balance + let deposit_available = self .origin .account_id() .map(|acc| { T::Currency::reducible_balance(acc, Preservation::Preserve, Fortitude::Polite) }) - .unwrap_or(BalanceOf::::max_value()); - let deposit_available = frame.nested_storage.available().min(available_balance); + .unwrap_or(BalanceOf::::max_value()) + .min(frame.nested_storage.available()); + weight_fee_available.saturating_add(deposit_available) - } - .saturated_into() + }; + + T::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(max_by_credit_hold.min(max_by_meter)) + .saturated_into() } fn get_storage(&mut self, key: &Key) -> Option> { diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index 8338b66825bb9..fc35c66c6488f 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -17,7 +17,10 @@ #![cfg(test)] use crate::{ - exec::{AccountIdOf, ExecError, Ext, Key, Origin, PrecompileExt, PrecompileWithInfoExt}, + exec::{ + AccountIdOf, CallResources, ExecError, Ext, Key, Origin, PrecompileExt, + PrecompileWithInfoExt, + }, gas::GasMeter, precompiles::Diff, storage::{ContractInfo, WriteOutcome}, @@ -47,8 +50,7 @@ impl PrecompileExt for MockExt { fn call( &mut self, - _gas_limit: Weight, - _deposit_limit: U256, + _call_resources: &CallResources, _to: &H160, _value: U256, _input_data: Vec, @@ -258,8 +260,7 @@ impl PrecompileWithInfoExt for MockExt { impl Ext for MockExt { fn delegate_call( &mut self, - _gas_limit: Weight, - _deposit_limit: U256, + _call_resources: &CallResources, _address: H160, _input_data: Vec, ) -> Result<(), ExecError> { diff --git a/substrate/frame/revive/src/exec/tests.rs b/substrate/frame/revive/src/exec/tests.rs index 11c2cb65e7e53..c415b07a16bdf 100644 --- a/substrate/frame/revive/src/exec/tests.rs +++ b/substrate/frame/revive/src/exec/tests.rs @@ -375,7 +375,7 @@ fn correct_transfer_on_delegate_call() { let delegate_ch = MockLoader::insert(Call, move |ctx, _| { assert_eq!(ctx.ext.value_transferred(), evm_value); - ctx.ext.delegate_call(Weight::zero(), U256::zero(), CHARLIE_ADDR, Vec::new())?; + ctx.ext.delegate_call(&Default::default(), CHARLIE_ADDR, Vec::new())?; Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); @@ -409,7 +409,7 @@ fn delegate_call_missing_contract() { }); let delegate_ch = MockLoader::insert(Call, move |ctx, _| { - ctx.ext.delegate_call(Weight::zero(), U256::zero(), CHARLIE_ADDR, Vec::new())?; + ctx.ext.delegate_call(&Default::default(), CHARLIE_ADDR, Vec::new())?; Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) }); @@ -633,15 +633,7 @@ fn max_depth() { let value = 0; let recurse_ch = MockLoader::insert(Call, |ctx, _| { // Try to call into yourself. - let r = ctx.ext.call( - Weight::zero(), - U256::zero(), - &BOB_ADDR, - U256::zero(), - vec![], - true, - false, - ); + let r = ctx.ext.call(&Default::default(), &BOB_ADDR, U256::zero(), vec![], true, false); ReachedBottom::mutate(|reached_bottom| { if !*reached_bottom { @@ -696,15 +688,8 @@ fn caller_returns_proper_values() { // Call into CHARLIE contract. assert_matches!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false - ), + ctx.ext + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false), Ok(_) ); exec_success() @@ -760,15 +745,8 @@ fn origin_returns_proper_values() { // Call into CHARLIE contract. assert_matches!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false - ), + ctx.ext + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false), Ok(_) ); exec_success() @@ -916,7 +894,7 @@ fn caller_is_origin_returns_proper_values() { assert!(ctx.ext.caller_is_origin(false)); // BOB calls CHARLIE ctx.ext - .call(Weight::zero(), U256::zero(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) .map(|_| ctx.ext.last_frame_output().clone()) }); @@ -1004,7 +982,7 @@ fn root_caller_succeeds_with_consecutive_calls() { assert!(ctx.ext.caller_is_root(false)); // BOB calls CHARLIE. ctx.ext - .call(Weight::zero(), U256::zero(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) .map(|_| ctx.ext.last_frame_output().clone()) }); @@ -1035,15 +1013,8 @@ fn address_returns_proper_values() { // Call into charlie contract. assert_matches!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false - ), + ctx.ext + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false), Ok(_) ); exec_success() @@ -1368,15 +1339,7 @@ fn in_memory_changes_not_discarded() { info.storage_byte_deposit = 42; assert_eq!( ctx.ext - .call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false - ) + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) .map(|_| ctx.ext.last_frame_output().clone()), exec_trapped() ); @@ -1387,7 +1350,7 @@ fn in_memory_changes_not_discarded() { let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext - .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![99], true, false) .is_ok()); exec_trapped() }); @@ -1423,8 +1386,7 @@ fn recursive_call_during_constructor_is_balance_transfer() { // Calling ourselves during the constructor will trigger a balance // transfer since no contract exist yet. assert_ok!(ctx.ext.call( - Weight::zero(), - U256::zero(), + &Default::default(), &addr, (balance - 1).into(), vec![], @@ -1435,8 +1397,7 @@ fn recursive_call_during_constructor_is_balance_transfer() { // Should also work with call data set as it is ignored when no // contract is deployed. assert_ok!(ctx.ext.call( - Weight::zero(), - U256::zero(), + &Default::default(), &addr, 1u32.into(), vec![1, 2, 3, 4], @@ -1480,15 +1441,8 @@ fn cannot_send_more_balance_than_available_to_self() { let balance = ctx.ext.balance(); assert_err!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &addr, - (balance + 1).into(), - vec![], - true, - false - ), + ctx.ext + .call(&Default::default(), &addr, (balance + 1).into(), vec![], true, false), >::TransferFailed, ); exec_success() @@ -1523,7 +1477,7 @@ fn call_reentry_direct_recursion() { let code_bob = MockLoader::insert(Call, |ctx, _| { let dest = H160::from_slice(ctx.input_data.as_ref()); ctx.ext - .call(Weight::zero(), U256::zero(), &dest, U256::zero(), vec![], false, false) + .call(&Default::default(), &dest, U256::zero(), vec![], false, false) .map(|_| ctx.ext.last_frame_output().clone()) }); @@ -1568,15 +1522,7 @@ fn call_deny_reentry() { let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { ctx.ext - .call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - false, - false, - ) + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], false, false) .map(|_| ctx.ext.last_frame_output().clone()) } else { exec_success() @@ -1586,7 +1532,7 @@ fn call_deny_reentry() { // call BOB with input set to '1' let code_charlie = MockLoader::insert(Call, |ctx, _| { ctx.ext - .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![1], true, false) + .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![1], true, false) .map(|_| ctx.ext.last_frame_output().clone()) }); @@ -1690,7 +1636,7 @@ fn nonce() { // a plain call should not influence the account counter ctx.ext - .call(Weight::zero(), U256::zero(), &addr, U256::zero(), vec![], false, false) + .call(&Default::default(), &addr, U256::zero(), vec![], false, false) .unwrap(); assert_eq!(System::account_nonce(ALICE), alice_nonce); @@ -2197,15 +2143,7 @@ fn get_transient_storage_works() { ); assert_eq!( ctx.ext - .call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false, - ) + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false,) .map(|_| ctx.ext.last_frame_output().clone()), exec_success() ); @@ -2227,7 +2165,7 @@ fn get_transient_storage_works() { let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext - .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![99], true, false) .is_ok()); // CHARLIE can not read BOB`s storage. assert_eq!(ctx.ext.get_transient_storage(storage_key_1), None); @@ -2303,15 +2241,7 @@ fn rollback_transient_storage_works() { ); assert_eq!( ctx.ext - .call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false - ) + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) .map(|_| ctx.ext.last_frame_output().clone()), exec_trapped() ); @@ -2329,7 +2259,7 @@ fn rollback_transient_storage_works() { let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext - .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![99], true, false) .is_ok()); exec_trapped() }); @@ -2412,8 +2342,7 @@ fn last_frame_output_works_on_instantiate() { // Balance transfers should reset the output ctx.ext .call( - Weight::MAX, - U256::MAX, + &CallResources::Precise { weight: Weight::MAX, deposit_limit: U256::MAX }, &address, Pallet::::convert_native_to_evm(1), vec![], @@ -2495,15 +2424,7 @@ fn last_frame_output_works_on_nested_call() { ); ctx.ext - .call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false, - ) + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -2522,7 +2443,7 @@ fn last_frame_output_works_on_nested_call() { assert!(ctx .ext - .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![99], true, false) .is_ok()); assert_eq!( ctx.ext.last_frame_output(), @@ -2561,8 +2482,7 @@ fn last_frame_output_is_always_reset() { *ctx.ext.last_frame_output_mut() = output_revert(); assert_eq!( ctx.ext.call( - Weight::zero(), - U256::zero(), + &Default::default(), &H160::zero(), U256::max_value(), vec![], @@ -2576,8 +2496,7 @@ fn last_frame_output_is_always_reset() { // An unknown code hash should succeed but clear the output. *ctx.ext.last_frame_output_mut() = output_revert(); assert_ok!(ctx.ext.delegate_call( - Weight::zero(), - U256::zero(), + &Default::default(), H160([0xff; 20]), Default::default() )); @@ -2680,24 +2599,18 @@ fn correct_immutable_data_in_delegate_call() { // In a regular call, we should witness the callee immutable data assert_eq!( ctx.ext - .call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false, - ) + .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false,) .map(|_| ctx.ext.last_frame_output().data.clone()), Ok(vec![2]), ); // Also in a delegate call, we should witness the callee immutable data assert_eq!( - ctx.ext - .delegate_call(Weight::zero(), U256::zero(), CHARLIE_ADDR, Vec::new()) - .map(|_| ctx.ext.last_frame_output().data.clone()), + ctx.ext.delegate_call(&Default::default(), CHARLIE_ADDR, Vec::new()).map(|_| ctx + .ext + .last_frame_output() + .data + .clone()), Ok(vec![2]) ); diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index 038e58b3d93cc..9da6ce7266b93 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -21,7 +21,7 @@ use frame_support::{ weights::Weight, DefaultNoBound, }; -use sp_runtime::{traits::Zero, DispatchError}; +use sp_runtime::DispatchError; #[cfg(test)] use std::{any::Any, fmt::Debug}; @@ -142,10 +142,11 @@ pub struct ErasedToken { #[derive(DefaultNoBound)] pub struct GasMeter { gas_limit: Weight, - /// Amount of gas left from initial gas limit. Can reach zero. - gas_left: Weight, - /// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value. - gas_left_lowest: Weight, + /// Amount of gas already consumed. Must be < `gas_limit`. + gas_consumed: Weight, + /// Due to `adjust_gas` and `nested` the `gas_consumed` can temporarily peak above its final + /// value. + gas_consumed_highest: Weight, /// The amount of resources that was consumed by the execution engine. /// We have to track it separately in order to avoid the loss of precision that happens when /// converting from ref_time to the execution engine unit. @@ -159,8 +160,8 @@ impl GasMeter { pub fn new(gas_limit: Weight) -> Self { GasMeter { gas_limit, - gas_left: gas_limit, - gas_left_lowest: gas_limit, + gas_consumed: Default::default(), + gas_consumed_highest: Default::default(), engine_meter: EngineMeter::new(gas_limit), _phantom: PhantomData, #[cfg(test)] @@ -173,24 +174,21 @@ impl GasMeter { /// This should only be used by the primordial frame in a sequence of calls - every subsequent /// frame should use [`nested`](Self::nested). pub fn nested_take_all(&mut self) -> Self { - let gas_left = self.gas_left; - self.gas_left -= gas_left; - GasMeter::new(gas_left) + GasMeter::new(self.gas_left()) } /// Create a new gas meter for a nested call by removing gas from the current meter. pub fn nested(&mut self, amount: Weight) -> Self { - let amount = amount.min(self.gas_left); - self.gas_left -= amount; - GasMeter::new(amount) + GasMeter::new(self.gas_left().min(amount)) } /// Absorb the remaining gas of a nested meter after we are done using it. pub fn absorb_nested(&mut self, nested: Self) { - self.gas_left_lowest = (self.gas_left + nested.gas_limit) - .saturating_sub(nested.gas_required()) - .min(self.gas_left_lowest); - self.gas_left += nested.gas_left; + self.gas_consumed_highest = self + .gas_consumed + .saturating_add(nested.gas_required()) + .max(self.gas_consumed_highest); + self.gas_consumed += nested.gas_consumed; } /// Account for used gas. @@ -214,7 +212,14 @@ impl GasMeter { let amount = token.weight(); // It is OK to not charge anything on failure because we always charge _before_ we perform // any action - self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| Error::::OutOfGas)?; + let consumed = { + let consumed = self.gas_consumed.saturating_add(amount); + if consumed.any_gt(self.gas_limit) { + Err(>::OutOfGas)?; + } + consumed + }; + self.gas_consumed = consumed; Ok(ChargedAmount(amount)) } @@ -233,10 +238,10 @@ impl GasMeter { /// refunded to match the actual amount. pub fn adjust_gas>(&mut self, charged_amount: ChargedAmount, token: Tok) { if token.influence_lowest_gas_limit() { - self.gas_left_lowest = self.gas_left_lowest(); + self.gas_consumed_highest = self.gas_required(); } let adjustment = charged_amount.0.saturating_sub(token.weight()); - self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit); + self.gas_consumed = self.gas_consumed.saturating_sub(adjustment); } /// Hand over the gas metering responsibility from the executor to this meter. @@ -251,10 +256,12 @@ impl GasMeter { let weight_consumed = self .engine_meter .set_fuel(engine_fuel.try_into().map_err(|_| Error::::OutOfGas)?); - self.gas_left - .checked_reduce(weight_consumed) - .ok_or_else(|| Error::::OutOfGas)?; - Ok(RefTimeLeft(self.gas_left.ref_time())) + self.gas_consumed.saturating_accrue(weight_consumed); + if self.gas_consumed.any_gt(self.gas_limit) { + self.gas_consumed = self.gas_limit; + Err(>::OutOfGas)?; + } + Ok(RefTimeLeft(self.gas_left().ref_time())) } /// Hand over the gas metering responsibility from this meter to the executor. @@ -275,17 +282,17 @@ impl GasMeter { /// This can be different from `gas_spent` because due to `adjust_gas` the amount of /// spent gas can temporarily drop and be refunded later. pub fn gas_required(&self) -> Weight { - self.gas_limit.saturating_sub(self.gas_left_lowest()) + self.gas_consumed_highest.max(self.gas_consumed) } /// Returns how much gas was spent pub fn gas_consumed(&self) -> Weight { - self.gas_limit.saturating_sub(self.gas_left) + self.gas_consumed } /// Returns how much gas left from the initial budget. pub fn gas_left(&self) -> Weight { - self.gas_left + self.gas_limit.saturating_sub(self.gas_consumed) } /// The amount of gas in terms of engine gas. @@ -312,17 +319,13 @@ impl GasMeter { .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error }) } - fn gas_left_lowest(&self) -> Weight { - self.gas_left_lowest.min(self.gas_left) - } - #[cfg(test)] pub fn tokens(&self) -> &[ErasedToken] { &self.tokens } pub fn consume_all(&mut self) { - self.gas_left = Zero::zero(); + self.gas_consumed = self.gas_limit; } } @@ -419,7 +422,7 @@ mod tests { let mut gas_meter = GasMeter::::new(test_weight); let gas_for_nested_call = gas_meter.nested(10000.into()); - assert_eq!(gas_meter.gas_left(), 40000.into()); + assert_eq!(gas_meter.gas_consumed(), 0.into()); assert_eq!(gas_for_nested_call.gas_left(), 10000.into()) } @@ -429,7 +432,7 @@ mod tests { let mut gas_meter = GasMeter::::new(test_weight); let gas_for_nested_call = gas_meter.nested(test_weight); - assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0)); + assert_eq!(gas_meter.gas_consumed(), Weight::from_parts(0, 0)); assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) } @@ -439,7 +442,7 @@ mod tests { let mut gas_meter = GasMeter::::new(test_weight); let gas_for_nested_call = gas_meter.nested(test_weight + 10000.into()); - assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0)); + assert_eq!(gas_meter.gas_consumed(), Weight::from_parts(0, 0)); assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index c869d5d65402a..1b341844e3c68 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -86,7 +86,10 @@ use frame_system::{ }; use scale_info::TypeInfo; use sp_runtime::{ - traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, UniqueSaturatedInto, Zero}, + traits::{ + BadOrigin, Bounded, Convert, Dispatchable, Saturating, UniqueSaturatedFrom, + UniqueSaturatedInto, Zero, + }, AccountId32, DispatchError, FixedPointNumber, FixedU128, }; @@ -156,7 +159,13 @@ pub mod pallet { /// /// Just added here to add additional trait bounds. #[pallet::no_default] - type Balance: Balance + TryFrom + Into + Bounded + UniqueSaturatedInto; + type Balance: Balance + + TryFrom + + Into + + Bounded + + UniqueSaturatedInto + + UniqueSaturatedFrom + + UniqueSaturatedInto; /// The fungible in which fees are paid and contract balances are held. #[pallet::no_default] diff --git a/substrate/frame/revive/src/tests/precompiles.rs b/substrate/frame/revive/src/tests/precompiles.rs index bb1840d5f762c..dbe94f5f60581 100644 --- a/substrate/frame/revive/src/tests/precompiles.rs +++ b/substrate/frame/revive/src/tests/precompiles.rs @@ -18,7 +18,7 @@ //! Precompiles added to the test runtime. use crate::{ - exec::{ErrorOrigin, ExecError}, + exec::{CallResources, ErrorOrigin, ExecError}, precompiles::{AddressMatcher, Error, Ext, ExtWithInfo, Precompile, Token}, Config, DispatchError, ExecOrigin as Origin, Weight, U256, }; @@ -109,8 +109,7 @@ impl Precompile for NoInfo { }, INoInfoCalls::passData(INoInfo::passDataCall { inputLen }) => { env.call( - Weight::MAX, - U256::MAX, + &CallResources::Precise { weight: Weight::MAX, deposit_limit: U256::MAX }, &env.address(), 0.into(), vec![42; *inputLen as usize], diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 71ec22f129966..4cf773e7f75c6 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -40,9 +40,8 @@ use crate::{ }, tracing::trace, weights::WeightInfo, - AccountInfo, AccountInfoOf, BalanceWithDust, Code, Combinator, Config, ContractInfo, - DeletionQueueCounter, Error, ExecConfig, HoldReason, Origin, Pallet, PristineCode, - StorageDeposit, H160, + AccountInfo, AccountInfoOf, BalanceWithDust, Code, Config, ContractInfo, DeletionQueueCounter, + Error, ExecConfig, HoldReason, Origin, Pallet, PristineCode, StorageDeposit, H160, }; use assert_matches::assert_matches; use codec::Encode; @@ -1595,13 +1594,12 @@ fn gas_left_api_works() { let received = builder::bare_call(addr).build_and_unwrap_result(); assert_eq!(received.flags, ReturnFlags::empty()); let gas_left = U256::from_little_endian(received.data.as_ref()); - let gas_left_max = - ::FeeInfo::weight_to_fee(&GAS_LIMIT, Combinator::Min) + 1_000_000; + let gas_left_max = ::FeeInfo::weight_to_fee(&GAS_LIMIT) + 1_000_000; assert!(gas_left > 0u32.into()); assert!(gas_left < gas_left_max.into()); // Call the contract using the hold - let hold_initial = ::FeeInfo::weight_to_fee(&GAS_LIMIT, Combinator::Max); + let hold_initial = ::FeeInfo::weight_to_fee(&GAS_LIMIT); ::FeeInfo::deposit_txfee(::Currency::issue(hold_initial)); let mut exec_config = ExecConfig::new_substrate_tx(); exec_config.collect_deposit_from_hold = Some((0u32.into(), Default::default())); diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index 74144ac3db845..a743024436c96 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -20,17 +20,20 @@ use core::iter; use crate::{ - address::AddressMapper, - evm::decode_revert_reason, - test_utils::{builder::Contract, ALICE, ALICE_ADDR, BOB_ADDR}, - tests::{builder, ExtBuilder, MockHandlerImpl, Test}, - Code, Config, DelegateInfo, Error, ExecConfig, ExecOrigin, ExecReturnValue, + evm::{decode_revert_reason, fees::InfoT}, + test_utils::{builder::Contract, deposit_limit, ALICE, ALICE_ADDR, GAS_LIMIT}, + tests::{builder, ExtBuilder, Test}, + BalanceOf, Code, Config, DispatchError, Error, ExecConfig, }; use alloy_core::{ primitives::{Bytes, FixedBytes}, sol_types::{Revert, SolCall, SolError, SolInterface}, }; -use frame_support::{assert_err, traits::fungible::Mutate}; +use frame_support::{ + assert_err, + traits::fungible::{Balanced, Mutate}, +}; +use itertools::Itertools; use pallet_revive_fixtures::{compile_module_with_type, Callee, Caller, FixtureType}; use pallet_revive_uapi::ReturnFlags; use pretty_assertions::assert_eq; @@ -635,3 +638,123 @@ fn instantiate_from_constructor_works() { assert_eq!(result, 42u64); }); } + +/// No resolc caller since the subcall limiting is not implemented on resolc, yet. +#[test_case(FixtureType::Solc, FixtureType::Solc; "solc->solc")] +#[test_case(FixtureType::Solc, FixtureType::Resolc; "solc->resolc")] +fn subcall_effectively_limited_substrate_tx(caller_type: FixtureType, callee_type: FixtureType) { + let (caller_code, _) = compile_module_with_type("Caller", caller_type).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", callee_type).unwrap(); + + let no_collection_config = ExecConfig::new_substrate_tx(); + let mut collection_config = ExecConfig::new_substrate_tx(); + collection_config.collect_deposit_from_hold = Some(Default::default()); + let configs = [no_collection_config, collection_config]; + + let call_types = [0u8, 1, 2]; + + struct Case { + deposit_limit: BalanceOf, + gas_divisor: u64, + callee_input: Vec, + result: Result, + is_store_call: bool, + } + + let test_cases = [ + Case { + deposit_limit: deposit_limit::(), + gas_divisor: 1, + callee_input: Callee::consumeAllReftimeCall {}.abi_encode(), + result: Err(>::OutOfGas.into()), + is_store_call: false, + }, + Case { + deposit_limit: deposit_limit::(), + gas_divisor: 2, + callee_input: Callee::consumeAllReftimeCall {}.abi_encode(), + result: Ok(false), + is_store_call: false, + }, + Case { + deposit_limit: deposit_limit::(), + gas_divisor: u64::MAX, + callee_input: Callee::consumeAllReftimeCall {}.abi_encode(), + result: Ok(false), + is_store_call: false, + }, + Case { + deposit_limit: 130, + gas_divisor: 1, + callee_input: Callee::storeCall { _data: 42 }.abi_encode(), + result: Err(>::StorageDepositLimitExhausted.into()), + is_store_call: true, + }, + Case { + deposit_limit: 130, + gas_divisor: 2, + callee_input: Callee::storeCall { _data: 42 }.abi_encode(), + result: Ok(false), + is_store_call: true, + }, + Case { + deposit_limit: 130, + gas_divisor: u64::MAX, + callee_input: Callee::storeCall { _data: 42 }.abi_encode(), + result: Ok(false), + is_store_call: true, + }, + Case { + deposit_limit: deposit_limit::(), + gas_divisor: 2, + callee_input: Callee::storeCall { _data: 42 }.abi_encode(), + result: Ok(true), + is_store_call: true, + }, + ]; + + for ((case, config), call_type) in + test_cases.iter().cartesian_product(configs).cartesian_product(call_types) + { + // the storage stuff won't work on static or delegate call + if case.is_store_call && call_type != 0 { + continue + } + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let fees = + ::FeeInfo::tx_fee_from_weight(0, &GAS_LIMIT) + case.deposit_limit; + ::FeeInfo::deposit_txfee(::Currency::issue(fees)); + + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code.clone())) + .build_and_unwrap_contract(); + + // Instantiate the caller contract. + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_code.clone())) + .build_and_unwrap_contract(); + + let output = builder::bare_call(caller_addr) + .data( + Caller::callPartialGasCall { + _callee: callee_addr.0.into(), + _data: case.callee_input.clone().into(), + _gasDivisor: case.gas_divisor, + _callType: call_type, + } + .abi_encode(), + ) + .exec_config(config) + .storage_deposit_limit(case.deposit_limit) + .build(); + + let result = output.result.map(|result| { + Caller::callPartialGasCall::abi_decode_returns(&result.data).unwrap() + }); + assert_eq!(case.result, result); + }); + } +} diff --git a/substrate/frame/revive/src/tests/sol/system.rs b/substrate/frame/revive/src/tests/sol/system.rs index 3e571bd85b5a7..078f55f64c592 100644 --- a/substrate/frame/revive/src/tests/sol/system.rs +++ b/substrate/frame/revive/src/tests/sol/system.rs @@ -21,7 +21,7 @@ use crate::{ evm::fees::InfoT, test_utils::{builder::Contract, ALICE, ALICE_ADDR, GAS_LIMIT}, tests::{builder, Contracts, ExtBuilder, Test}, - Code, Combinator, Config, ExecConfig, U256, + Code, Config, ExecConfig, U256, }; use alloy_core::sol_types::SolCall; use frame_support::traits::fungible::{Balanced, Mutate}; @@ -213,7 +213,7 @@ fn gas_works(fixture_type: FixtureType) { builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); // enable txhold collection which we expect to be on when using the evm backend - let hold_initial = ::FeeInfo::weight_to_fee(&GAS_LIMIT, Combinator::Max); + let hold_initial = ::FeeInfo::weight_to_fee(&GAS_LIMIT); ::FeeInfo::deposit_txfee(::Currency::issue(hold_initial)); let mut exec_config = ExecConfig::new_substrate_tx(); exec_config.collect_deposit_from_hold = Some((0u32.into(), Default::default())); diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 1641ad1e33e6d..467d1768cb5af 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -54,7 +54,7 @@ pub trait Tracing { _is_read_only: bool, _value: U256, _input: &[u8], - _gas: Weight, + _gas: U256, ) { } diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index 421f592b8462f..d93806d564d4c 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -19,6 +19,7 @@ mod call_helpers; use super::utility::IntoAddress; use crate::{ + exec::CallResources, vm::{ evm::{interpreter::Halt, util::as_usize_or_halt, Interpreter}, Ext, RuntimeCosts, @@ -26,7 +27,7 @@ use crate::{ Code, Error, Pallet, Weight, H160, LOG_TARGET, U256, }; use alloc::{vec, vec::Vec}; -pub use call_helpers::{calc_call_gas, get_memory_in_and_out_ranges}; +pub use call_helpers::{charge_call_gas, get_memory_in_and_out_ranges}; use core::{ cmp::min, ops::{ControlFlow, Range}, @@ -46,9 +47,6 @@ pub fn create( let [value, code_offset, len] = interpreter.stack.popn()?; let len = as_usize_or_halt::(len)?; - // TODO: We do not charge for the new code in storage. When implementing the new gas: - // Introduce EthInstantiateWithCode, which shall charge gas based on the code length. - // See #9577 for more context. interpreter.ext.charge_or_halt(RuntimeCosts::Instantiate { input_data_len: len as u32, // We charge for initcode execution balance_transfer: Pallet::::has_balance(value), @@ -75,7 +73,7 @@ pub fn create( }; let call_result = interpreter.ext.instantiate( - Weight::from_parts(u64::MAX, u64::MAX), // TODO: set the right limit + Weight::from_parts(u64::MAX, u64::MAX), U256::MAX, Code::Upload(code), value, @@ -107,26 +105,22 @@ pub fn create( /// /// Message call with value transfer to another account. pub fn call(interpreter: &mut Interpreter) -> ControlFlow { - let [_local_gas_limit, to, value] = interpreter.stack.popn()?; + let [gas_limit, to, value] = interpreter.stack.popn()?; let to = to.into_address(); - // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be - // addressed in #9577. - let has_transfer = !value.is_zero(); if interpreter.ext.is_read_only() && has_transfer { return ControlFlow::Break(Error::::StateChangeDenied.into()); } - let (input, return_memory_range) = get_memory_in_and_out_ranges(interpreter)?; let scheme = CallScheme::Call; - let gas_limit = calc_call_gas(interpreter, to, scheme, input.len(), value)?; + charge_call_gas(interpreter, to, scheme, input.len(), value)?; run_call( interpreter, to, + gas_limit, interpreter.memory.slice(input).to_vec(), scheme, - Weight::from_parts(gas_limit, u64::MAX), value, return_memory_range, ) @@ -146,22 +140,19 @@ pub fn call_code(_interpreter: &mut Interpreter) -> ControlFlow /// /// Message call with alternative account's code but same sender and value. pub fn delegate_call(interpreter: &mut Interpreter) -> ControlFlow { - let [_local_gas_limit, to] = interpreter.stack.popn()?; + let [gas_limit, to] = interpreter.stack.popn()?; let to = to.into_address(); - // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be - // addressed in #9577. - let (input, return_memory_range) = get_memory_in_and_out_ranges(interpreter)?; let scheme = CallScheme::DelegateCall; let value = U256::zero(); - let gas_limit = calc_call_gas(interpreter, to, scheme, input.len(), value)?; + charge_call_gas(interpreter, to, scheme, input.len(), value)?; run_call( interpreter, to, + gas_limit, interpreter.memory.slice(input).to_vec(), scheme, - Weight::from_parts(gas_limit, u64::MAX), value, return_memory_range, ) @@ -171,21 +162,19 @@ pub fn delegate_call(interpreter: &mut Interpreter) -> ControlFlow(interpreter: &mut Interpreter) -> ControlFlow { - let [_local_gas_limit, to] = interpreter.stack.popn()?; + let [gas_limit, to] = interpreter.stack.popn()?; let to = to.into_address(); - // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be - // addressed in #9577. let (input, return_memory_range) = get_memory_in_and_out_ranges(interpreter)?; let scheme = CallScheme::StaticCall; let value = U256::zero(); - let gas_limit = calc_call_gas(interpreter, to, scheme, input.len(), value)?; + charge_call_gas(interpreter, to, scheme, input.len(), value)?; run_call( interpreter, to, + gas_limit, interpreter.memory.slice(input).to_vec(), scheme, - Weight::from_parts(gas_limit, u64::MAX), value, return_memory_range, ) @@ -194,16 +183,15 @@ pub fn static_call(interpreter: &mut Interpreter) -> ControlFlow( interpreter: &mut Interpreter<'a, E>, callee: H160, + gas_limit: U256, input: Vec, scheme: CallScheme, - gas_limit: Weight, value: U256, return_memory_range: Range, ) -> ControlFlow { let call_result = match scheme { CallScheme::Call | CallScheme::StaticCall => interpreter.ext.call( - gas_limit, - U256::MAX, + &CallResources::Ethereum(gas_limit), &callee, value, input, @@ -211,7 +199,9 @@ fn run_call<'a, E: Ext>( scheme.is_static_call(), ), CallScheme::DelegateCall => - interpreter.ext.delegate_call(gas_limit, U256::MAX, callee, input), + interpreter + .ext + .delegate_call(&CallResources::Ethereum(gas_limit), callee, input), CallScheme::CallCode => { unreachable!() }, diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs index 9796006882846..4b6cfbc990f72 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs @@ -56,13 +56,13 @@ pub fn resize_memory<'a, E: Ext>( } /// Calculates gas cost and limit for call instructions. -pub fn calc_call_gas<'a, E: Ext>( +pub fn charge_call_gas<'a, E: Ext>( interpreter: &mut Interpreter<'a, E>, callee: H160, scheme: CallScheme, input_len: usize, value: U256, -) -> ControlFlow { +) -> ControlFlow { let precompile = >::get::(&callee.as_fixed_bytes()); match precompile { @@ -106,5 +106,5 @@ pub fn calc_call_gas<'a, E: Ext>( })?; } - ControlFlow::Continue(u64::MAX) // TODO: Set the right gas limit + ControlFlow::Continue(()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/system.rs b/substrate/frame/revive/src/vm/evm/instructions/system.rs index 411ed142e07f5..a112fc35b569e 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/system.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/system.rs @@ -28,7 +28,6 @@ use core::ops::ControlFlow; use revm::interpreter::gas::{BASE, VERYLOW}; use sp_core::H256; use sp_io::hashing::keccak_256; -// TODO: Fix the gas handling for the memory operations /// The Keccak-256 hash of the empty string `""`. pub const KECCAK_EMPTY: [u8; 32] = diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index aeeafb95cfecd..d43890191cfae 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -23,7 +23,7 @@ pub mod env; pub use env::SyscallDoc; use crate::{ - exec::{ExecError, ExecResult, Ext, Key}, + exec::{CallResources, ExecError, ExecResult, Ext, Key}, gas::ChargedAmount, limits, precompiles::{All as AllPrecompiles, Precompiles}, @@ -687,8 +687,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { })?; } self.ext.call( - weight, - deposit_limit, + &CallResources::Precise { weight, deposit_limit }, &callee, value, input_data, @@ -700,7 +699,11 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { return Err(Error::::InvalidCallFlags.into()); } - self.ext.delegate_call(weight, deposit_limit, callee, input_data) + self.ext.delegate_call( + &CallResources::Precise { weight, deposit_limit }, + callee, + input_data, + ) }, }; From 2c69ffdb58bdd50ba2c4d4eacad04c3b51174f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sat, 25 Oct 2025 08:36:51 -0300 Subject: [PATCH 02/69] Rename gas metering to weight metering --- .../assets/common/src/erc20_transactor.rs | 28 +- substrate/frame/revive/proc-macro/src/lib.rs | 4 +- substrate/frame/revive/src/benchmarking.rs | 4 +- substrate/frame/revive/src/call_builder.rs | 17 +- substrate/frame/revive/src/evm/call.rs | 4 +- substrate/frame/revive/src/evm/runtime.rs | 30 +- .../revive/src/evm/tracing/call_tracing.rs | 12 +- .../src/evm/tracing/prestate_tracing.rs | 6 +- substrate/frame/revive/src/exec.rs | 158 ++++---- substrate/frame/revive/src/exec/mock_ext.rs | 18 +- substrate/frame/revive/src/exec/tests.rs | 290 +++++++------- substrate/frame/revive/src/impl_fungibles.rs | 14 +- substrate/frame/revive/src/lib.rs | 365 +++++++++++------- substrate/frame/revive/src/metering/mod.rs | 19 + .../{storage/meter.rs => metering/storage.rs} | 6 +- .../revive/src/{gas.rs => metering/weight.rs} | 208 +++++----- substrate/frame/revive/src/precompiles.rs | 6 +- .../revive/src/precompiles/builtin/modexp.rs | 6 +- .../revive/src/precompiles/builtin/storage.rs | 8 +- .../revive/src/precompiles/builtin/system.rs | 4 +- substrate/frame/revive/src/primitives.rs | 14 +- substrate/frame/revive/src/storage.rs | 20 +- substrate/frame/revive/src/test_utils.rs | 2 +- .../frame/revive/src/test_utils/builder.rs | 26 +- substrate/frame/revive/src/tests.rs | 10 +- substrate/frame/revive/src/tests/pvm.rs | 67 ++-- .../frame/revive/src/tests/sol/contract.rs | 6 +- .../frame/revive/src/tests/sol/control.rs | 7 +- .../frame/revive/src/tests/sol/system.rs | 4 +- substrate/frame/revive/src/tracing.rs | 6 +- .../revive/src/vm/evm/instructions/host.rs | 2 +- substrate/frame/revive/src/vm/mod.rs | 13 +- substrate/frame/revive/src/vm/pvm.rs | 6 +- .../frame/revive/src/vm/runtime_costs.rs | 6 +- 34 files changed, 770 insertions(+), 626 deletions(-) create mode 100644 substrate/frame/revive/src/metering/mod.rs rename substrate/frame/revive/src/{storage/meter.rs => metering/storage.rs} (99%) rename substrate/frame/revive/src/{gas.rs => metering/weight.rs} (63%) diff --git a/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs b/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs index 75244bd122af5..7fa2559b26f25 100644 --- a/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs +++ b/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs @@ -44,7 +44,7 @@ pub struct ERC20Transactor< T, Matcher, AccountIdConverter, - GasLimit, + WeightLimit, StorageDepositLimit, AccountId, TransfersCheckingAccount, @@ -53,7 +53,7 @@ pub struct ERC20Transactor< T, Matcher, AccountIdConverter, - GasLimit, + WeightLimit, StorageDepositLimit, AccountId, TransfersCheckingAccount, @@ -65,7 +65,7 @@ impl< T: pallet_revive::Config, AccountIdConverter: ConvertLocation, Matcher: MatchesFungibles, - GasLimit: Get, + WeightLimit: Get, StorageDepositLimit: Get>, TransfersCheckingAccount: Get, > TransactAsset @@ -73,7 +73,7 @@ impl< T, Matcher, AccountIdConverter, - GasLimit, + WeightLimit, StorageDepositLimit, AccountId, TransfersCheckingAccount, @@ -116,24 +116,24 @@ where // We need to map the 32 byte checking account to a 20 byte account. let checking_account_eth = T::AddressMapper::to_address(&TransfersCheckingAccount::get()); let checking_address = Address::from(Into::<[u8; 20]>::into(checking_account_eth)); - let gas_limit = GasLimit::get(); + let weight_limit = WeightLimit::get(); // To withdraw, we actually transfer to the checking account. // We do this using the solidity ERC20 interface. let data = IERC20::transferCall { to: checking_address, value: EU256::from(amount) }.abi_encode(); - let ContractResult { result, gas_consumed, storage_deposit, .. } = + let ContractResult { result, weight_consumed, storage_deposit, .. } = pallet_revive::Pallet::::bare_call( OriginFor::::signed(who.clone()), asset_id, U256::zero(), - gas_limit, + weight_limit, StorageDepositLimit::get(), data, ExecConfig::new_substrate_tx(), ); // We need to return this surplus for the executor to allow refunding it. - let surplus = gas_limit.saturating_sub(gas_consumed); - tracing::trace!(target: "xcm::transactor::erc20::withdraw", ?gas_consumed, ?surplus, ?storage_deposit); + let surplus = weight_limit.saturating_sub(weight_consumed); + tracing::trace!(target: "xcm::transactor::erc20::withdraw", ?weight_consumed, ?surplus, ?storage_deposit); if let Ok(return_value) = result { tracing::trace!(target: "xcm::transactor::erc20::withdraw", ?return_value, "Return value by withdraw_asset"); if return_value.did_revert() { @@ -179,20 +179,20 @@ where // To deposit, we actually transfer from the checking account to the beneficiary. // We do this using the solidity ERC20 interface. let data = IERC20::transferCall { to: address, value: EU256::from(amount) }.abi_encode(); - let gas_limit = GasLimit::get(); - let ContractResult { result, gas_consumed, storage_deposit, .. } = + let weight_limit = WeightLimit::get(); + let ContractResult { result, weight_consumed, storage_deposit, .. } = pallet_revive::Pallet::::bare_call( OriginFor::::signed(TransfersCheckingAccount::get()), asset_id, U256::zero(), - gas_limit, + weight_limit, StorageDepositLimit::get(), data, ExecConfig::new_substrate_tx(), ); // We need to return this surplus for the executor to allow refunding it. - let surplus = gas_limit.saturating_sub(gas_consumed); - tracing::trace!(target: "xcm::transactor::erc20::deposit", ?gas_consumed, ?surplus, ?storage_deposit); + let surplus = weight_limit.saturating_sub(weight_consumed); + tracing::trace!(target: "xcm::transactor::erc20::deposit", ?weight_consumed, ?surplus, ?storage_deposit); if let Ok(return_value) = result { tracing::trace!(target: "xcm::transactor::erc20::deposit", ?return_value, "Return value"); if return_value.did_revert() { diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 87f7c699f461d..98a885a46f9ba 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -419,12 +419,12 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { .map(|s| format!("{s}: {{:?}}")) .collect::>() .join(", "); - let trace_fmt_str = format!("{}({}) = {{:?}} gas_consumed: {{:?}}", name, params_fmt_str); + let trace_fmt_str = format!("{}({}) = {{:?}} weight_consumed: {{:?}}", name, params_fmt_str); quote! { // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); - ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.gas_meter().gas_consumed()); + ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.gas_meter().weight_consumed()); result } }; diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index e5b19c5976e4d..65d275f41b50f 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -812,7 +812,7 @@ mod benchmarks { let mut call_setup = CallSetup::::default(); let (mut ext, _) = call_setup.ext(); - let weight_left_before = ext.gas_meter().gas_left(); + let weight_left_before = ext.gas_meter().weight_left(); let result; #[block] { @@ -822,7 +822,7 @@ mod benchmarks { input_bytes, ); } - let weight_left_after = ext.gas_meter().gas_left(); + let weight_left_after = ext.gas_meter().weight_left(); assert_ne!(weight_left_after.ref_time(), 0); assert!(weight_left_before.ref_time() > weight_left_after.ref_time()); diff --git a/substrate/frame/revive/src/call_builder.rs b/substrate/frame/revive/src/call_builder.rs index c6b6886b4e900..d1fd82e6765c7 100644 --- a/substrate/frame/revive/src/call_builder.rs +++ b/substrate/frame/revive/src/call_builder.rs @@ -29,12 +29,11 @@ use crate::{ address::AddressMapper, exec::{ExportedFunction, Key, PrecompileExt, Stack}, limits, - storage::meter::Meter, + metering::{storage::Meter as StorageMeter, weight::WeightMeter}, transient_storage::MeterEntry, vm::pvm::{PreparedCall, Runtime}, AccountInfo, BalanceOf, BalanceWithDust, Code, CodeInfoOf, Config, ContractBlob, ContractInfo, - Error, ExecConfig, ExecOrigin as Origin, GasMeter, OriginFor, Pallet as Contracts, - PristineCode, Weight, + Error, ExecConfig, ExecOrigin as Origin, OriginFor, Pallet as Contracts, PristineCode, Weight, }; use alloc::{vec, vec::Vec}; use frame_support::{storage::child, traits::fungible::Mutate}; @@ -51,8 +50,8 @@ pub struct CallSetup { contract: Contract, dest: T::AccountId, origin: Origin, - gas_meter: GasMeter, - storage_meter: Meter, + weight_meter: WeightMeter, + storage_meter: StorageMeter, value: BalanceOf, data: Vec, transient_storage_size: u32, @@ -78,7 +77,7 @@ where let dest = contract.account_id.clone(); let origin = Origin::from_account_id(contract.caller.clone()); - let storage_meter = Meter::new(default_deposit_limit::()); + let storage_meter = StorageMeter::new(default_deposit_limit::()); #[cfg(feature = "runtime-benchmarks")] { @@ -101,7 +100,7 @@ where contract, dest, origin, - gas_meter: GasMeter::new(Weight::MAX), + weight_meter: WeightMeter::new(Weight::MAX), storage_meter, value: 0u32.into(), data: vec![], @@ -112,7 +111,7 @@ where /// Set the meter's storage deposit limit. pub fn set_storage_deposit_limit(&mut self, balance: BalanceOf) { - self.storage_meter = Meter::new(balance); + self.storage_meter = StorageMeter::new(balance); } /// Set the call's origin. @@ -150,7 +149,7 @@ where let mut ext = StackExt::bench_new_call( T::AddressMapper::to_address(&self.dest), self.origin.clone(), - &mut self.gas_meter, + &mut self.weight_meter, &mut self.storage_meter, self.value, &self.exec_config, diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs index b35301455710d..2eeaf6701e1e2 100644 --- a/substrate/frame/revive/src/evm/call.rs +++ b/substrate/frame/revive/src/evm/call.rs @@ -120,7 +120,7 @@ where let call = crate::Call::eth_call:: { dest, value, - gas_limit: Zero::zero(), + weight_limit: Zero::zero(), data, transaction_encoded, effective_gas_price, @@ -142,7 +142,7 @@ where let call = crate::Call::eth_instantiate_with_code:: { value, - gas_limit: Zero::zero(), + weight_limit: Zero::zero(), code, data, transaction_encoded, diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 39b346db3f677..fd76991990eea 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -262,7 +262,7 @@ pub trait EthExtra { /// Convert the unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`]. /// and ensure that the fees from the Ethereum transaction correspond to the fees computed from - /// the encoded_len, the injected gas_limit and storage_deposit_limit. + /// the encoded_len and the injected weight_limit. /// /// # Parameters /// - `payload`: The RLP-encoded Ethereum transaction. @@ -516,8 +516,7 @@ mod test { result.function, extra, tx, - self.dry_run.unwrap().gas_required, - signed_transaction, + self.dry_run.unwrap().weight_required, )) }) } @@ -526,17 +525,10 @@ mod test { #[test] fn check_eth_transact_call_works() { let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - let (expected_encoded_len, call, _, tx, gas_required, signed_transaction) = - builder.check().unwrap(); - let expected_effective_gas_price: u32 = ::NativeToEthRatio::get(); - - match call { - RuntimeCall::Contracts(crate::Call::eth_call:: { - dest, + let (expected_encoded_len, call, _, tx, weight_required) = builder.check().unwrap(); value, data, - gas_limit, - transaction_encoded, + weight_limit, effective_gas_price, encoded_len, }) if dest == tx.to.unwrap() && @@ -547,8 +539,8 @@ mod test { { assert_eq!(encoded_len, expected_encoded_len); assert!( - gas_limit.all_gte(gas_required), - "Assert failed: gas_limit={gas_limit:?} >= gas_required={gas_required:?}" + weight_limit.all_gte(weight_required), + "Assert failed: weight_limit={weight_limit:?} >= weight_required={weight_required:?}" ); }, _ => panic!("Call does not match."), @@ -563,8 +555,7 @@ mod test { expected_code.clone(), expected_data.clone(), ); - let (expected_encoded_len, call, _, tx, gas_required, signed_transaction) = - builder.check().unwrap(); + let (expected_encoded_len, call, _, tx, weight_required) = builder.check().unwrap(); let expected_effective_gas_price: u32 = ::NativeToEthRatio::get(); let expected_value = tx.value.unwrap_or_default().as_u64().into(); @@ -573,8 +564,7 @@ mod test { value, code, data, - gas_limit, - transaction_encoded, + weight_limit, effective_gas_price, encoded_len, }) if value == expected_value && @@ -585,8 +575,8 @@ mod test { { assert_eq!(encoded_len, expected_encoded_len); assert!( - gas_limit.all_gte(gas_required), - "Assert failed: gas_limit={gas_limit:?} >= gas_required={gas_required:?}" + weight_limit.all_gte(weight_required), + "Assert failed: weight_limit={weight_limit:?} >= weight_required={weight_required:?}" ); }, _ => panic!("Call does not match."), diff --git a/substrate/frame/revive/src/evm/tracing/call_tracing.rs b/substrate/frame/revive/src/evm/tracing/call_tracing.rs index 9392dcb8fb3e5..270810800c13f 100644 --- a/substrate/frame/revive/src/evm/tracing/call_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/call_tracing.rs @@ -69,7 +69,7 @@ impl Gas> Tracing for CallTracer Gas> Tracing for CallTracer Gas> Tracing for CallTracer Gas> Tracing for CallTracer Gas> Tracing for CallTracer diff --git a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs index e6bdf843be223..1693996575745 100644 --- a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs @@ -183,7 +183,7 @@ where _is_read_only: bool, _value: U256, _input: &[u8], - _gas: Weight, + _weight: Weight, ) { let include_code = !self.config.disable_code; self.trace.0.entry(from).or_insert_with_key(|addr| { @@ -209,11 +209,11 @@ where } } - fn exit_child_span_with_error(&mut self, _error: crate::DispatchError, _gas_used: Weight) { + fn exit_child_span_with_error(&mut self, _error: crate::DispatchError, _weight_used: Weight) { self.is_create = None; } - fn exit_child_span(&mut self, output: &ExecReturnValue, _gas_used: Weight) { + fn exit_child_span(&mut self, output: &ExecReturnValue, _weight_used: Weight) { let create_code = self.is_create.take(); if output.did_revert() { return diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index abd46820d0818..4a009ecf72250 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -23,10 +23,14 @@ use crate::{ }, gas::GasMeter, limits, + metering::{ + storage, + weight::{ChargedAmount, Token, WeightMeter}, + }, precompiles::{All as AllPrecompiles, Instance as PrecompileInstance, Precompiles}, primitives::{ExecConfig, ExecReturnValue, StorageDeposit}, runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, - storage::{self, meter::Diff, AccountIdOrAddress, WriteOutcome}, + storage::{AccountIdOrAddress, WriteOutcome}, tracing::if_tracing, transient_storage::TransientStorage, AccountInfo, AccountInfoOf, BalanceOf, BalanceWithDust, Code, CodeInfo, CodeInfoOf, @@ -277,21 +281,22 @@ pub trait PrecompileWithInfoExt: PrecompileExt { pub trait PrecompileExt: sealing::Sealed { type T: Config; - /// Charges the gas meter with the given weight. - fn charge(&mut self, weight: Weight) -> Result { + /// Charges the weight meter with the given weight. + fn charge(&mut self, weight: Weight) -> Result { self.gas_meter_mut().charge(RuntimeCosts::Precompile(weight)) } - fn adjust_gas(&mut self, charged: crate::gas::ChargedAmount, actual_weight: Weight) { + fn adjust_gas(&mut self, charged: ChargedAmount, actual_weight: Weight) { self.gas_meter_mut() - .adjust_gas(charged, RuntimeCosts::Precompile(actual_weight)); + .adjust_weight(charged, RuntimeCosts::Precompile(actual_weight)); } - /// Charges the gas meter with the given token or halts execution if not enough gas is left. - fn charge_or_halt>( + /// Charges the weight meter with the given token or halts execution if not enough weight is + /// left. + fn charge_or_halt>( &mut self, token: Tok, - ) -> ControlFlow { + ) -> ControlFlow { self.gas_meter_mut().charge_or_halt(token) } @@ -400,11 +405,14 @@ pub trait PrecompileExt: sealing::Sealed { /// Returns the chain id. fn chain_id(&self) -> u64; - /// Get an immutable reference to the nested gas meter. - fn gas_meter(&self) -> &GasMeter; + /// Returns the maximum allowed size of a storage item. + fn max_value_size(&self) -> u32; + + /// Get an immutable reference to the nested weight meter. + fn gas_meter(&self) -> &WeightMeter; - /// Get a mutable reference to the nested gas meter. - fn gas_meter_mut(&mut self) -> &mut GasMeter; + /// Get a mutable reference to the nested weight meter. + fn gas_meter_mut(&mut self) -> &mut WeightMeter; /// Recovers ECDSA compressed public key based on signature and message hash. fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; @@ -484,7 +492,7 @@ pub trait PrecompileExt: sealing::Sealed { ) -> Result; /// Charges `diff` from the meter. - fn charge_storage(&mut self, diff: &Diff); + fn charge_storage(&mut self, diff: &storage::Diff); } /// Describes the different functions that can be exported by an [`Executable`]. @@ -514,8 +522,11 @@ pub trait Executable: Sized { /// Load the executable from storage. /// /// # Note - /// Charges size base load weight from the gas meter. - fn from_storage(code_hash: H256, gas_meter: &mut GasMeter) -> Result; + /// Charges size base load weight from the weight meter. + fn from_storage( + code_hash: H256, + weight_meter: &mut WeightMeter, + ) -> Result; /// Load the executable from EVM bytecode fn from_evm_init_code(code: Vec, owner: AccountIdOf) -> Result; @@ -561,10 +572,10 @@ pub struct Stack<'a, T: Config, E> { /// than a plain account when being called through one of the contract RPCs where the /// client can freely choose the origin. This usually makes no sense but is still possible. origin: Origin, - /// The gas meter where costs are charged to. - gas_meter: &'a mut GasMeter, + /// The weight meter where costs are charged to. + weight_meter: &'a mut WeightMeter, /// The storage meter makes sure that the storage deposit limit is obeyed. - storage_meter: &'a mut storage::meter::Meter, + storage_meter: &'a mut storage::Meter, /// The timestamp at the point of call stack instantiation. timestamp: MomentOf, /// The block number at the time of call stack instantiation. @@ -600,10 +611,10 @@ struct Frame { value_transferred: U256, /// Determines whether this is a call or instantiate frame. entry_point: ExportedFunction, - /// The gas meter capped to the supplied gas limit. - nested_gas: GasMeter, + /// The weight meter capped to the supplied weight limit. + nested_weight: WeightMeter, /// The storage meter for the individual call. - nested_storage: storage::meter::NestedMeter, + nested_storage: storage::NestedMeter, /// If `false` the contract enabled its defense against reentrance attacks. allows_reentry: bool, /// If `true` subsequent calls cannot modify storage. @@ -808,8 +819,8 @@ where pub fn run_call( origin: Origin, dest: H160, - gas_meter: &mut GasMeter, - storage_meter: &mut storage::meter::Meter, + weight_meter: &mut WeightMeter, + storage_meter: &mut storage::Meter, value: U256, input_data: Vec, exec_config: &ExecConfig, @@ -818,7 +829,7 @@ where if let Some((mut stack, executable)) = Stack::<'_, T, E>::new( FrameArgs::Call { dest: dest.clone(), cached_info: None, delegated_call: None }, origin.clone(), - gas_meter, + weight_meter, storage_meter, value, exec_config, @@ -873,8 +884,8 @@ where pub fn run_instantiate( origin: T::AccountId, executable: E, - gas_meter: &mut GasMeter, - storage_meter: &mut storage::meter::Meter, + weight_meter: &mut WeightMeter, + storage_meter: &mut storage::Meter, value: U256, input_data: Vec, salt: Option<&[u8; 32]>, @@ -889,7 +900,7 @@ where input_data: input_data.as_ref(), }, Origin::from_account_id(origin), - gas_meter, + weight_meter, storage_meter, value, exec_config, @@ -913,8 +924,8 @@ where pub fn bench_new_call( dest: H160, origin: Origin, - gas_meter: &'a mut GasMeter, - storage_meter: &'a mut storage::meter::Meter, + weight_meter: &'a mut WeightMeter, + storage_meter: &'a mut storage::Meter, value: BalanceOf, exec_config: &'a ExecConfig, ) -> (Self, E) { @@ -925,7 +936,7 @@ where delegated_call: None, }, origin, - gas_meter, + weight_meter, storage_meter, value.into(), exec_config, @@ -943,8 +954,8 @@ where fn new( args: FrameArgs, origin: Origin, - gas_meter: &'a mut GasMeter, - storage_meter: &'a mut storage::meter::Meter, + weight_meter: &'a mut WeightMeter, + storage_meter: &'a mut storage::Meter, value: U256, exec_config: &'a ExecConfig, input_data: &Vec, @@ -953,7 +964,7 @@ where let Some((first_frame, executable)) = Self::new_frame( args, value, - gas_meter, + weight_meter, Weight::max_value(), storage_meter, BalanceOf::::max_value(), @@ -968,7 +979,7 @@ where let stack = Self { origin, - gas_meter, + weight_meter, storage_meter, timestamp: T::Time::now(), block_number: >::block_number(), @@ -988,12 +999,12 @@ where /// /// This does not take `self` because when constructing the first frame `self` is /// not initialized, yet. - fn new_frame( + fn new_frame( frame_args: FrameArgs, value_transferred: U256, - gas_meter: &mut GasMeter, - gas_limit: Weight, - storage_meter: &mut storage::meter::GenericMeter, + weight_meter: &mut WeightMeter, + weight_limit: Weight, + storage_meter: &mut storage::GenericMeter, deposit_limit: BalanceOf, read_only: bool, origin_is_caller: bool, @@ -1046,7 +1057,7 @@ where else { return Ok(None); }; - let executable = E::from_storage(info.code_hash, gas_meter)?; + let executable = E::from_storage(info.code_hash, weight_meter)?; ExecutableOrPrecompile::Executable(executable) } } else { @@ -1061,7 +1072,7 @@ where .as_contract() .expect("When not a precompile the contract was loaded above; qed") .code_hash, - gas_meter, + weight_meter, )?; ExecutableOrPrecompile::Executable(executable) } @@ -1108,7 +1119,7 @@ where contract_info, account_id, entry_point, - nested_gas: gas_meter.nested(gas_limit), + nested_weight: weight_meter.nested(weight_limit), nested_storage: storage_meter.nested(deposit_limit), allows_reentry: true, read_only, @@ -1123,7 +1134,7 @@ where &mut self, frame_args: FrameArgs, value_transferred: U256, - gas_limit: Weight, + weight_limit: Weight, deposit_limit: BalanceOf, read_only: bool, input_data: &[u8], @@ -1147,13 +1158,13 @@ where } let frame = top_frame_mut!(self); - let nested_gas = &mut frame.nested_gas; + let nested_weight = &mut frame.nested_weight; let nested_storage = &mut frame.nested_storage; if let Some((frame, executable)) = Self::new_frame( frame_args, value_transferred, - nested_gas, - gas_limit, + nested_weight, + weight_limit, nested_storage, deposit_limit, read_only, @@ -1188,7 +1199,7 @@ where frame.read_only, frame.value_transferred, &input_data, - frame.nested_gas.gas_left(), + frame.nested_weight.weight_left(), ); }); let mock_answer = self.exec_config.mock_handler.as_ref().and_then(|handler| { @@ -1400,10 +1411,11 @@ where // `with_transactional` executed successfully, and we have the expected output. Ok((success, output)) => { if_tracing(|tracer| { - let gas_consumed = top_frame!(self).nested_gas.gas_consumed(); + let weight_consumed = top_frame!(self).nested_weight.weight_consumed(); match &output { - Ok(output) => tracer.exit_child_span(&output, gas_consumed), - Err(e) => tracer.exit_child_span_with_error(e.error.into(), gas_consumed), + Ok(output) => tracer.exit_child_span(&output, weight_consumed), + Err(e) => + tracer.exit_child_span_with_error(e.error.into(), weight_consumed), } }); @@ -1413,8 +1425,8 @@ where // has changed. Err(error) => { if_tracing(|tracer| { - let gas_consumed = top_frame!(self).nested_gas.gas_consumed(); - tracer.exit_child_span_with_error(error.into(), gas_consumed); + let weight_consumed = top_frame!(self).nested_weight.weight_consumed(); + tracer.exit_child_span_with_error(error.into(), weight_consumed); }); (false, Err(error.into())) @@ -1451,9 +1463,9 @@ where let account_id = &frame.account_id; let prev = top_frame_mut!(self); - prev.nested_gas.absorb_nested(frame.nested_gas); + prev.nested_weight.absorb_nested(frame.nested_weight); - // Only gas counter changes are persisted in case of a failure. + // Only weight counter changes are persisted in case of a failure. if !persist { return; } @@ -1491,7 +1503,8 @@ where } } } else { - self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas)); + // TODO: iterate contracts_to_be_destroyed and destroy each contract + self.weight_meter.absorb_nested(mem::take(&mut self.first_frame.nested_weight)); if !persist { return; } @@ -1528,13 +1541,13 @@ where /// not exist, the transfer does fail and nothing will be sent to `to` if either `origin` can /// not provide the ED or transferring `value` from `from` to `to` fails. /// Note: This will also fail if `origin` is root. - fn transfer( + fn transfer( origin: &Origin, from: &T::AccountId, to: &T::AccountId, value: U256, - storage_meter: &mut storage::meter::GenericMeter, - exec_config: &ExecConfig, + storage_meter: &mut storage::GenericMeter, + exec_config: &ExecConfig, ) -> DispatchResult { fn transfer_with_dust( from: &AccountIdOf, @@ -1639,13 +1652,13 @@ where } /// Same as `transfer` but `from` is an `Origin`. - fn transfer_from_origin( + fn transfer_from_origin( origin: &Origin, from: &Origin, to: &T::AccountId, value: U256, - storage_meter: &mut storage::meter::GenericMeter, - exec_config: &ExecConfig, + storage_meter: &mut storage::GenericMeter, + exec_config: &ExecConfig, ) -> ExecResult { // If the from address is root there is no account to transfer from, and therefore we can't // take any `value` other than 0. @@ -1777,7 +1790,7 @@ where // weight limit is expected to be set to we can just multiply directly let weight_limit = { - let weight_left = self.top_frame().nested_gas.gas_left(); + let weight_left = self.top_frame().nested_weight.weight_left(); Weight::from_parts( ratio.saturating_mul_int(weight_left.ref_time()), ratio.saturating_mul_int(weight_left.proof_size()), @@ -1944,7 +1957,7 @@ where { fn instantiate( &mut self, - gas_limit: Weight, + weight_limit: Weight, deposit_limit: U256, code: Code, value: U256, @@ -1974,7 +1987,7 @@ where input_data: input_data.as_ref(), }, value, - gas_limit, + weight_limit, deposit_limit.saturated_into::>(), self.is_read_only(), &input_data, @@ -2266,12 +2279,16 @@ where ::ChainId::get() } - fn gas_meter(&self) -> &GasMeter { - &self.top_frame().nested_gas + fn max_value_size(&self) -> u32 { + limits::PAYLOAD_BYTES + } + + fn gas_meter(&self) -> &WeightMeter { + &self.top_frame().nested_weight } - fn gas_meter_mut(&mut self) -> &mut GasMeter { - &mut self.top_frame_mut().nested_gas + fn gas_meter_mut(&mut self) -> &mut WeightMeter { + &mut self.top_frame_mut().nested_weight } fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()> { @@ -2374,7 +2391,7 @@ where |(deposit, weight), frame| { ( deposit.saturating_add(&frame.nested_storage.consumed()), - weight.saturating_add(frame.nested_gas.gas_consumed()), + weight.saturating_add(frame.nested_weight.weight_consumed()), ) }, ); @@ -2391,7 +2408,8 @@ where // this is the actual limit derived from what is set in the meter let max_by_meter = { use frame_support::traits::tokens::{Fortitude, Preservation}; - let weight_fee_available = T::FeeInfo::weight_to_fee(&frame.nested_gas.gas_left()); + let weight_fee_available = + T::FeeInfo::weight_to_fee(&frame.nested_weight.weight_left()); // in the dry run no deposit limit is set. // this means its only limited by the origins free balance @@ -2438,7 +2456,7 @@ where ) } - fn charge_storage(&mut self, diff: &Diff) { + fn charge_storage(&mut self, diff: &storage::Diff) { assert!(self.has_contract_info()); self.top_frame_mut().nested_storage.charge(diff) } diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index fc35c66c6488f..d193d12268c96 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -21,7 +21,7 @@ use crate::{ AccountIdOf, CallResources, ExecError, Ext, Key, Origin, PrecompileExt, PrecompileWithInfoExt, }, - gas::GasMeter, + metering::weight::WeightMeter, precompiles::Diff, storage::{ContractInfo, WriteOutcome}, transient_storage::TransientStorage, @@ -35,13 +35,13 @@ use sp_runtime::DispatchError; /// Mock implementation of the Ext trait that panics for all methods pub struct MockExt { - gas_meter: GasMeter, + weight_meter: WeightMeter, _phantom: PhantomData, } impl MockExt { pub fn new() -> Self { - Self { gas_meter: GasMeter::new(Weight::MAX), _phantom: PhantomData } + Self { weight_meter: WeightMeter::new(Weight::MAX), _phantom: PhantomData } } } @@ -153,12 +153,16 @@ impl PrecompileExt for MockExt { panic!("MockExt::chain_id") } - fn gas_meter(&self) -> &GasMeter { - &self.gas_meter + fn max_value_size(&self) -> u32 { + panic!("MockExt::max_value_size") } - fn gas_meter_mut(&mut self) -> &mut GasMeter { - &mut self.gas_meter + fn gas_meter(&self) -> &WeightMeter { + &self.weight_meter + } + + fn gas_meter_mut(&mut self) -> &mut WeightMeter { + &mut self.weight_meter } fn ecdsa_recover( diff --git a/substrate/frame/revive/src/exec/tests.rs b/substrate/frame/revive/src/exec/tests.rs index c415b07a16bdf..be7e241a1d001 100644 --- a/substrate/frame/revive/src/exec/tests.rs +++ b/substrate/frame/revive/src/exec/tests.rs @@ -24,7 +24,7 @@ use super::*; use crate::{ exec::ExportedFunction::*, - gas::GasMeter, + metering::{storage::Meter as StorageMeter, weight::WeightMeter}, test_utils::*, tests::{ test_utils::{get_balance, place_contract, set_balance}, @@ -141,7 +141,7 @@ impl MockLoader { impl Executable for MockExecutable { fn from_storage( code_hash: H256, - _gas_meter: &mut GasMeter, + _weight_meter: &mut WeightMeter, ) -> Result { Loader::mutate(|loader| { loader.map.get(&code_hash).cloned().ok_or(Error::::CodeNotFound.into()) @@ -207,7 +207,7 @@ fn it_works() { } let value = 0; - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); let exec_ch = MockLoader::insert(Call, |_ctx, _executable| { TestData::mutate(|data| data.push(1)); exec_success() @@ -215,13 +215,13 @@ fn it_works() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, exec_ch); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); assert_matches!( MockStack::run_call( Origin::from_account_id(ALICE), BOB_ADDR, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, value.into(), vec![], @@ -244,7 +244,7 @@ fn transfer_works() { let value = 55; let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(u64::MAX); + let mut storage_meter = StorageMeter::new(u64::MAX); MockStack::transfer( &origin, @@ -278,7 +278,7 @@ fn transfer_to_nonexistent_account_works() { let ed = ::Currency::minimum_balance(); let value = 1024; let evm_value = Pallet::::convert_native_to_evm(value); - let mut storage_meter = storage::meter::Meter::new(u64::MAX); + let mut storage_meter = StorageMeter::new(u64::MAX); // Transfers to nonexistent accounts should work set_balance(&ALICE, ed * 2); @@ -345,12 +345,12 @@ fn correct_transfer_on_call() { set_balance(&ALICE, 100); let balance = get_balance(&BOB_FALLBACK); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let _ = MockStack::run_call( origin.clone(), BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, evm_value.as_u64().into(), vec![], @@ -385,12 +385,12 @@ fn correct_transfer_on_delegate_call() { set_balance(&ALICE, 100); let balance = get_balance(&BOB_FALLBACK); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, evm_value.as_u64().into(), vec![], @@ -418,13 +418,13 @@ fn delegate_call_missing_contract() { set_balance(&ALICE, 100); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); // contract code missing should still succeed to mimic EVM behavior. assert_ok!(MockStack::run_call( origin.clone(), BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -436,7 +436,7 @@ fn delegate_call_missing_contract() { assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -459,12 +459,12 @@ fn changes_are_reverted_on_failing_call() { set_balance(&ALICE, 100); let balance = get_balance(&BOB); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let output = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, 55u64.into(), vec![], @@ -489,7 +489,7 @@ fn balance_too_low() { let ed = ::Currency::minimum_balance(); set_balance(&ALICE, ed * 2); set_balance(&from, ed + 99); - let mut storage_meter = storage::meter::Meter::new(u64::MAX); + let mut storage_meter = StorageMeter::new(u64::MAX); let result = MockStack::transfer( &Origin::from_account_id(ALICE), @@ -517,13 +517,13 @@ fn output_is_returned_on_success() { ExtBuilder::default().build().execute_with(|| { let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); place_contract(&BOB, return_ch); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -547,12 +547,12 @@ fn output_is_returned_on_failure() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, return_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -576,12 +576,12 @@ fn input_data_to_call() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, input_data_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![1, 2, 3, 4], @@ -604,15 +604,16 @@ fn input_data_to_instantiate() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap(); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let executable = + MockExecutable::from_storage(input_data_ch, &mut weight_meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); let result = MockStack::run_instantiate( ALICE, executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, min_balance.into(), vec![1, 2, 3, 4], @@ -654,12 +655,12 @@ fn max_depth() { set_balance(&BOB, 1); place_contract(&BOB, recurse_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, value.into(), vec![], @@ -709,12 +710,12 @@ fn caller_returns_proper_values() { place_contract(&BOB, bob_ch); place_contract(&CHARLIE, charlie_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -765,12 +766,12 @@ fn origin_returns_proper_values() { place_contract(&BOB, bob_ch); place_contract(&CHARLIE, charlie_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -804,11 +805,11 @@ fn to_account_id_returns_proper_values() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, bob_code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -840,12 +841,12 @@ fn code_hash_returns_proper_values() { ); place_contract(&BOB, bob_code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); // ALICE (not contract) -> BOB (contract) let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -866,12 +867,12 @@ fn own_code_hash_returns_proper_values() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, bob_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); // ALICE (not contract) -> BOB (contract) let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -902,12 +903,12 @@ fn caller_is_origin_returns_proper_values() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); // ALICE -> BOB (caller is origin) -> CHARLIE (caller is not origin) let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -928,12 +929,12 @@ fn root_caller_succeeds() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_bob); let origin = Origin::Root; - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); // root -> BOB (caller is root) let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -954,12 +955,12 @@ fn root_caller_does_not_succeed_when_value_not_zero() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_bob); let origin = Origin::Root; - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); // root -> BOB (caller is root) let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, 1u64.into(), vec![0], @@ -990,12 +991,12 @@ fn root_caller_succeeds_with_consecutive_calls() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::Root; - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); // root -> BOB (caller is root) -> CHARLIE (caller is not root) let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -1028,12 +1029,12 @@ fn address_returns_proper_values() { place_contract(&BOB, bob_ch); place_contract(&CHARLIE, charlie_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -1049,15 +1050,15 @@ fn refuse_instantiate_with_value_below_existential_deposit() { let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success()); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); - let mut storage_meter = storage::meter::Meter::new(0); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let executable = MockExecutable::from_storage(dummy_ch, &mut weight_meter).unwrap(); + let mut storage_meter = StorageMeter::new(0); assert_matches!( MockStack::run_instantiate( ALICE, executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, U256::zero(), // <- zero value vec![], @@ -1081,16 +1082,16 @@ fn instantiation_work_with_success_output() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let executable = MockExecutable::from_storage(dummy_ch, &mut weight_meter).unwrap(); set_balance(&ALICE, min_balance * 1000); - let mut storage_meter = storage::meter::Meter::new(min_balance * 100); + let mut storage_meter = StorageMeter::new(min_balance * 100); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( ALICE, executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, Pallet::::convert_native_to_evm(min_balance), vec![], @@ -1132,16 +1133,16 @@ fn instantiation_fails_with_failing_output() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let executable = MockExecutable::from_storage(dummy_ch, &mut weight_meter).unwrap(); set_balance(&ALICE, min_balance * 1000); - let mut storage_meter = storage::meter::Meter::new(min_balance * 100); + let mut storage_meter = StorageMeter::new(min_balance * 100); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( ALICE, executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, Pallet::::convert_native_to_evm(min_balance), vec![], @@ -1198,13 +1199,13 @@ fn instantiation_from_contract() { set_balance(&ALICE, min_balance * 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(min_balance * 10); + let mut storage_meter = StorageMeter::new(min_balance * 10); assert_matches!( MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, Pallet::::convert_native_to_evm(min_balance * 10), vec![], @@ -1267,13 +1268,13 @@ fn instantiation_traps() { set_balance(&BOB_FALLBACK, 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(200); + let mut storage_meter = StorageMeter::new(200); assert_matches!( MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -1296,16 +1297,16 @@ fn termination_from_instantiate_fails() { .existential_deposit(15) .build() .execute_with(|| { - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap(); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let executable = MockExecutable::from_storage(terminate_ch, &mut weight_meter).unwrap(); set_balance(&ALICE, 10_000); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_eq!( MockStack::run_instantiate( ALICE, executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, Pallet::::convert_native_to_evm(100u64), vec![], @@ -1360,12 +1361,12 @@ fn in_memory_changes_not_discarded() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -1413,15 +1414,15 @@ fn recursive_call_during_constructor_is_balance_transfer() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap(); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let executable = MockExecutable::from_storage(code, &mut weight_meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); let result = MockStack::run_instantiate( ALICE, executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, 10u64.into(), vec![], @@ -1453,15 +1454,15 @@ fn cannot_send_more_balance_than_available_to_self() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); MockStack::run_call( origin, BOB_ADDR, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, U256::zero(), vec![], @@ -1487,13 +1488,13 @@ fn call_reentry_direct_recursion() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); // Calling another contract should succeed assert_ok!(MockStack::run_call( origin.clone(), BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), CHARLIE_ADDR.as_bytes().to_vec(), @@ -1505,7 +1506,7 @@ fn call_reentry_direct_recursion() { MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), BOB_ADDR.as_bytes().to_vec(), @@ -1540,14 +1541,14 @@ fn call_deny_reentry() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); // BOB -> CHARLIE -> BOB fails as BOB denies reentry. assert_err!( MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -1575,16 +1576,16 @@ fn minimum_balance_must_return_converted_balance() { .with_code_hashes(MockLoader::code_hashes()) .build() .execute_with(|| { - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); let succ_fail_executable = - MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap(); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + MockExecutable::from_storage(succ_fail_code, &mut weight_meter).unwrap(); + let mut storage_meter = StorageMeter::new(deposit_limit::()); set_balance(&ALICE, min_balance * 10_000); assert_ok!(MockStack::run_instantiate( ALICE, succ_fail_executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, min_balance_evm_value, vec![], @@ -1653,23 +1654,24 @@ fn nonce() { let min_balance = ::Currency::minimum_balance(); let min_balance_evm_value: U256 = Pallet::::convert_native_to_evm(min_balance); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let fail_executable = MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap(); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let fail_executable = + MockExecutable::from_storage(fail_code, &mut weight_meter).unwrap(); let success_executable = - MockExecutable::from_storage(success_code, &mut gas_meter).unwrap(); + MockExecutable::from_storage(success_code, &mut weight_meter).unwrap(); let succ_fail_executable = - MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap(); + MockExecutable::from_storage(succ_fail_code, &mut weight_meter).unwrap(); let succ_succ_executable = - MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap(); + MockExecutable::from_storage(succ_succ_code, &mut weight_meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); set_balance(&BOB, min_balance * 10_000); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); // fail should not increment MockStack::run_instantiate( ALICE, fail_executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, min_balance_evm_value * 100, vec![], @@ -1682,7 +1684,7 @@ fn nonce() { assert_ok!(MockStack::run_instantiate( ALICE, success_executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, min_balance_evm_value * 100, vec![], @@ -1694,7 +1696,7 @@ fn nonce() { assert_ok!(MockStack::run_instantiate( ALICE, succ_fail_executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, min_balance_evm_value * 200, vec![], @@ -1706,7 +1708,7 @@ fn nonce() { assert_ok!(MockStack::run_instantiate( ALICE, succ_succ_executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, min_balance_evm_value * 200, vec![], @@ -1766,15 +1768,15 @@ fn set_storage_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, U256::zero(), vec![], @@ -1864,15 +1866,15 @@ fn set_storage_varsized_key_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, U256::zero(), vec![], @@ -1902,15 +1904,15 @@ fn get_storage_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, U256::zero(), vec![], @@ -1940,15 +1942,15 @@ fn get_storage_size_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, U256::zero(), vec![], @@ -1989,15 +1991,15 @@ fn get_storage_varsized_key_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, U256::zero(), vec![], @@ -2038,15 +2040,15 @@ fn get_storage_size_varsized_key_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, U256::zero(), vec![], @@ -2116,11 +2118,11 @@ fn set_transient_storage_works() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(deposit_limit::()); + let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -2177,12 +2179,12 @@ fn get_transient_storage_works() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -2216,11 +2218,11 @@ fn get_transient_storage_size_works() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -2269,12 +2271,12 @@ fn rollback_transient_storage_works() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -2301,11 +2303,11 @@ fn ecdsa_to_eth_address_returns_proper_value() { place_contract(&BOB, bob_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -2397,12 +2399,12 @@ fn last_frame_output_works_on_instantiate() { set_balance(&BOB, 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(200); + let mut storage_meter = StorageMeter::new(200); MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -2457,12 +2459,12 @@ fn last_frame_output_works_on_nested_call() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], @@ -2523,12 +2525,12 @@ fn last_frame_output_is_always_reset() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_bob); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); let result = MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -2572,12 +2574,12 @@ fn immutable_data_access_checks_work() { set_balance(&BOB, 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(200); + let mut storage_meter = StorageMeter::new(200); MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -2625,7 +2627,7 @@ fn correct_immutable_data_in_delegate_call() { place_contract(&CHARLIE, charlie_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(200); + let mut storage_meter = StorageMeter::new(200); // Place unique immutable data for each contract >::insert::<_, ImmutableData>( @@ -2640,7 +2642,7 @@ fn correct_immutable_data_in_delegate_call() { MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -2672,13 +2674,13 @@ fn immutable_data_set_overrides() { .execute_with(|| { set_balance(&ALICE, 1000); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(200); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let mut storage_meter = StorageMeter::new(200); + let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); let addr = MockStack::run_instantiate( ALICE, - MockExecutable::from_storage(hash, &mut gas_meter).unwrap(), - &mut gas_meter, + MockExecutable::from_storage(hash, &mut weight_meter).unwrap(), + &mut weight_meter, &mut storage_meter, U256::zero(), vec![], @@ -2691,7 +2693,7 @@ fn immutable_data_set_overrides() { MockStack::run_call( origin, addr, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -2732,12 +2734,12 @@ fn immutable_data_set_errors_with_empty_data() { set_balance(&BOB, 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(200); + let mut storage_meter = StorageMeter::new(200); MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![], @@ -2792,12 +2794,12 @@ fn block_hash_returns_proper_values() { place_contract(&BOB, bob_code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(0); + let mut storage_meter = StorageMeter::new(0); assert_matches!( MockStack::run_call( origin, BOB_ADDR, - &mut GasMeter::::new(GAS_LIMIT), + &mut WeightMeter::::new(WEIGHT_LIMIT), &mut storage_meter, U256::zero(), vec![0], diff --git a/substrate/frame/revive/src/impl_fungibles.rs b/substrate/frame/revive/src/impl_fungibles.rs index 3e9e2653e8ad1..47e66e124eaeb 100644 --- a/substrate/frame/revive/src/impl_fungibles.rs +++ b/substrate/frame/revive/src/impl_fungibles.rs @@ -45,7 +45,7 @@ use sp_runtime::{traits::AccountIdConversion, DispatchError}; use super::{address::AddressMapper, pallet, Config, ContractResult, ExecConfig, Pallet, Weight}; use ethereum_standards::IERC20; -const GAS_LIMIT: Weight = Weight::from_parts(500_000_000_000, 10 * 1024 * 1024); +const WEIGHT_LIMIT: Weight = Weight::from_parts(1_000_000_000, 100_000); impl Pallet { // Test checking account for the `fungibles::*` implementation. @@ -69,7 +69,7 @@ impl fungibles::Inspect<::AccountId> for P OriginFor::::signed(Self::checking_account()), asset_id, U256::zero(), - GAS_LIMIT, + WEIGHT_LIMIT, <::Currency as fungible::Inspect<_>>::total_issuance(), data, ExecConfig::new_substrate_tx(), @@ -104,7 +104,7 @@ impl fungibles::Inspect<::AccountId> for P OriginFor::::signed(account_id.clone()), asset_id, U256::zero(), - GAS_LIMIT, + WEIGHT_LIMIT, <::Currency as fungible::Inspect<_>>::total_issuance(), data, ExecConfig::new_substrate_tx(), @@ -169,16 +169,16 @@ impl fungibles::Mutate<::AccountId> for Pa let checking_address = Address::from(Into::<[u8; 20]>::into(checking_account_eth)); let data = IERC20::transferCall { to: checking_address, value: EU256::from(amount) }.abi_encode(); - let ContractResult { result, gas_consumed, .. } = Self::bare_call( + let ContractResult { result, weight_consumed, .. } = Self::bare_call( OriginFor::::signed(who.clone()), asset_id, U256::zero(), - GAS_LIMIT, + WEIGHT_LIMIT, <::Currency as fungible::Inspect<_>>::total_issuance(), data, ExecConfig::new_substrate_tx(), ); - log::trace!(target: "whatiwant", "{gas_consumed}"); + log::trace!(target: "whatiwant", "{weight_consumed}"); if let Ok(return_value) = result { if return_value.did_revert() { Err("Contract reverted".into()) @@ -209,7 +209,7 @@ impl fungibles::Mutate<::AccountId> for Pa OriginFor::::signed(Self::checking_account()), asset_id, U256::zero(), - GAS_LIMIT, + WEIGHT_LIMIT, <::Currency as fungible::Inspect<_>>::total_issuance(), data, ExecConfig::new_substrate_tx(), diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 1b341844e3c68..5cfc419b3f411 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -27,9 +27,9 @@ mod benchmarking; mod call_builder; mod debug; mod exec; -mod gas; mod impl_fungibles; mod limits; +mod metering; mod primitives; mod storage; #[cfg(test)] @@ -54,9 +54,12 @@ use crate::{ runtime::SetWeightLimit, CallTracer, GenericTransaction, PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, }, - exec::{AccountIdOf, ExecError, Stack as ExecStack}, - gas::GasMeter, - storage::{meter::Meter as StorageMeter, AccountType, DeletionQueueManager}, + exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, + metering::{ + storage::Meter as StorageMeter, + weight::{Token as WeightToken, WeightMeter as ContractWeightMeter}, + }, + storage::{AccountType, DeletionQueueManager}, tracing::if_tracing, vm::{pvm::extract_code_and_data, CodeInfo, RuntimeCosts}, weightinfo_extension::OnFinalizeBlockParts, @@ -999,19 +1002,48 @@ pub mod pallet { memory_left.saturating_mul(TOTAL_MEMORY_DEVIDER.into()).abs() / 1024 ); + // Validators are configured to be able to use more memory than block builders. This is + // because in addition to `max_runtime_mem` they need to hold additional data in + // memory: PoV in multiple copies (1x encoded + 2x decoded) and all storage which + // includes emitted events. The assumption is that storage/events size + // can be a maximum of half of the validator runtime memory - max_runtime_mem. + let max_block_ref_time = T::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap_or_else(|| T::BlockWeights::get().max_block) + .ref_time(); + let max_payload_size = limits::PAYLOAD_BYTES; + let max_key_size = + Key::try_from_var(alloc::vec![0u8; limits::STORAGE_KEY_BYTES as usize]) + .expect("Key of maximal size shall be created") + .hash() + .len() as u32; + + let max_immutable_key_size = T::AccountId::max_encoded_len() as u32; + let max_immutable_size: u32 = ((max_block_ref_time / + (>::weight(&RuntimeCosts::SetImmutableData( + limits::IMMUTABLE_BYTES, + )) + .ref_time())) + .saturating_mul(limits::IMMUTABLE_BYTES.saturating_add(max_immutable_key_size) as u64)) + .try_into() + .expect("Immutable data size too big"); + // We can use storage to store items using the available block ref_time with the // `set_storage` host function. - let max_storage_size = max_block_weight - .checked_div_per_component( - &>::weight(&RuntimeCosts::SetStorage { - new_bytes: limits::STORAGE_BYTES, - old_bytes: 0, - }) - .saturating_mul(u64::from(limits::STORAGE_BYTES).saturating_add(max_key_size)), - ) - .unwrap() - .saturating_add(max_immutable_size.into()) - .saturating_add(max_eth_block_builder_bytes.into()); + let max_storage_size: u32 = ((max_block_ref_time / + (>::weight(&RuntimeCosts::SetStorage { + new_bytes: max_payload_size, + old_bytes: 0, + }) + .ref_time())) + .saturating_mul(max_payload_size.saturating_add(max_key_size) as u64)) + .saturating_add(max_immutable_size.into()) + .try_into() + .expect("Storage size too big"); + + let max_pvf_mem: u32 = T::PVFMemory::get(); + let storage_size_limit = max_pvf_mem.saturating_sub(max_runtime_mem) / 2; assert!( max_storage_size < storage_size_limit, @@ -1019,8 +1051,26 @@ pub mod pallet { max_storage_size, storage_size_limit ); + + // We can use storage to store events using the available block ref_time with the + // `deposit_event` host function. The overhead of stored events, which is around 100B, + // is not taken into account to simplify calculations, as it does not change much. + let max_events_size: u32 = ((max_block_ref_time / + (>::weight(&RuntimeCosts::DepositEvent { + num_topic: 0, + len: max_payload_size, + }) + .saturating_add(>::weight(&RuntimeCosts::HostFn)) + .ref_time())) + .saturating_mul(max_payload_size as u64)) + .try_into() + .expect("Events size too big"); + max_events_size < storage_size_limit, + "Maximal events size {} exceeds the events limit {}", + max_events_size, + storage_size_limit + ); } - } #[pallet::call] impl Pallet { @@ -1034,7 +1084,6 @@ pub mod pallet { /// /// This call cannot be dispatched directly; attempting to do so will result in a failed /// transaction. It serves as a wrapper for an Ethereum transaction. When submitted, the - /// runtime converts it into a [`sp_runtime::generic::CheckedExtrinsic`] by recovering the /// signer and validating the transaction. #[allow(unused_variables)] #[pallet::call_index(0)] @@ -1049,7 +1098,7 @@ pub mod pallet { /// /// * `dest`: Address of the contract to call. /// * `value`: The balance to transfer from the `origin` to `dest`. - /// * `gas_limit`: The gas limit enforced when executing the constructor. + /// * `weight_limit`: The weight limit enforced when executing the constructor. /// * `storage_deposit_limit`: The maximum amount of balance that can be charged from the /// caller to pay for the storage consumed. /// * `data`: The input data to pass to the contract. @@ -1060,12 +1109,12 @@ 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(::WeightInfo::call().saturating_add(*gas_limit))] + #[pallet::weight(::WeightInfo::call().saturating_add(*weight_limit))] pub fn call( origin: OriginFor, dest: H160, #[pallet::compact] value: BalanceOf, - gas_limit: Weight, + weight_limit: Weight, #[pallet::compact] storage_deposit_limit: BalanceOf, data: Vec, ) -> DispatchResultWithPostInfo { @@ -1074,7 +1123,7 @@ pub mod pallet { origin, dest, Pallet::::convert_native_to_evm(value), - gas_limit, + weight_limit, storage_deposit_limit, data, ExecConfig::new_substrate_tx(), @@ -1085,7 +1134,11 @@ pub mod pallet { output.result = Err(>::ContractReverted.into()); } } - dispatch_result(output.result, output.gas_consumed, ::WeightInfo::call()) + dispatch_result( + output.result, + output.weight_consumed, + ::WeightInfo::call(), + ) } /// Instantiates a contract from a previously deployed vm binary. @@ -1095,12 +1148,12 @@ pub mod pallet { /// must be supplied. #[pallet::call_index(2)] #[pallet::weight( - ::WeightInfo::instantiate(data.len() as u32).saturating_add(*gas_limit) + ::WeightInfo::instantiate(data.len() as u32).saturating_add(*weight_limit) )] pub fn instantiate( origin: OriginFor, #[pallet::compact] value: BalanceOf, - gas_limit: Weight, + weight_limit: Weight, #[pallet::compact] storage_deposit_limit: BalanceOf, code_hash: sp_core::H256, data: Vec, @@ -1111,7 +1164,7 @@ pub mod pallet { let mut output = Self::bare_instantiate( origin, Pallet::::convert_native_to_evm(value), - gas_limit, + weight_limit, storage_deposit_limit, Code::Existing(code_hash), data, @@ -1125,7 +1178,7 @@ pub mod pallet { } dispatch_result( output.result.map(|result| result.result), - output.gas_consumed, + output.weight_consumed, ::WeightInfo::instantiate(data_len), ) } @@ -1140,7 +1193,7 @@ pub mod pallet { /// # Parameters /// /// * `value`: The balance to transfer from the `origin` to the newly created contract. - /// * `gas_limit`: The gas limit enforced when executing the constructor. + /// * `weight_limit`: The weight limit enforced when executing the constructor. /// * `storage_deposit_limit`: The maximum amount of balance that can be charged/reserved /// from the caller to pay for the storage consumed. /// * `code`: The contract code to deploy in raw bytes. @@ -1160,12 +1213,12 @@ pub mod pallet { #[pallet::call_index(3)] #[pallet::weight( ::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32) - .saturating_add(*gas_limit) + .saturating_add(*weight_limit) )] pub fn instantiate_with_code( origin: OriginFor, #[pallet::compact] value: BalanceOf, - gas_limit: Weight, + weight_limit: Weight, #[pallet::compact] storage_deposit_limit: BalanceOf, code: Vec, data: Vec, @@ -1177,7 +1230,7 @@ pub mod pallet { let mut output = Self::bare_instantiate( origin, Pallet::::convert_native_to_evm(value), - gas_limit, + weight_limit, storage_deposit_limit, Code::Upload(code), data, @@ -1191,7 +1244,7 @@ pub mod pallet { } dispatch_result( output.result.map(|result| result.result), - output.gas_consumed, + output.weight_consumed, ::WeightInfo::instantiate_with_code(code_len, data_len), ) } @@ -1220,13 +1273,12 @@ pub mod pallet { #[pallet::call_index(10)] #[pallet::weight( ::WeightInfo::eth_instantiate_with_code(code.len() as u32, data.len() as u32, Pallet::::has_dust(*value).into()) - .saturating_add(T::WeightInfo::on_finalize_block_per_tx(transaction_encoded.len() as u32)) - .saturating_add(*gas_limit) + .saturating_add(*weight_limit) )] pub fn eth_instantiate_with_code( origin: OriginFor, value: U256, - gas_limit: Weight, + weight_limit: Weight, code: Vec, data: Vec, transaction_encoded: Vec, @@ -1237,7 +1289,7 @@ pub mod pallet { Self::ensure_non_contract_if_signed(&origin)?; let mut call = Call::::eth_instantiate_with_code { value, - gas_limit, + weight_limit, code: code.clone(), data: data.clone(), transaction_encoded: transaction_encoded.clone(), @@ -1248,52 +1300,38 @@ pub mod pallet { let info = T::FeeInfo::dispatch_info(&call); let base_info = T::FeeInfo::base_dispatch_info(&mut call); drop(call); - - block_storage::with_ethereum_context::(transaction_encoded, || { - let mut output = Self::bare_instantiate( - origin, - value, - gas_limit, - BalanceOf::::max_value(), - Code::Upload(code), - data, - None, - ExecConfig::new_eth_tx( - effective_gas_price, - encoded_len, - base_info.total_weight(), - ), - ); - - if let Ok(retval) = &output.result { - if retval.result.did_revert() { - output.result = Err(>::ContractReverted.into()); - } + let mut output = Self::bare_instantiate( + origin, + value, + weight_limit, + BalanceOf::::max_value(), + Code::Upload(code), + data, + None, + ExecConfig::new_eth_tx(effective_gas_price, encoded_len, base_info.total_weight()), + ); + if let Ok(retval) = &output.result { + if retval.result.did_revert() { + output.result = Err(>::ContractReverted.into()); } - - let result = dispatch_result( - output.result.map(|result| result.result), - output.gas_consumed, - base_info.call_weight, - ); - let result = T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result); - (output.gas_consumed, result) - }) + } + let result = dispatch_result( + output.result.map(|result| result.result), + output.weight_consumed, + base_info.call_weight, + ); + T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result) } /// 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) - .saturating_add(T::WeightInfo::on_finalize_block_per_tx(transaction_encoded.len() as u32)) - )] + #[pallet::weight(::WeightInfo::eth_call(Pallet::::has_dust(*value).into()).saturating_add(*weight_limit))] pub fn eth_call( origin: OriginFor, dest: H160, value: U256, - gas_limit: Weight, + weight_limit: Weight, data: Vec, transaction_encoded: Vec, effective_gas_price: U256, @@ -1304,7 +1342,7 @@ pub mod pallet { let mut call = Call::::eth_call { dest, value, - gas_limit, + weight_limit, data: data.clone(), transaction_encoded: transaction_encoded.clone(), effective_gas_price, @@ -1314,33 +1352,23 @@ pub mod pallet { let info = T::FeeInfo::dispatch_info(&call); let base_info = T::FeeInfo::base_dispatch_info(&mut call); drop(call); - - block_storage::with_ethereum_context::(transaction_encoded, || { - let mut output = Self::bare_call( - origin, - dest, - value, - gas_limit, - BalanceOf::::max_value(), - data, - ExecConfig::new_eth_tx( - effective_gas_price, - encoded_len, - base_info.total_weight(), - ), - ); - - if let Ok(return_value) = &output.result { - if return_value.did_revert() { - output.result = Err(>::ContractReverted.into()); - } + let mut output = Self::bare_call( + origin, + dest, + value, + weight_limit, + BalanceOf::::max_value(), + data, + ExecConfig::new_eth_tx(effective_gas_price, encoded_len, base_info.total_weight()), + ); + if let Ok(return_value) = &output.result { + if return_value.did_revert() { + output.result = Err(>::ContractReverted.into()); } - - let result = - dispatch_result(output.result, output.gas_consumed, base_info.call_weight); - let result = T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result); - (output.gas_consumed, result) - }) + } + let result = + dispatch_result(output.result, output.weight_consumed, base_info.call_weight); + T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result) } /// Upload new `code` without instantiating a contract from it. @@ -1468,14 +1496,14 @@ pub mod pallet { } } -/// Create a dispatch result reflecting the amount of consumed gas. +/// Create a dispatch result reflecting the amount of consumed weight. fn dispatch_result( result: Result, - gas_consumed: Weight, + weight_consumed: Weight, base_weight: Weight, ) -> DispatchResultWithPostInfo { let post_info = PostDispatchInfo { - actual_weight: Some(gas_consumed.saturating_add(base_weight)), + actual_weight: Some(weight_consumed.saturating_add(base_weight)), pays_fee: Default::default(), }; @@ -1495,12 +1523,15 @@ impl Pallet { origin: OriginFor, dest: H160, evm_value: U256, - gas_limit: Weight, + weight_limit: Weight, storage_deposit_limit: BalanceOf, data: Vec, exec_config: ExecConfig, ) -> ContractResult> { - let mut gas_meter = GasMeter::new(gas_limit); + if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) { + return contract_result; + } + let mut weight_meter = ContractWeightMeter::new(weight_limit); let mut storage_deposit = Default::default(); let try_call = || { @@ -1509,7 +1540,7 @@ impl Pallet { let result = ExecStack::>::run_call( origin.clone(), dest, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, evm_value, data, @@ -1524,8 +1555,8 @@ impl Pallet { let result = Self::run_guarded(try_call); ContractResult { result: result.map_err(|r| r.error), - gas_consumed: gas_meter.gas_consumed(), - gas_required: gas_meter.gas_required(), + weight_consumed: weight_meter.weight_consumed(), + weight_required: weight_meter.weight_required(), storage_deposit, } } @@ -1549,14 +1580,18 @@ impl Pallet { pub fn bare_instantiate( origin: OriginFor, evm_value: U256, - gas_limit: Weight, + weight_limit: Weight, mut storage_deposit_limit: BalanceOf, code: Code, data: Vec, salt: Option<[u8; 32]>, exec_config: ExecConfig, ) -> ContractResult> { - let mut gas_meter = GasMeter::new(gas_limit); + // Enforce EIP-3607 for top-level signed origins: deny signed contract addresses. + if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) { + return contract_result; + } + let mut weight_meter = ContractWeightMeter::new(weight_limit); let mut storage_deposit = Default::default(); let try_instantiate = || { let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?; @@ -1583,14 +1618,14 @@ impl Pallet { return Err(>::CodeRejected.into()) }, Code::Existing(code_hash) => - (ContractBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()), + (ContractBlob::from_storage(code_hash, &mut weight_meter)?, Default::default()), }; let instantiate_origin = ExecOrigin::from_account_id(instantiate_account.clone()); let mut storage_meter = StorageMeter::new(storage_deposit_limit); let result = ExecStack::>::run_instantiate( instantiate_account, executable, - &mut gas_meter, + &mut weight_meter, &mut storage_meter, evm_value, data, @@ -1607,8 +1642,8 @@ impl Pallet { result: output .map(|(addr, result)| InstantiateReturnValue { result, addr }) .map_err(|e| e.error), - gas_consumed: gas_meter.gas_consumed(), - gas_required: gas_meter.gas_required(), + weight_consumed: weight_meter.weight_consumed(), + weight_required: weight_meter.weight_required(), storage_deposit, } } @@ -1758,7 +1793,7 @@ impl Pallet { }; EthTransactInfo { - gas_required: result.gas_required, + weight_required: result.weight_required, storage_deposit: result.storage_deposit.charge_or_zero(), data, eth_gas: Default::default(), @@ -1800,7 +1835,7 @@ impl Pallet { }; EthTransactInfo { - gas_required: result.gas_required, + weight_required: result.weight_required, storage_deposit: result.storage_deposit.charge_or_zero(), data: returned_data, eth_gas: Default::default(), @@ -1809,7 +1844,7 @@ impl Pallet { }; // replace the weight passed in the transaction with the dry_run result - call_info.call.set_weight_limit(dry_run.gas_required); + call_info.call.set_weight_limit(dry_run.weight_required); // we notify the wallet that the tx would not fit let total_weight = T::FeeInfo::dispatch_info(&call_info.call).total_weight(); @@ -1854,10 +1889,7 @@ impl Pallet { tx_fee={transaction_fee:?} \ storage_deposit={:?}\ ", - dry_run.gas_required, - max_weight.saturating_sub(total_weight), - call_info.encoded_len, - dry_run.storage_deposit, + dry_run.weight_required, ); dry_run.eth_gas = eth_gas; @@ -2173,8 +2205,81 @@ impl Pallet { } /// Convert a weight to a gas value. - pub fn evm_gas_from_weight(weight: Weight) -> U256 { - T::FeeInfo::weight_to_fee(&weight, Combinator::Max).into() + fn evm_gas_from_weight(weight: Weight) -> U256 { + T::FeeInfo::weight_to_fee(&weight).into() + } + + /// Ensure the origin has no code deplyoyed if it is a signed origin. + fn ensure_non_contract_if_signed( + origin: &OriginFor, + ) -> Result<(), ContractResult>> { + use crate::exec::is_precompile; + let Ok(who) = ensure_signed(origin.clone()) else { return Ok(()) }; + let address = >::to_address(&who); + + // EIP_1052: precompile can never be used as EOA. + if is_precompile::>(&address) { + log::debug!( + target: crate::LOG_TARGET, + "EIP-3607: reject externally-signed tx from precompile account {:?}", + address + ); + return Err(ContractResult { + result: Err(DispatchError::BadOrigin), + weight_consumed: Weight::default(), + weight_required: Weight::default(), + storage_deposit: Default::default(), + }); + } + + // Deployed code exists when hash is neither zero (no account) nor EMPTY_CODE_HASH + // (account exists but no code). + if >::is_contract(&address) { + log::debug!( + target: crate::LOG_TARGET, + "EIP-3607: reject externally-signed tx from contract account {:?}", + address + ); + return Err(ContractResult { + result: Err(DispatchError::BadOrigin), + weight_consumed: Weight::default(), + weight_required: Weight::default(), + storage_deposit: Default::default(), + }); + } + Ok(()) + } + + /// Pallet account, used to hold funds for contracts upload deposit. + pub fn account_id() -> T::AccountId { + use frame_support::PalletId; + use sp_runtime::traits::AccountIdConversion; + PalletId(*b"py/reviv").into_account_truncating() + } + + /// The address of the validator that produced the current block. + pub fn block_author() -> Option { + use frame_support::traits::FindAuthor; + + let digest = >::digest(); + let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); + + let account_id = T::FindAuthor::find_author(pre_runtime_digests)?; + Some(T::AddressMapper::to_address(&account_id)) + } + + /// Returns the code at `address`. + /// + /// This takes pre-compiles into account. + pub fn code(address: &H160) -> Vec { + use precompiles::{All, Precompiles}; + if let Some(code) = >::code(address.as_fixed_bytes()) { + return code.into() + } + AccountInfo::::load_contract(&address) + .and_then(|contract| >::get(contract.code_hash)) + .map(|code| code.into()) + .unwrap_or_default() } /// Transfer a deposit from some account to another. @@ -2501,15 +2606,15 @@ macro_rules! impl_runtime_apis_plus_revive_traits { ($Runtime: ty, $Revive: ident, $Executive: ty, $EthExtra: ty, $($rest:tt)*) => { impl $crate::evm::runtime::SetWeightLimit for RuntimeCall { - fn set_weight_limit(&mut self, weight_limit: Weight) -> Weight { + fn set_weight_limit(&mut self, new_weight_limit: Weight) -> Weight { use $crate::pallet::Call as ReviveCall; match self { Self::$Revive( - ReviveCall::eth_call{ gas_limit, .. } | - ReviveCall::eth_instantiate_with_code{ gas_limit, .. } + ReviveCall::eth_call{ weight_limit, .. } | + ReviveCall::eth_instantiate_with_code{ weight_limit, .. } ) => { - let old = *gas_limit; - *gas_limit = weight_limit; + let old = *weight_limit; + *weight_limit = new_weight_limit; old }, _ => Weight::default(), @@ -2575,7 +2680,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { origin: AccountId, dest: $crate::H160, value: Balance, - gas_limit: Option<$crate::Weight>, + weight_limit: Option<$crate::Weight>, storage_deposit_limit: Option, input_data: Vec, ) -> $crate::ContractResult<$crate::ExecReturnValue, Balance> { @@ -2588,7 +2693,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { ::RuntimeOrigin::signed(origin), dest, $crate::Pallet::::convert_native_to_evm(value), - gas_limit.unwrap_or(blockweights.max_block), + weight_limit.unwrap_or(blockweights.max_block), storage_deposit_limit.unwrap_or(u128::MAX), input_data, $crate::ExecConfig::new_substrate_tx().with_dry_run(), @@ -2598,7 +2703,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { fn instantiate( origin: AccountId, value: Balance, - gas_limit: Option<$crate::Weight>, + weight_limit: Option<$crate::Weight>, storage_deposit_limit: Option, code: $crate::Code, data: Vec, @@ -2612,7 +2717,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { $crate::Pallet::::bare_instantiate( ::RuntimeOrigin::signed(origin), $crate::Pallet::::convert_native_to_evm(value), - gas_limit.unwrap_or(blockweights.max_block), + weight_limit.unwrap_or(blockweights.max_block), storage_deposit_limit.unwrap_or(u128::MAX), code, data, diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs new file mode 100644 index 0000000000000..164804925e74c --- /dev/null +++ b/substrate/frame/revive/src/metering/mod.rs @@ -0,0 +1,19 @@ +// 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. + +pub mod storage; +pub mod weight; diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/metering/storage.rs similarity index 99% rename from substrate/frame/revive/src/storage/meter.rs rename to substrate/frame/revive/src/metering/storage.rs index 7c4b724fac460..4edf7ee72ae2e 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -149,7 +149,7 @@ impl Diff { let info = if let Some(info) = info { info } else { - return bytes_deposit.saturating_add(&items_deposit) + return bytes_deposit.saturating_add(&items_deposit); }; // Refunds are calculated pro rata based on the accumulated storage within the contract @@ -338,7 +338,7 @@ where // do not sapwn a frame. This is specifically to enforce the limit for those. if self.is_root && total_deposit.charge_or_zero() > self.limit { log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit); - return Err(>::StorageDepositLimitExhausted.into()) + return Err(>::StorageDepositLimitExhausted.into()); } self.total_deposit = total_deposit; @@ -494,7 +494,7 @@ impl> RawMeter { if let Deposit::Charge(amount) = total_deposit { if amount > self.limit { log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit); - return Err(>::StorageDepositLimitExhausted.into()) + return Err(>::StorageDepositLimitExhausted.into()); } } Ok(()) diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/metering/weight.rs similarity index 63% rename from substrate/frame/revive/src/gas.rs rename to substrate/frame/revive/src/metering/weight.rs index 9da6ce7266b93..a8e07266bc7ac 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/metering/weight.rs @@ -35,7 +35,7 @@ impl ChargedAmount { } } -/// Meter for syncing the gas between the executor and the gas meter. +/// Meter for syncing the gas between the executor and the weight meter. #[derive(DefaultNoBound)] struct EngineMeter { fuel: u64, @@ -59,7 +59,7 @@ impl EngineMeter { Weight::from_parts(consumed, 0) } - /// Charge the given amount of gas. + /// Charge the given amount of ref time. /// Returns the amount of fuel left. fn charge_ref_time(&mut self, ref_time: u64) -> Result { let amount = ref_time @@ -81,7 +81,7 @@ impl EngineMeter { } } -/// Used to capture the gas left before entering a host function. +/// Used to capture the ref time left before entering a host function. /// /// Has to be consumed in order to sync back the gas after leaving the host function. #[must_use] @@ -109,25 +109,25 @@ pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {} #[cfg(test)] impl TestAuxiliaries for T {} -/// This trait represents a token that can be used for charging `GasMeter`. +/// This trait represents a token that can be used for charging `WeightMeter`. /// There is no other way of charging it. /// /// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added /// for consistency). If inlined there should be no observable difference compared /// to a hand-written code. pub trait Token: Copy + Clone + TestAuxiliaries { - /// Return the amount of gas that should be taken by this token. + /// Return the amount of weight that should be taken by this token. /// /// This function should be really lightweight and must not fail. It is not /// expected that implementors will query the storage or do any kinds of heavy operations. /// /// That said, implementors of this function still can run into overflows /// while calculating the amount. In this case it is ok to use saturating operations - /// since on overflow they will return `max_value` which should consume all gas. + /// since on overflow they will return `max_value` which should consume all weight. fn weight(&self) -> Weight; - /// Returns true if this token is expected to influence the lowest gas limit. - fn influence_lowest_gas_limit(&self) -> bool { + /// Returns true if this token is expected to influence the lowest weight limit. + fn influence_lowest_weight_limit(&self) -> bool { true } } @@ -140,13 +140,13 @@ pub struct ErasedToken { } #[derive(DefaultNoBound)] -pub struct GasMeter { - gas_limit: Weight, - /// Amount of gas already consumed. Must be < `gas_limit`. - gas_consumed: Weight, - /// Due to `adjust_gas` and `nested` the `gas_consumed` can temporarily peak above its final - /// value. - gas_consumed_highest: Weight, +pub struct WeightMeter { + weight_limit: Weight, + /// Amount of weight already consumed. Must be < `weight_limit`. + weight_consumed: Weight, + /// Due to `adjust_weight` and `nested` the `weight_consumed` can temporarily peak above its + /// final value. + weight_consumed_highest: Weight, /// The amount of resources that was consumed by the execution engine. /// We have to track it separately in order to avoid the loss of precision that happens when /// converting from ref_time to the execution engine unit. @@ -156,50 +156,50 @@ pub struct GasMeter { tokens: Vec, } -impl GasMeter { - pub fn new(gas_limit: Weight) -> Self { - GasMeter { - gas_limit, - gas_consumed: Default::default(), - gas_consumed_highest: Default::default(), - engine_meter: EngineMeter::new(gas_limit), +impl WeightMeter { + pub fn new(weight_limit: Weight) -> Self { + WeightMeter { + weight_limit, + weight_consumed: Default::default(), + weight_consumed_highest: Default::default(), + engine_meter: EngineMeter::new(weight_limit), _phantom: PhantomData, #[cfg(test)] tokens: Vec::new(), } } - /// Create a new gas meter by removing *all* the gas from the current meter. + /// Create a new weight meter by removing *all* the weight from the current meter. /// /// This should only be used by the primordial frame in a sequence of calls - every subsequent /// frame should use [`nested`](Self::nested). pub fn nested_take_all(&mut self) -> Self { - GasMeter::new(self.gas_left()) + WeightMeter::new(self.weight_left()) } - /// Create a new gas meter for a nested call by removing gas from the current meter. + /// Create a new weight meter for a nested call by removing weight from the current meter. pub fn nested(&mut self, amount: Weight) -> Self { - GasMeter::new(self.gas_left().min(amount)) + WeightMeter::new(self.weight_left().min(amount)) } - /// Absorb the remaining gas of a nested meter after we are done using it. + /// Absorb the remaining weight of a nested meter after we are done using it. pub fn absorb_nested(&mut self, nested: Self) { - self.gas_consumed_highest = self - .gas_consumed - .saturating_add(nested.gas_required()) - .max(self.gas_consumed_highest); - self.gas_consumed += nested.gas_consumed; + self.weight_consumed_highest = self + .weight_consumed + .saturating_add(nested.weight_required()) + .max(self.weight_consumed_highest); + self.weight_consumed += nested.weight_consumed; } - /// Account for used gas. + /// Account for used weight. /// /// Amount is calculated by the given `token`. /// - /// Returns `OutOfGas` if there is not enough gas or addition of the specified - /// amount of gas has lead to overflow. + /// Returns `OutOfGas` if there is not enough weight or addition of the specified + /// amount of weight has lead to overflow. /// - /// NOTE that amount isn't consumed if there is not enough gas. This is considered - /// safe because we always charge gas before performing any resource-spending action. + /// NOTE that amount isn't consumed if there is not enough weight. This is considered + /// safe because we always charge weight before performing any resource-spending action. #[inline] pub fn charge>(&mut self, token: Tok) -> Result { #[cfg(test)] @@ -213,17 +213,17 @@ impl GasMeter { // It is OK to not charge anything on failure because we always charge _before_ we perform // any action let consumed = { - let consumed = self.gas_consumed.saturating_add(amount); - if consumed.any_gt(self.gas_limit) { + let consumed = self.weight_consumed.saturating_add(amount); + if consumed.any_gt(self.weight_limit) { Err(>::OutOfGas)?; } consumed }; - self.gas_consumed = consumed; + self.weight_consumed = consumed; Ok(ChargedAmount(amount)) } - /// Charge the specified token amount of gas or halt if not enough gas is left. + /// Charge the specified token amount of weight or halt if not enough weight is left. pub fn charge_or_halt>( &mut self, token: Tok, @@ -236,12 +236,12 @@ impl GasMeter { /// /// This is when a maximum a priori amount was charged and then should be partially /// refunded to match the actual amount. - pub fn adjust_gas>(&mut self, charged_amount: ChargedAmount, token: Tok) { - if token.influence_lowest_gas_limit() { - self.gas_consumed_highest = self.gas_required(); + pub fn adjust_weight>(&mut self, charged_amount: ChargedAmount, token: Tok) { + if token.influence_lowest_weight_limit() { + self.weight_consumed_highest = self.weight_required(); } let adjustment = charged_amount.0.saturating_sub(token.weight()); - self.gas_consumed = self.gas_consumed.saturating_sub(adjustment); + self.weight_consumed = self.weight_consumed.saturating_sub(adjustment); } /// Hand over the gas metering responsibility from the executor to this meter. @@ -256,12 +256,12 @@ impl GasMeter { let weight_consumed = self .engine_meter .set_fuel(engine_fuel.try_into().map_err(|_| Error::::OutOfGas)?); - self.gas_consumed.saturating_accrue(weight_consumed); - if self.gas_consumed.any_gt(self.gas_limit) { - self.gas_consumed = self.gas_limit; + self.weight_consumed.saturating_accrue(weight_consumed); + if self.weight_consumed.any_gt(self.weight_limit) { + self.weight_consumed = self.weight_limit; Err(>::OutOfGas)?; } - Ok(RefTimeLeft(self.gas_left().ref_time())) + Ok(RefTimeLeft(self.weight_left().ref_time())) } /// Hand over the gas metering responsibility from this meter to the executor. @@ -273,34 +273,34 @@ impl GasMeter { /// It is important that this does **not** actually sync with the executor. That has /// to be done by the caller. pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result { - let ref_time_consumed = before.0.saturating_sub(self.gas_left().ref_time()); + let ref_time_consumed = before.0.saturating_sub(self.weight_left().ref_time()); self.engine_meter.charge_ref_time(ref_time_consumed) } - /// Returns the amount of gas that is required to run the same call. + /// Returns the amount of weight that is required to run the same call. /// - /// This can be different from `gas_spent` because due to `adjust_gas` the amount of - /// spent gas can temporarily drop and be refunded later. - pub fn gas_required(&self) -> Weight { - self.gas_consumed_highest.max(self.gas_consumed) + /// This can be different from `weight_spent` because due to `adjust_weight` the amount of + /// spent weight can temporarily drop and be refunded later. + pub fn weight_required(&self) -> Weight { + self.weight_consumed_highest.max(self.weight_consumed) } - /// Returns how much gas was spent - pub fn gas_consumed(&self) -> Weight { - self.gas_consumed + /// Returns how much weight was spent + pub fn weight_consumed(&self) -> Weight { + self.weight_consumed } - /// Returns how much gas left from the initial budget. - pub fn gas_left(&self) -> Weight { - self.gas_limit.saturating_sub(self.gas_consumed) + /// Returns how much weight left from the initial budget. + pub fn weight_left(&self) -> Weight { + self.weight_limit.saturating_sub(self.weight_consumed) } - /// The amount of gas in terms of engine gas. + /// The amount of weight in terms of engine gas. pub fn engine_fuel_left(&self) -> Result { self.engine_meter.fuel.try_into().map_err(|_| >::OutOfGas.into()) } - /// Turn this GasMeter into a DispatchResult that contains the actually used gas. + /// Turn this WeightMeter into a DispatchResult that contains the actually used weight. pub fn into_dispatch_result( self, result: Result, @@ -310,7 +310,7 @@ impl GasMeter { E: Into, { let post_info = PostDispatchInfo { - actual_weight: Some(self.gas_consumed().saturating_add(base_weight)), + actual_weight: Some(self.weight_consumed().saturating_add(base_weight)), pays_fee: Default::default(), }; @@ -325,13 +325,13 @@ impl GasMeter { } pub fn consume_all(&mut self) { - self.gas_consumed = self.gas_limit; + self.weight_consumed = self.weight_limit; } } #[cfg(test)] mod tests { - use super::{GasMeter, Token, Weight}; + use super::{Token, Weight, WeightMeter}; use crate::tests::Test; /// A simple utility macro that helps to match against a @@ -371,7 +371,7 @@ mod tests { }; } - /// A trivial token that charges the specified number of gas units. + /// A trivial token that charges the specified number of weight units. #[derive(Copy, Clone, PartialEq, Eq, Debug)] struct SimpleToken(u64); impl Token for SimpleToken { @@ -382,87 +382,87 @@ mod tests { #[test] fn it_works() { - let gas_meter = GasMeter::::new(Weight::from_parts(50000, 0)); - assert_eq!(gas_meter.gas_left(), Weight::from_parts(50000, 0)); + let weight_meter = WeightMeter::::new(Weight::from_parts(50000, 0)); + assert_eq!(weight_meter.weight_left(), Weight::from_parts(50000, 0)); } #[test] fn tracing() { - let mut gas_meter = GasMeter::::new(Weight::from_parts(50000, 0)); - assert!(!gas_meter.charge(SimpleToken(1)).is_err()); + let mut weight_meter = WeightMeter::::new(Weight::from_parts(50000, 0)); + assert!(!weight_meter.charge(SimpleToken(1)).is_err()); - let mut tokens = gas_meter.tokens().iter(); + let mut tokens = weight_meter.tokens().iter(); match_tokens!(tokens, SimpleToken(1),); } - // This test makes sure that nothing can be executed if there is no gas. + // This test makes sure that nothing can be executed if there is no weight. #[test] fn refuse_to_execute_anything_if_zero() { - let mut gas_meter = GasMeter::::new(Weight::zero()); - assert!(gas_meter.charge(SimpleToken(1)).is_err()); + let mut weight_meter = WeightMeter::::new(Weight::zero()); + assert!(weight_meter.charge(SimpleToken(1)).is_err()); } /// Previously, passing a `Weight` of 0 to `nested` would consume all of the meter's current - /// gas. + /// weight. /// - /// Now, a `Weight` of 0 means no gas for the nested call. + /// Now, a `Weight` of 0 means no weight for the nested call. #[test] - fn nested_zero_gas_requested() { + fn nested_zero_weight_requested() { let test_weight = 50000.into(); - let mut gas_meter = GasMeter::::new(test_weight); - let gas_for_nested_call = gas_meter.nested(0.into()); + let mut weight_meter = WeightMeter::::new(test_weight); + let weight_for_nested_call = weight_meter.nested(0.into()); - assert_eq!(gas_meter.gas_left(), 50000.into()); - assert_eq!(gas_for_nested_call.gas_left(), 0.into()) + assert_eq!(weight_meter.weight_left(), 50000.into()); + assert_eq!(weight_for_nested_call.weight_left(), 0.into()) } #[test] - fn nested_some_gas_requested() { + fn nested_some_weight_requested() { let test_weight = 50000.into(); - let mut gas_meter = GasMeter::::new(test_weight); - let gas_for_nested_call = gas_meter.nested(10000.into()); + let mut weight_meter = WeightMeter::::new(test_weight); + let weight_for_nested_call = weight_meter.nested(10000.into()); - assert_eq!(gas_meter.gas_consumed(), 0.into()); - assert_eq!(gas_for_nested_call.gas_left(), 10000.into()) + assert_eq!(weight_meter.weight_consumed(), 0.into()); + assert_eq!(weight_for_nested_call.weight_left(), 10000.into()) } #[test] - fn nested_all_gas_requested() { + fn nested_all_weight_requested() { let test_weight = Weight::from_parts(50000, 50000); - let mut gas_meter = GasMeter::::new(test_weight); - let gas_for_nested_call = gas_meter.nested(test_weight); + let mut weight_meter = WeightMeter::::new(test_weight); + let weight_for_nested_call = weight_meter.nested(test_weight); - assert_eq!(gas_meter.gas_consumed(), Weight::from_parts(0, 0)); - assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) + assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); + assert_eq!(weight_for_nested_call.weight_left(), 50_000.into()) } #[test] - fn nested_excess_gas_requested() { + fn nested_excess_weight_requested() { let test_weight = Weight::from_parts(50000, 50000); - let mut gas_meter = GasMeter::::new(test_weight); - let gas_for_nested_call = gas_meter.nested(test_weight + 10000.into()); + let mut weight_meter = WeightMeter::::new(test_weight); + let weight_for_nested_call = weight_meter.nested(test_weight + 10000.into()); - assert_eq!(gas_meter.gas_consumed(), Weight::from_parts(0, 0)); - assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) + assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); + assert_eq!(weight_for_nested_call.weight_left(), 50_000.into()) } - // Make sure that the gas meter does not charge in case of overcharge + // Make sure that the weight meter does not charge in case of overcharge #[test] fn overcharge_does_not_charge() { - let mut gas_meter = GasMeter::::new(Weight::from_parts(200, 0)); + let mut weight_meter = WeightMeter::::new(Weight::from_parts(200, 0)); // The first charge is should lead to OOG. - assert!(gas_meter.charge(SimpleToken(300)).is_err()); + assert!(weight_meter.charge(SimpleToken(300)).is_err()); - // The gas meter should still contain the full 200. - assert!(gas_meter.charge(SimpleToken(200)).is_ok()); + // The weight meter should still contain the full 200. + assert!(weight_meter.charge(SimpleToken(200)).is_ok()); } // Charging the exact amount that the user paid for should be // possible. #[test] fn charge_exact_amount() { - let mut gas_meter = GasMeter::::new(Weight::from_parts(25, 0)); - assert!(!gas_meter.charge(SimpleToken(25)).is_err()); + let mut weight_meter = WeightMeter::::new(Weight::from_parts(25, 0)); + assert!(!weight_meter.charge(SimpleToken(25)).is_err()); } } diff --git a/substrate/frame/revive/src/precompiles.rs b/substrate/frame/revive/src/precompiles.rs index 4bdcb70bb5a28..6b5d55ba5fdd9 100644 --- a/substrate/frame/revive/src/precompiles.rs +++ b/substrate/frame/revive/src/precompiles.rs @@ -31,8 +31,10 @@ mod tests; pub use crate::{ exec::{ExecError, PrecompileExt as Ext, PrecompileWithInfoExt as ExtWithInfo}, - gas::{GasMeter, Token}, - storage::meter::Diff, + metering::{ + storage::Diff, + weight::{Token, WeightMeter}, + }, vm::RuntimeCosts, AddressMapper, }; diff --git a/substrate/frame/revive/src/precompiles/builtin/modexp.rs b/substrate/frame/revive/src/precompiles/builtin/modexp.rs index 6326980a335f3..6d18d29137f2c 100644 --- a/substrate/frame/revive/src/precompiles/builtin/modexp.rs +++ b/substrate/frame/revive/src/precompiles/builtin/modexp.rs @@ -366,7 +366,7 @@ mod tests { #[test] fn test_long_exp_gas_cost_matches_specs() { - use crate::{call_builder::CallSetup, gas::Token, tests::ExtBuilder}; + use crate::{call_builder::CallSetup, metering::weight::Token, tests::ExtBuilder}; let input = vec![ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -389,9 +389,9 @@ mod tests { let mut call_setup = CallSetup::::default(); let (mut ext, _) = call_setup.ext(); - let before = ext.gas_meter().gas_consumed(); + let before = ext.gas_meter().weight_consumed(); >::call(&>::MATCHER.base_address(), input, &mut ext).unwrap(); - let after = ext.gas_meter().gas_consumed(); + let after = ext.gas_meter().weight_consumed(); // 7104 * 20 gas used when ran in geth (x20) assert_eq!(after - before, Token::::weight(&RuntimeCosts::Modexp(7104 * 20))); diff --git a/substrate/frame/revive/src/precompiles/builtin/storage.rs b/substrate/frame/revive/src/precompiles/builtin/storage.rs index 682624ad93094..7a087e668b8b4 100644 --- a/substrate/frame/revive/src/precompiles/builtin/storage.rs +++ b/substrate/frame/revive/src/precompiles/builtin/storage.rs @@ -80,7 +80,7 @@ impl BuiltinPrecompile for Storage { }; let contained_key = outcome != WriteOutcome::New; let ret = (contained_key, outcome.old_len()); - env.gas_meter_mut().adjust_gas(charged, costs(outcome.old_len())); + env.gas_meter_mut().adjust_weight(charged, costs(outcome.old_len())); Ok(ret.abi_encode()) }, IStorageCalls::containsStorage(IStorage::containsStorageCall { @@ -107,7 +107,7 @@ impl BuiltinPrecompile for Storage { }; let value_len = outcome.unwrap_or(0); let ret = (outcome.is_some(), value_len); - env.gas_meter_mut().adjust_gas(charged, costs(value_len)); + env.gas_meter_mut().adjust_weight(charged, costs(value_len)); Ok(ret.abi_encode()) }, IStorageCalls::takeStorage(IStorage::takeStorageCall { flags, key, isFixedKey }) => { @@ -130,10 +130,10 @@ impl BuiltinPrecompile for Storage { }; if let crate::storage::WriteOutcome::Taken(value) = outcome { - env.gas_meter_mut().adjust_gas(charged, costs(value.len() as u32)); + env.gas_meter_mut().adjust_weight(charged, costs(value.len() as u32)); Ok(value.abi_encode()) } else { - env.gas_meter_mut().adjust_gas(charged, costs(0)); + env.gas_meter_mut().adjust_weight(charged, costs(0)); Ok(Vec::::new().abi_encode()) } }, diff --git a/substrate/frame/revive/src/precompiles/builtin/system.rs b/substrate/frame/revive/src/precompiles/builtin/system.rs index 9d3ab491b686f..ac8d6647d26c3 100644 --- a/substrate/frame/revive/src/precompiles/builtin/system.rs +++ b/substrate/frame/revive/src/precompiles/builtin/system.rs @@ -85,8 +85,8 @@ impl BuiltinPrecompile for System { }, ISystemCalls::weightLeft(ISystem::weightLeftCall {}) => { env.gas_meter_mut().charge(RuntimeCosts::WeightLeft)?; - let ref_time = env.gas_meter().gas_left().ref_time(); - let proof_size = env.gas_meter().gas_left().proof_size(); + let ref_time = env.gas_meter().weight_left().ref_time(); + let proof_size = env.gas_meter().weight_left().proof_size(); let res = (ref_time, proof_size); Ok(res.abi_encode()) }, diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 2180fd27c467c..1875d5dc4fc88 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -42,18 +42,18 @@ use sp_runtime::{ #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ContractResult { /// How much weight was consumed during execution. - pub gas_consumed: Weight, - /// How much weight is required as gas limit in order to execute this call. + pub weight_consumed: Weight, + /// How much weight is required as weight limit in order to execute this call. /// /// This value should be used to determine the weight limit for on-chain execution. /// /// # Note /// - /// This can only be different from [`Self::gas_consumed`] when weight pre charging + /// This can only be different from [`Self::weight_consumed`] when weight pre charging /// is used. Currently, only `seal_call_runtime` makes use of pre charging. /// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging - /// when a non-zero `gas_limit` argument is supplied. - pub gas_required: Weight, + /// when a non-zero `weight_limit` argument is supplied. + pub weight_required: Weight, /// How much balance was paid by the origin into the contract's deposit account in order to /// pay for storage. /// @@ -68,8 +68,8 @@ pub struct ContractResult { /// The result of the execution of a `eth_transact` call. #[derive(Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct EthTransactInfo { - /// The amount of gas that was necessary to execute the transaction. - pub gas_required: Weight, + /// The amount of weight that was necessary to execute the transaction. + pub weight_required: Weight, /// Storage deposit charged. pub storage_deposit: Balance, /// The weight and deposit equivalent in EVM Gas. diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index 0033142ec5754..a46f372f5c23c 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -17,8 +17,6 @@ //! This module contains routines for accessing and altering a contract related state. -pub mod meter; - use crate::{ address::AddressMapper, exec::{AccountIdOf, Key}, @@ -43,6 +41,8 @@ use sp_runtime::{ DispatchError, RuntimeDebug, }; +use crate::metering::storage::{Diff, NestedMeter}; + pub enum AccountIdOrAddress { /// An account that is a contract. AccountId(AccountIdOf), @@ -104,20 +104,20 @@ pub struct ContractInfo { /// The code associated with a given account. pub code_hash: sp_core::H256, /// How many bytes of storage are accumulated in this contract's child trie. - storage_bytes: u32, + pub storage_bytes: u32, /// How many items of storage are accumulated in this contract's child trie. - storage_items: u32, + pub storage_items: u32, /// This records to how much deposit the accumulated `storage_bytes` amount to. pub storage_byte_deposit: BalanceOf, /// This records to how much deposit the accumulated `storage_items` amount to. - storage_item_deposit: BalanceOf, + pub storage_item_deposit: BalanceOf, /// This records how much deposit is put down in order to pay for the contract itself. /// /// We need to store this information separately so it is not used when calculating any refunds /// since the base deposit can only ever be refunded on contract termination. - storage_base_deposit: BalanceOf, + pub storage_base_deposit: BalanceOf, /// The size of the immutable data of this contract. - immutable_data_len: u32, + pub immutable_data_len: u32, } impl From for AccountIdOrAddress { @@ -275,7 +275,7 @@ impl ContractInfo { &self, key: &Key, new_value: Option>, - storage_meter: Option<&mut meter::NestedMeter>, + storage_meter: Option<&mut NestedMeter>, take: bool, ) -> Result { log::trace!(target: crate::LOG_TARGET, "contract storage: writing value {:?} for key {:x?}", new_value, key); @@ -304,7 +304,7 @@ impl ContractInfo { &self, key: &[u8], new_value: Option<&[u8]>, - storage_meter: Option<&mut meter::NestedMeter>, + storage_meter: Option<&mut NestedMeter>, take: bool, ) -> Result { let child_trie_info = &self.child_trie_info(); @@ -316,7 +316,7 @@ impl ContractInfo { }; if let Some(storage_meter) = storage_meter { - let mut diff = meter::Diff::default(); + let mut diff = Diff::default(); let key_len = key.len() as u32; match (old_len, new_value.as_ref().map(|v| v.len() as u32)) { (Some(old_len), Some(new_len)) => diff --git a/substrate/frame/revive/src/test_utils.rs b/substrate/frame/revive/src/test_utils.rs index ec9843240303a..c658ef26f282c 100644 --- a/substrate/frame/revive/src/test_utils.rs +++ b/substrate/frame/revive/src/test_utils.rs @@ -72,7 +72,7 @@ pub const EVE: AccountId32 = AccountId32::new([5u8; 32]); pub const EVE_ADDR: H160 = H160(hex!("e21eecd6e51cbcda5b0c5207ae87e605839e70ef")); pub const EVE_FALLBACK: AccountId32 = ee_extend(EVE_ADDR.0); -pub const GAS_LIMIT: Weight = Weight::from_parts(500_000_000_000, 10 * 1024 * 1024); +pub const WEIGHT_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); pub fn deposit_limit() -> BalanceOf { 10_000_000u32.into() diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index 3d52f0662b55b..bafe978206a02 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{deposit_limit, GAS_LIMIT}; +use super::{deposit_limit, WEIGHT_LIMIT}; use crate::{ address::AddressMapper, evm::TransactionSigned, AccountIdOf, BalanceOf, Code, Config, ContractResult, ExecConfig, ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, @@ -79,7 +79,7 @@ builder!( instantiate_with_code( origin: OriginFor, value: BalanceOf, - gas_limit: Weight, + weight_limit: Weight, storage_deposit_limit: BalanceOf, code: Vec, data: Vec, @@ -91,7 +91,7 @@ builder!( Self { origin, value: 0u32.into(), - gas_limit: GAS_LIMIT, + weight_limit: WEIGHT_LIMIT, storage_deposit_limit: deposit_limit::(), code, data: vec![], @@ -104,7 +104,7 @@ builder!( instantiate( origin: OriginFor, value: BalanceOf, - gas_limit: Weight, + weight_limit: Weight, storage_deposit_limit: BalanceOf, code_hash: sp_core::H256, data: Vec, @@ -116,7 +116,7 @@ builder!( Self { origin, value: 0u32.into(), - gas_limit: GAS_LIMIT, + weight_limit: WEIGHT_LIMIT, storage_deposit_limit: deposit_limit::(), code_hash, data: vec![], @@ -129,7 +129,7 @@ builder!( bare_instantiate( origin: OriginFor, evm_value: U256, - gas_limit: Weight, + weight_limit: Weight, storage_deposit_limit: BalanceOf, code: Code, data: Vec, @@ -171,7 +171,7 @@ builder!( Self { origin, evm_value: Default::default(), - gas_limit: GAS_LIMIT, + weight_limit: WEIGHT_LIMIT, storage_deposit_limit: deposit_limit::(), code, data: vec![], @@ -186,7 +186,7 @@ builder!( origin: OriginFor, dest: H160, value: BalanceOf, - gas_limit: Weight, + weight_limit: Weight, storage_deposit_limit: BalanceOf, data: Vec, ) -> DispatchResultWithPostInfo; @@ -197,7 +197,7 @@ builder!( origin, dest, value: 0u32.into(), - gas_limit: GAS_LIMIT, + weight_limit: WEIGHT_LIMIT, storage_deposit_limit: deposit_limit::(), data: vec![], } @@ -209,7 +209,7 @@ builder!( origin: OriginFor, dest: H160, evm_value: U256, - gas_limit: Weight, + weight_limit: Weight, storage_deposit_limit: BalanceOf, data: Vec, exec_config: ExecConfig, @@ -232,7 +232,7 @@ builder!( origin, dest, evm_value: Default::default(), - gas_limit: GAS_LIMIT, + weight_limit: WEIGHT_LIMIT, storage_deposit_limit: deposit_limit::(), data: vec![], exec_config: ExecConfig::new_substrate_tx(), @@ -245,7 +245,7 @@ builder!( origin: OriginFor, dest: H160, value: U256, - gas_limit: Weight, + weight_limit: Weight, data: Vec, transaction_encoded: Vec, effective_gas_price: U256, @@ -258,7 +258,7 @@ builder!( origin, dest, value: 0u32.into(), - gas_limit: GAS_LIMIT, + weight_limit: WEIGHT_LIMIT, data: vec![], transaction_encoded: TransactionSigned::TransactionLegacySigned(Default::default()).signed_payload(), effective_gas_price: 0u32.into(), diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index f09afd070789a..fbcc34e88470a 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -419,14 +419,14 @@ impl TryFrom for Call { } impl SetWeightLimit for RuntimeCall { - fn set_weight_limit(&mut self, weight_limit: Weight) -> Weight { + fn set_weight_limit(&mut self, new_weight_limit: Weight) -> Weight { match self { Self::Contracts( - Call::eth_call { gas_limit, .. } | - Call::eth_instantiate_with_code { gas_limit, .. }, + Call::eth_call { weight_limit, .. } | + Call::eth_instantiate_with_code { weight_limit, .. }, ) => { - let old = *gas_limit; - *gas_limit = weight_limit; + let old = *weight_limit; + *weight_limit = new_weight_limit; old }, _ => Default::default(), diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 4cf773e7f75c6..0c8cda076a0bf 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -437,8 +437,8 @@ fn deposit_event_max_value_limit() { // Call contract with allowed event size. assert_ok!(builder::call(addr) - .gas_limit(Weight::from_parts(u64::MAX, u64::MAX)) - .data(limits::EVENT_BYTES.encode()) + .weight_limit(WEIGHT_LIMIT.set_ref_time(WEIGHT_LIMIT.ref_time() * 2)) // we are copying a huge buffer, + .data(limits::PAYLOAD_BYTES.encode()) .build()); // Call contract with too large a evene size @@ -468,7 +468,7 @@ fn run_out_of_fuel_engine() { // loops forever. assert_err_ignore_postinfo!( builder::call(addr) - .gas_limit(Weight::from_parts(10_000_000_000, u64::MAX)) + .weight_limit(Weight::from_parts(10_000_000_000, u64::MAX)) .build(), Error::::OutOfGas, ); @@ -500,19 +500,20 @@ fn gas_syncs_work() { let result = builder::bare_call(contract.addr).data(0u32.encode()).build(); assert_ok!(result.result); - let engine_consumed_noop = result.gas_consumed.ref_time(); + let engine_consumed_noop = result.weight_consumed.ref_time(); let result = builder::bare_call(contract.addr).data(1u32.encode()).build(); assert_ok!(result.result); - let gas_consumed_once = result.gas_consumed.ref_time(); + let weight_consumed_once = result.weight_consumed.ref_time(); let host_consumed_once = ::WeightInfo::seal_gas_price().ref_time(); - let engine_consumed_once = gas_consumed_once - host_consumed_once - engine_consumed_noop; + let engine_consumed_once = weight_consumed_once - host_consumed_once - engine_consumed_noop; let result = builder::bare_call(contract.addr).data(2u32.encode()).build(); assert_ok!(result.result); - let gas_consumed_twice = result.gas_consumed.ref_time(); + let weight_consumed_twice = result.weight_consumed.ref_time(); let host_consumed_twice = host_consumed_once * 2; - let engine_consumed_twice = gas_consumed_twice - host_consumed_twice - engine_consumed_noop; + let engine_consumed_twice = + weight_consumed_twice - host_consumed_twice - engine_consumed_noop; // Second contract just repeats first contract's instructions twice. // If runtime syncs gas with the engine properly, this should pass. @@ -605,8 +606,8 @@ fn storage_max_value_limit() { // Call contract with allowed storage value. assert_ok!(builder::call(addr) - .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer - .data(limits::STORAGE_BYTES.encode()) + .weight_limit(WEIGHT_LIMIT.set_ref_time(WEIGHT_LIMIT.ref_time() * 2)) // we are copying a huge buffer + .data(limits::PAYLOAD_BYTES.encode()) .build()); // Call contract with too large a storage value. @@ -1594,12 +1595,12 @@ fn gas_left_api_works() { let received = builder::bare_call(addr).build_and_unwrap_result(); assert_eq!(received.flags, ReturnFlags::empty()); let gas_left = U256::from_little_endian(received.data.as_ref()); - let gas_left_max = ::FeeInfo::weight_to_fee(&GAS_LIMIT) + 1_000_000; + let gas_left_max = ::FeeInfo::weight_to_fee(&WEIGHT_LIMIT) + 1_000_000; assert!(gas_left > 0u32.into()); assert!(gas_left < gas_left_max.into()); // Call the contract using the hold - let hold_initial = ::FeeInfo::weight_to_fee(&GAS_LIMIT); + let hold_initial = ::FeeInfo::weight_to_fee(&WEIGHT_LIMIT); ::FeeInfo::deposit_txfee(::Currency::issue(hold_initial)); let mut exec_config = ExecConfig::new_substrate_tx(); exec_config.collect_deposit_from_hold = Some((0u32.into(), Default::default())); @@ -1916,11 +1917,11 @@ fn gas_estimation_for_subcalls() { // Run the test for all of those weight limits for the subcall let weights = [ Weight::MAX, - GAS_LIMIT, - GAS_LIMIT * 2, - GAS_LIMIT / 5, - Weight::from_parts(u64::MAX, GAS_LIMIT.proof_size()), - Weight::from_parts(GAS_LIMIT.ref_time(), u64::MAX), + WEIGHT_LIMIT, + WEIGHT_LIMIT * 2, + WEIGHT_LIMIT / 5, + Weight::from_parts(u64::MAX, WEIGHT_LIMIT.proof_size()), + Weight::from_parts(WEIGHT_LIMIT.ref_time(), u64::MAX), ]; let (sub_addr, sub_input) = (addr_dummy.as_ref(), vec![]); @@ -1937,11 +1938,11 @@ fn gas_estimation_for_subcalls() { // Call in order to determine the gas that is required for this call let result_orig = builder::bare_call(addr_caller).data(input.clone()).build(); assert_ok!(&result_orig.result); - assert_eq!(result_orig.gas_required, result_orig.gas_consumed); + assert_eq!(result_orig.weight_required, result_orig.weight_consumed); // Make the same call using the estimated gas. Should succeed. let result = builder::bare_call(addr_caller) - .gas_limit(result_orig.gas_required) + .weight_limit(result_orig.weight_required) .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); @@ -1949,7 +1950,7 @@ fn gas_estimation_for_subcalls() { // Check that it fails with too little ref_time let result = builder::bare_call(addr_caller) - .gas_limit(result_orig.gas_required.sub_ref_time(1)) + .weight_limit(result_orig.weight_required.sub_ref_time(1)) .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); @@ -1957,7 +1958,7 @@ fn gas_estimation_for_subcalls() { // Check that it fails with too little proof_size let result = builder::bare_call(addr_caller) - .gas_limit(result_orig.gas_required.sub_proof_size(1)) + .weight_limit(result_orig.weight_required.sub_proof_size(1)) .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); @@ -1990,7 +1991,7 @@ fn call_runtime_reentrancy_guarded() { let call = RuntimeCall::Contracts(crate::Call::call { dest: addr_callee, value: 0, - gas_limit: GAS_LIMIT / 3, + weight_limit: WEIGHT_LIMIT / 3, storage_deposit_limit: deposit_limit::(), data: vec![], }) @@ -3335,7 +3336,7 @@ fn call_depth_is_enforced() { } #[test] -fn gas_consumed_is_linear_for_nested_calls() { +fn weight_consumed_is_linear_for_nested_calls() { let (code, _code_hash) = compile_module("recurse").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); @@ -3352,7 +3353,7 @@ fn gas_consumed_is_linear_for_nested_calls() { u32::from_le_bytes(result.result.unwrap().data.try_into().unwrap()), 0 ); - result.gas_consumed + result.weight_consumed }) .collect::>() .try_into() @@ -4139,7 +4140,7 @@ fn call_tracing_works() { /* let mut tracer = CallTracer::new(false, |w| w); let gas_used = trace(&mut tracer, || { - builder::bare_call(addr).data((3u32, addr_callee).encode()).build().gas_consumed + builder::bare_call(addr).data((3u32, addr_callee).encode()).build().weight_consumed }); let trace = tracer.collect_trace().unwrap(); assert_eq!(&trace.gas_used, &gas_used); @@ -5220,16 +5221,16 @@ fn consume_all_gas_works() { let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); assert_eq!( - builder::bare_instantiate(Code::Upload(code)).build().gas_consumed, - GAS_LIMIT, + builder::bare_instantiate(Code::Upload(code)).build().weight_consumed, + WEIGHT_LIMIT, "callvalue == 0 should consume all gas in deploy" ); assert_ne!( builder::bare_instantiate(Code::Existing(code_hash)) .evm_value(1.into()) .build() - .gas_consumed, - GAS_LIMIT, + .weight_consumed, + WEIGHT_LIMIT, "callvalue == 1 should not consume all gas in deploy" ); @@ -5238,13 +5239,13 @@ fn consume_all_gas_works() { .build_and_unwrap_contract(); assert_eq!( - builder::bare_call(addr).build().gas_consumed, - GAS_LIMIT, + builder::bare_call(addr).build().weight_consumed, + WEIGHT_LIMIT, "callvalue == 0 should consume all gas" ); assert_ne!( - builder::bare_call(addr).evm_value(1.into()).build().gas_consumed, - GAS_LIMIT, + builder::bare_call(addr).evm_value(1.into()).build().weight_consumed, + WEIGHT_LIMIT, "callvalue == 1 should not consume all gas" ); }); diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index a743024436c96..183765332ebc8 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -21,7 +21,7 @@ use core::iter; use crate::{ evm::{decode_revert_reason, fees::InfoT}, - test_utils::{builder::Contract, deposit_limit, ALICE, ALICE_ADDR, GAS_LIMIT}, + test_utils::{builder::Contract, deposit_limit, ALICE, ALICE_ADDR, WEIGHT_LIMIT}, tests::{builder, ExtBuilder, Test}, BalanceOf, Code, Config, DispatchError, Error, ExecConfig, }; @@ -723,8 +723,8 @@ fn subcall_effectively_limited_substrate_tx(caller_type: FixtureType, callee_typ ExtBuilder::default().build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let fees = - ::FeeInfo::tx_fee_from_weight(0, &GAS_LIMIT) + case.deposit_limit; + let fees = ::FeeInfo::tx_fee_from_weight(0, &WEIGHT_LIMIT) + + case.deposit_limit; ::FeeInfo::deposit_txfee(::Currency::issue(fees)); // Instantiate the callee contract, which can echo a value. diff --git a/substrate/frame/revive/src/tests/sol/control.rs b/substrate/frame/revive/src/tests/sol/control.rs index 042092df500cb..43e641be56af0 100644 --- a/substrate/frame/revive/src/tests/sol/control.rs +++ b/substrate/frame/revive/src/tests/sol/control.rs @@ -253,17 +253,18 @@ fn invalid_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let output = builder::bare_call(addr).gas_limit(expected_gas.into()).data(vec![]).build(); + let output = + builder::bare_call(addr).weight_limit(expected_gas.into()).data(vec![]).build(); let result = output.result; assert_err!(result, Error::::InvalidInstruction); assert_eq!( - output.gas_consumed.ref_time(), + output.weight_consumed.ref_time(), expected_gas, "Gas consumed does not match expected gas" ); assert_eq!( - output.gas_consumed.proof_size(), + output.weight_consumed.proof_size(), expected_gas, "Gas consumed does not match expected gas" ); diff --git a/substrate/frame/revive/src/tests/sol/system.rs b/substrate/frame/revive/src/tests/sol/system.rs index 078f55f64c592..7e3788dfcfe1f 100644 --- a/substrate/frame/revive/src/tests/sol/system.rs +++ b/substrate/frame/revive/src/tests/sol/system.rs @@ -19,7 +19,7 @@ use crate::{ evm::fees::InfoT, - test_utils::{builder::Contract, ALICE, ALICE_ADDR, GAS_LIMIT}, + test_utils::{builder::Contract, ALICE, ALICE_ADDR, WEIGHT_LIMIT}, tests::{builder, Contracts, ExtBuilder, Test}, Code, Config, ExecConfig, U256, }; @@ -213,7 +213,7 @@ fn gas_works(fixture_type: FixtureType) { builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); // enable txhold collection which we expect to be on when using the evm backend - let hold_initial = ::FeeInfo::weight_to_fee(&GAS_LIMIT); + let hold_initial = ::FeeInfo::weight_to_fee(&WEIGHT_LIMIT); ::FeeInfo::deposit_txfee(::Currency::issue(hold_initial)); let mut exec_config = ExecConfig::new_substrate_tx(); exec_config.collect_deposit_from_hold = Some((0u32.into(), Default::default())); diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 467d1768cb5af..2536ff47bf7b5 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -54,7 +54,7 @@ pub trait Tracing { _is_read_only: bool, _value: U256, _input: &[u8], - _gas: U256, + _weight: Weight, ) { } @@ -80,8 +80,8 @@ pub trait Tracing { fn log_event(&mut self, _event: H160, _topics: &[H256], _data: &[u8]) {} /// Called after a contract call is executed - fn exit_child_span(&mut self, _output: &ExecReturnValue, _gas_left: Weight) {} + fn exit_child_span(&mut self, _output: &ExecReturnValue, _weight_left: Weight) {} /// Called when a contract call terminates with an error - fn exit_child_span_with_error(&mut self, _error: DispatchError, _gas_left: Weight) {} + fn exit_child_span_with_error(&mut self, _error: DispatchError, _weight_left: Weight) {} } diff --git a/substrate/frame/revive/src/vm/evm/instructions/host.rs b/substrate/frame/revive/src/vm/evm/instructions/host.rs index 160d6c3d6dd2f..5ea07fb7180fe 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/host.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/host.rs @@ -155,7 +155,7 @@ fn store_helper<'ext, E: Ext>( interpreter .ext .gas_meter_mut() - .adjust_gas(charged_amount, adjust_cost(32, write_outcome.old_len())); + .adjust_weight(charged_amount, adjust_cost(32, write_outcome.old_len())); ControlFlow::Continue(()) } diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index 4f80e3b13cd2a..b14ab38b90c62 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -27,8 +27,10 @@ pub use runtime_costs::RuntimeCosts; use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, frame_support::{ensure, error::BadOrigin, traits::tokens::Restriction}, - gas::{GasMeter, Token}, - storage::meter::NestedMeter, + metering::{ + storage::NestedMeter, + weight::{Token, WeightMeter}, + }, weights::WeightInfo, AccountIdOf, BalanceOf, CodeInfoOf, CodeRemoved, Config, Error, ExecConfig, ExecError, HoldReason, Pallet, PristineCode, StorageDeposit, Weight, LOG_TARGET, @@ -323,9 +325,12 @@ impl CodeInfo { } impl Executable for ContractBlob { - fn from_storage(code_hash: H256, gas_meter: &mut GasMeter) -> Result { + fn from_storage( + code_hash: H256, + weight_meter: &mut WeightMeter, + ) -> Result { let code_info = >::get(code_hash).ok_or(Error::::CodeNotFound)?; - gas_meter.charge(CodeLoadToken::from_code_info(&code_info))?; + weight_meter.charge(CodeLoadToken::from_code_info(&code_info))?; let code = >::get(&code_hash).ok_or(Error::::CodeNotFound)?; Ok(Self { code, code_info, code_hash }) } diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index d43890191cfae..2e57b28435ba1 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -24,8 +24,8 @@ pub use env::SyscallDoc; use crate::{ exec::{CallResources, ExecError, ExecResult, Ext, Key}, - gas::ChargedAmount, limits, + metering::weight::ChargedAmount, precompiles::{All as AllPrecompiles, Precompiles}, primitives::ExecReturnValue, Code, Config, Error, Pallet, RuntimeCosts, LOG_TARGET, SENTINEL, @@ -356,7 +356,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { /// This is when a maximum a priori amount was charged and then should be partially /// refunded to match the actual amount. fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) { - self.ext.gas_meter_mut().adjust_gas(charged, actual_costs); + self.ext.gas_meter_mut().adjust_weight(charged, actual_costs); } /// Write the given buffer and its length to the designated locations in sandbox memory and @@ -835,7 +835,7 @@ impl<'a, E: Ext> PreparedCall<'a, E> { if let Some(exec_result) = self.runtime.handle_interrupt(interrupt, &self.module, &mut self.instance) { - break exec_result + break exec_result; } }; let _ = self.runtime.ext().gas_meter_mut().sync_from_executor(self.instance.gas())?; diff --git a/substrate/frame/revive/src/vm/runtime_costs.rs b/substrate/frame/revive/src/vm/runtime_costs.rs index d4398406845db..67e0ef5217792 100644 --- a/substrate/frame/revive/src/vm/runtime_costs.rs +++ b/substrate/frame/revive/src/vm/runtime_costs.rs @@ -15,9 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - gas::Token, limits, weightinfo_extension::OnFinalizeBlockParts, weights::WeightInfo, Config, -}; +use crate::{metering::weight::Token, weights::WeightInfo, Config}; use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; /// Current approximation of the gas/s consumption considering @@ -222,7 +220,7 @@ macro_rules! cost_args { } impl Token for RuntimeCosts { - fn influence_lowest_gas_limit(&self) -> bool { + fn influence_lowest_weight_limit(&self) -> bool { true } From faa5866fee874d71cd95aa8cc75f28e856d7d936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:39:41 -0300 Subject: [PATCH 03/69] Implement general resource metering --- substrate/frame/revive/proc-macro/src/lib.rs | 5 +- substrate/frame/revive/src/call_builder.rs | 6 +- substrate/frame/revive/src/evm/call.rs | 11 +- substrate/frame/revive/src/evm/runtime.rs | 2 + substrate/frame/revive/src/exec.rs | 351 ++++------- substrate/frame/revive/src/exec/tests.rs | 45 +- substrate/frame/revive/src/impl_fungibles.rs | 30 +- substrate/frame/revive/src/lib.rs | 189 +++--- substrate/frame/revive/src/metering/mod.rs | 591 ++++++++++++++++++ .../frame/revive/src/metering/storage.rs | 86 +-- substrate/frame/revive/src/metering/weight.rs | 117 +--- substrate/frame/revive/src/precompiles.rs | 5 +- .../revive/src/precompiles/builtin/blake2f.rs | 2 +- .../revive/src/precompiles/builtin/bn128.rs | 8 +- .../src/precompiles/builtin/ecrecover.rs | 2 +- .../src/precompiles/builtin/identity.rs | 2 +- .../revive/src/precompiles/builtin/modexp.rs | 4 +- .../src/precompiles/builtin/ripemd160.rs | 3 +- .../revive/src/precompiles/builtin/sha256.rs | 2 +- .../revive/src/precompiles/builtin/storage.rs | 6 +- .../revive/src/precompiles/builtin/system.rs | 22 +- substrate/frame/revive/src/primitives.rs | 26 +- substrate/frame/revive/src/storage.rs | 13 +- substrate/frame/revive/src/test_utils.rs | 1 + .../frame/revive/src/test_utils/builder.rs | 28 +- .../frame/revive/src/tests/precompiles.rs | 4 +- .../src/vm/evm/instructions/contract.rs | 16 +- .../revive/src/vm/evm/instructions/control.rs | 2 +- substrate/frame/revive/src/vm/mod.rs | 21 +- substrate/frame/revive/src/vm/pvm.rs | 11 +- substrate/frame/revive/src/vm/pvm/env.rs | 4 +- 31 files changed, 1020 insertions(+), 595 deletions(-) diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 98a885a46f9ba..c00625bfaa11e 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -443,8 +443,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { quote! { // Write gas from polkavm into pallet-revive before entering the host function. - let __gas_left_before__ = self - .ext + self.ext .gas_meter_mut() .sync_from_executor(memory.gas()) .map_err(TrapReason::from)?; @@ -462,7 +461,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { })(); // Write gas from pallet-revive into polkavm after leaving the host function. - let gas = self.ext.gas_meter_mut().sync_to_executor(__gas_left_before__).map_err(TrapReason::from)?; + let gas = self.ext.gas_meter_mut().sync_to_executor(); memory.set_gas(gas.into()); result } diff --git a/substrate/frame/revive/src/call_builder.rs b/substrate/frame/revive/src/call_builder.rs index d1fd82e6765c7..ee054e3757961 100644 --- a/substrate/frame/revive/src/call_builder.rs +++ b/substrate/frame/revive/src/call_builder.rs @@ -258,8 +258,10 @@ where let outcome = Contracts::::bare_instantiate( origin, U256::zero(), - Weight::MAX, - default_deposit_limit::(), + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: default_deposit_limit::(), + }, Code::Upload(module.code), data, salt, diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs index 2eeaf6701e1e2..60ae13cef4b99 100644 --- a/substrate/frame/revive/src/evm/call.rs +++ b/substrate/frame/revive/src/evm/call.rs @@ -25,7 +25,7 @@ use crate::{ use alloc::vec::Vec; use codec::DecodeLimit; use frame_support::MAX_EXTRINSIC_DEPTH; -use sp_core::Get; +use sp_core::{Get, U256}; use sp_runtime::{transaction_validity::InvalidTransaction, FixedPointNumber, SaturatedConversion}; /// Result of decoding an eth transaction into a dispatchable call. @@ -42,8 +42,8 @@ pub struct CallInfo { pub tx_fee: BalanceOf, /// The additional storage deposit to be deposited into the txhold. pub storage_deposit: BalanceOf, - /// The gas limit as supplied in the transaction. - pub gas: U256, + /// The ethereum gas limit of the transaction. + pub eth_gas_limit: U256, } /// Decode `tx` into a dispatchable call. @@ -121,6 +121,7 @@ where dest, value, weight_limit: Zero::zero(), + eth_gas_limit: gas, data, transaction_encoded, effective_gas_price, @@ -143,6 +144,7 @@ where let call = crate::Call::eth_instantiate_with_code:: { value, weight_limit: Zero::zero(), + eth_gas_limit: gas, code, data, transaction_encoded, @@ -196,5 +198,4 @@ where InvalidTransaction::Payment })?.saturated_into(); - Ok(CallInfo { call, weight_limit, encoded_len, tx_fee, storage_deposit, gas }) -} + Ok(CallInfo { call, weight_limit, encoded_len, tx_fee, storage_deposit, eth_gas_limit: gas }) diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index fd76991990eea..7d71e59f29ece 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -529,6 +529,7 @@ mod test { value, data, weight_limit, + eth_gas_limit, effective_gas_price, encoded_len, }) if dest == tx.to.unwrap() && @@ -565,6 +566,7 @@ mod test { code, data, weight_limit, + eth_gas_limit, effective_gas_price, encoded_len, }) if value == expected_value && diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 4a009ecf72250..8336462a60642 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -25,7 +25,8 @@ use crate::{ limits, metering::{ storage, - weight::{ChargedAmount, Token, WeightMeter}, + weight::{ChargedAmount, Token}, + FrameMeter, ResourceMeter, State, TransactionMeter, }, precompiles::{All as AllPrecompiles, Instance as PrecompileInstance, Precompiles}, primitives::{ExecConfig, ExecReturnValue, StorageDeposit}, @@ -65,8 +66,8 @@ use sp_core::{ }; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::{ - traits::{BadOrigin, Bounded, Saturating, TrailingZeroInput, Zero}, - DispatchError, FixedPointNumber, FixedU128, SaturatedConversion, + traits::{BadOrigin, Saturating, TrailingZeroInput, Zero}, + DispatchError, SaturatedConversion, }; #[cfg(test)] @@ -199,14 +200,26 @@ impl Origin { /// Argument passed by a contact to describe the amount of resources allocated to a cross contact /// call. -pub enum CallResources { +pub enum CallResources { + /// Resources are not limited + NoLimits, /// Resources encoded using their actual values. - Precise { weight: Weight, deposit_limit: U256 }, + Precise { weight: Weight, deposit_limit: BalanceOf }, /// Resources encoded as unified ethereum gas. - Ethereum(U256), + Ethereum(BalanceOf), } -impl Default for CallResources { +impl CallResources { + pub fn from_weight_and_deposit(weight: Weight, deposit_limit: U256) -> Self { + Self::Precise { weight, deposit_limit: deposit_limit.saturated_into::>() } + } + + pub fn from_ethereum_gas(gas: U256) -> Self { + Self::Ethereum(gas.saturated_into::>()) + } +} + +impl Default for CallResources { fn default() -> Self { Self::Precise { weight: Default::default(), deposit_limit: Default::default() } } @@ -219,7 +232,7 @@ pub trait Ext: PrecompileWithInfoExt { /// Returns the code size of the called contract. fn delegate_call( &mut self, - call_resources: &CallResources, + call_resources: &CallResources, address: H160, input_data: Vec, ) -> Result<(), ExecError>; @@ -268,8 +281,7 @@ pub trait PrecompileWithInfoExt: PrecompileExt { /// value transferred from the caller to the newly created account. fn instantiate( &mut self, - gas_limit: Weight, - deposit_limit: U256, + limits: &CallResources, code: Code, value: U256, input_data: Vec, @@ -283,7 +295,7 @@ pub trait PrecompileExt: sealing::Sealed { /// Charges the weight meter with the given weight. fn charge(&mut self, weight: Weight) -> Result { - self.gas_meter_mut().charge(RuntimeCosts::Precompile(weight)) + self.gas_meter_mut().charge_weight_token(RuntimeCosts::Precompile(weight)) } fn adjust_gas(&mut self, charged: ChargedAmount, actual_weight: Weight) { @@ -303,7 +315,7 @@ pub trait PrecompileExt: sealing::Sealed { /// Call (possibly transferring some amount of funds) into the specified account. fn call( &mut self, - call_resources: &CallResources, + call_resources: &CallResources, to: &H160, value: U256, input_data: Vec, @@ -409,10 +421,10 @@ pub trait PrecompileExt: sealing::Sealed { fn max_value_size(&self) -> u32; /// Get an immutable reference to the nested weight meter. - fn gas_meter(&self) -> &WeightMeter; + fn gas_meter(&self) -> &FrameMeter; /// Get a mutable reference to the nested weight meter. - fn gas_meter_mut(&mut self) -> &mut WeightMeter; + fn gas_meter_mut(&mut self) -> &mut FrameMeter; /// Recovers ECDSA compressed public key based on signature and message hash. fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; @@ -523,9 +535,9 @@ pub trait Executable: Sized { /// /// # Note /// Charges size base load weight from the weight meter. - fn from_storage( + fn from_storage( code_hash: H256, - weight_meter: &mut WeightMeter, + meter: &mut ResourceMeter, ) -> Result; /// Load the executable from EVM bytecode @@ -572,10 +584,8 @@ pub struct Stack<'a, T: Config, E> { /// than a plain account when being called through one of the contract RPCs where the /// client can freely choose the origin. This usually makes no sense but is still possible. origin: Origin, - /// The weight meter where costs are charged to. - weight_meter: &'a mut WeightMeter, - /// The storage meter makes sure that the storage deposit limit is obeyed. - storage_meter: &'a mut storage::Meter, + /// The resource meter that tracks all resource usage before the first frame starts. + transaction_meter: &'a mut TransactionMeter, /// The timestamp at the point of call stack instantiation. timestamp: MomentOf, /// The block number at the time of call stack instantiation. @@ -611,10 +621,8 @@ struct Frame { value_transferred: U256, /// Determines whether this is a call or instantiate frame. entry_point: ExportedFunction, - /// The weight meter capped to the supplied weight limit. - nested_weight: WeightMeter, - /// The storage meter for the individual call. - nested_storage: storage::NestedMeter, + /// The resource meter that tracks all resource usage of this frame. + frame_meter: FrameMeter, /// If `false` the contract enabled its defense against reentrance attacks. allows_reentry: bool, /// If `true` subsequent calls cannot modify storage. @@ -819,8 +827,7 @@ where pub fn run_call( origin: Origin, dest: H160, - weight_meter: &mut WeightMeter, - storage_meter: &mut storage::Meter, + transaction_meter: &'a mut TransactionMeter, value: U256, input_data: Vec, exec_config: &ExecConfig, @@ -829,8 +836,7 @@ where if let Some((mut stack, executable)) = Stack::<'_, T, E>::new( FrameArgs::Call { dest: dest.clone(), cached_info: None, delegated_call: None }, origin.clone(), - weight_meter, - storage_meter, + transaction_meter, value, exec_config, &input_data, @@ -849,21 +855,14 @@ where ); }); - let result = if let Some(mock_answer) = - exec_config.mock_handler.as_ref().and_then(|handler| { - handler.mock_call(T::AddressMapper::to_address(&dest), &input_data, value) - }) { - Ok(mock_answer) - } else { - Self::transfer_from_origin( - &origin, - &origin, - &dest, - value, - storage_meter, - exec_config, - ) - }; + let result = Self::transfer_from_origin( + &origin, + &origin, + &dest, + value, + transaction_meter, + exec_config, + ); if_tracing(|t| match result { Ok(ref output) => t.exit_child_span(&output, Weight::zero()), @@ -884,8 +883,7 @@ where pub fn run_instantiate( origin: T::AccountId, executable: E, - weight_meter: &mut WeightMeter, - storage_meter: &mut storage::Meter, + transaction_meter: &'a mut TransactionMeter, value: U256, input_data: Vec, salt: Option<&[u8; 32]>, @@ -900,8 +898,7 @@ where input_data: input_data.as_ref(), }, Origin::from_account_id(origin), - weight_meter, - storage_meter, + transaction_meter, value, exec_config, &input_data, @@ -954,33 +951,21 @@ where fn new( args: FrameArgs, origin: Origin, - weight_meter: &'a mut WeightMeter, - storage_meter: &'a mut storage::Meter, + transaction_meter: &'a mut TransactionMeter, value: U256, exec_config: &'a ExecConfig, input_data: &Vec, ) -> Result)>, ExecError> { origin.ensure_mapped()?; - let Some((first_frame, executable)) = Self::new_frame( - args, - value, - weight_meter, - Weight::max_value(), - storage_meter, - BalanceOf::::max_value(), - false, - true, - input_data, - exec_config, - )? + let Some((first_frame, executable)) = + Self::new_frame(args, value, transaction_meter, &CallResources::NoLimits, false, true)? else { return Ok(None); }; let stack = Self { origin, - weight_meter, - storage_meter, + transaction_meter, timestamp: T::Time::now(), block_number: >::block_number(), first_frame, @@ -999,13 +984,11 @@ where /// /// This does not take `self` because when constructing the first frame `self` is /// not initialized, yet. - fn new_frame( + fn new_frame( frame_args: FrameArgs, value_transferred: U256, - weight_meter: &mut WeightMeter, - weight_limit: Weight, - storage_meter: &mut storage::GenericMeter, - deposit_limit: BalanceOf, + meter: &mut ResourceMeter, + call_resources: &CallResources, read_only: bool, origin_is_caller: bool, input_data: &[u8], @@ -1057,7 +1040,7 @@ where else { return Ok(None); }; - let executable = E::from_storage(info.code_hash, weight_meter)?; + let executable = E::from_storage(info.code_hash, meter)?; ExecutableOrPrecompile::Executable(executable) } } else { @@ -1072,7 +1055,7 @@ where .as_contract() .expect("When not a precompile the contract was loaded above; qed") .code_hash, - weight_meter, + meter, )?; ExecutableOrPrecompile::Executable(executable) } @@ -1119,8 +1102,7 @@ where contract_info, account_id, entry_point, - nested_weight: weight_meter.nested(weight_limit), - nested_storage: storage_meter.nested(deposit_limit), + frame_meter: meter.new_nested(call_resources)?, allows_reentry: true, read_only, last_frame_output: Default::default(), @@ -1134,8 +1116,7 @@ where &mut self, frame_args: FrameArgs, value_transferred: U256, - weight_limit: Weight, - deposit_limit: BalanceOf, + call_resources: &CallResources, read_only: bool, input_data: &[u8], ) -> Result>, ExecError> { @@ -1158,20 +1139,10 @@ where } let frame = top_frame_mut!(self); - let nested_weight = &mut frame.nested_weight; - let nested_storage = &mut frame.nested_storage; - if let Some((frame, executable)) = Self::new_frame( - frame_args, - value_transferred, - nested_weight, - weight_limit, - nested_storage, - deposit_limit, - read_only, - false, - input_data, - self.exec_config, - )? { + let meter = &mut frame.frame_meter; + if let Some((frame, executable)) = + Self::new_frame(frame_args, value_transferred, meter, call_resources, read_only, false)? + { self.frames.try_push(frame).map_err(|_| Error::::MaxCallDepthReached)?; Ok(Some(executable)) } else { @@ -1199,7 +1170,7 @@ where frame.read_only, frame.value_transferred, &input_data, - frame.nested_weight.weight_left(), + frame.frame_meter.weight_left().unwrap_or_default(), ); }); let mock_answer = self.exec_config.mock_handler.as_ref().and_then(|handler| { @@ -1249,7 +1220,7 @@ where let origin = &self.origin.account_id()?; let ed = >::min_balance(); - frame.nested_storage.record_charge(&StorageDeposit::Charge(ed))?; + frame.frame_meter.charge_deposit(&StorageDeposit::Charge(ed))?; >::charge_deposit(None, origin, account_id, ed, self.exec_config) .map_err(|_| >::StorageDepositNotEnoughFunds)?; @@ -1287,7 +1258,7 @@ where &caller, account_id, frame.value_transferred, - &mut frame.nested_storage, + &mut frame.frame_meter, self.exec_config, )?; } @@ -1358,7 +1329,7 @@ where }; let mut module = crate::ContractBlob::::from_evm_runtime_code(data, origin)?; - module.store_code(&self.exec_config, Some(&mut frame.nested_storage))?; + module.store_code(&self.exec_config, &mut frame.frame_meter)?; code_deposit = module.code_info().deposit(); let contract_info = frame.contract_info(); @@ -1367,9 +1338,10 @@ where } let deposit = frame.contract_info().update_base_deposit(code_deposit); - frame - .nested_storage - .charge_deposit(frame.account_id.clone(), StorageDeposit::Charge(deposit)); + frame.frame_meter.charge_contract_deposit_and_transfer( + frame.account_id.clone(), + StorageDeposit::Charge(deposit), + ); frame } else { self.top_frame_mut() @@ -1380,8 +1352,8 @@ where // the limit is manually enforced here. let contract = frame.contract_info.as_contract(); frame - .nested_storage - .enforce_limit(contract) + .frame_meter + .finalize(contract) .map_err(|e| ExecError { error: e, origin: ErrorOrigin::Callee })?; Ok(output) @@ -1411,7 +1383,7 @@ where // `with_transactional` executed successfully, and we have the expected output. Ok((success, output)) => { if_tracing(|tracer| { - let weight_consumed = top_frame!(self).nested_weight.weight_consumed(); + let weight_consumed = top_frame!(self).frame_meter.weight_consumed(); match &output { Ok(output) => tracer.exit_child_span(&output, weight_consumed), Err(e) => @@ -1425,7 +1397,7 @@ where // has changed. Err(error) => { if_tracing(|tracer| { - let weight_consumed = top_frame!(self).nested_weight.weight_consumed(); + let weight_consumed = top_frame!(self).frame_meter.weight_consumed(); tracer.exit_child_span_with_error(error.into(), weight_consumed); }); @@ -1463,10 +1435,9 @@ where let account_id = &frame.account_id; let prev = top_frame_mut!(self); - prev.nested_weight.absorb_nested(frame.nested_weight); - // Only weight counter changes are persisted in case of a failure. if !persist { + prev.frame_meter.absorb_weight_meter_only(frame.frame_meter); return; } @@ -1476,7 +1447,8 @@ where // it was invalidated. frame.contract_info.load(account_id); let mut contract = frame.contract_info.into_contract(); - prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut()); + prev.frame_meter + .absorb_all_meters(frame.frame_meter, account_id, contract.as_mut()); // In case the contract wasn't terminated we need to persist changes made to it. if let Some(contract) = contract { @@ -1504,13 +1476,15 @@ where } } else { // TODO: iterate contracts_to_be_destroyed and destroy each contract - self.weight_meter.absorb_nested(mem::take(&mut self.first_frame.nested_weight)); if !persist { + self.transaction_meter + .absorb_weight_meter_only(mem::take(&mut self.first_frame.frame_meter)); return; } + let mut contract = self.first_frame.contract_info.as_contract(); - self.storage_meter.absorb( - mem::take(&mut self.first_frame.nested_storage), + self.transaction_meter.absorb_all_meters( + mem::take(&mut self.first_frame.frame_meter), &self.first_frame.account_id, contract.as_deref_mut(), ); @@ -1541,12 +1515,12 @@ where /// not exist, the transfer does fail and nothing will be sent to `to` if either `origin` can /// not provide the ED or transferring `value` from `from` to `to` fails. /// Note: This will also fail if `origin` is root. - fn transfer( + fn transfer( origin: &Origin, from: &T::AccountId, to: &T::AccountId, value: U256, - storage_meter: &mut storage::GenericMeter, + meter: &mut ResourceMeter, exec_config: &ExecConfig, ) -> DispatchResult { fn transfer_with_dust( @@ -1636,8 +1610,8 @@ where let origin = origin.account_id()?; let ed = ::Currency::minimum_balance(); with_transaction(|| -> TransactionOutcome { - match storage_meter - .record_charge(&StorageDeposit::Charge(ed)) + match meter + .charge_deposit(&StorageDeposit::Charge(ed)) .and_then(|_| { >::charge_deposit(None, origin, to, ed, exec_config) .map_err(|_| Error::::StorageDepositNotEnoughFunds)?; @@ -1652,12 +1626,12 @@ where } /// Same as `transfer` but `from` is an `Origin`. - fn transfer_from_origin( + fn transfer_from_origin( origin: &Origin, from: &Origin, to: &T::AccountId, value: U256, - storage_meter: &mut storage::GenericMeter, + meter: &mut ResourceMeter, exec_config: &ExecConfig, ) -> ExecResult { // If the from address is root there is no account to transfer from, and therefore we can't @@ -1667,7 +1641,7 @@ where Origin::Root if value.is_zero() => return Ok(Default::default()), Origin::Root => return Err(DispatchError::RootNotAllowed.into()), }; - Self::transfer(origin, from, to, value, storage_meter, exec_config) + Self::transfer(origin, from, to, value, meter, exec_config) .map(|_| Default::default()) .map_err(Into::into) } @@ -1773,45 +1747,6 @@ where } true } - - /// Calc the limits for a cross contract call. - fn calc_limits(&self, call_resources: &CallResources) -> (Weight, BalanceOf) { - match call_resources { - CallResources::Precise { weight, deposit_limit } => - (*weight, (*deposit_limit).saturated_into()), - CallResources::Ethereum(gas_limit) => { - // the resources of the subcall are relative to the available resources - let available: BalanceOf = self.gas_left().saturated_into(); - let gas_limit = (*gas_limit).saturated_into::>().min(available); - let ratio = FixedU128::from_rational( - gas_limit.saturated_into(), - available.max(1u32.into()).saturated_into(), - ); - - // weight limit is expected to be set to we can just multiply directly - let weight_limit = { - let weight_left = self.top_frame().nested_weight.weight_left(); - Weight::from_parts( - ratio.saturating_mul_int(weight_left.ref_time()), - ratio.saturating_mul_int(weight_left.proof_size()), - ) - }; - - // the gas_limit is in unadjusted fee - let deposit_limit = { - let weight_fee = T::FeeInfo::weight_to_fee(&weight_limit); - gas_limit.saturating_sub(weight_fee).min( - ratio.saturating_mul_int( - T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(self.top_frame().nested_storage.available()), - ), - ) - }; - - (weight_limit, T::FeeInfo::next_fee_multiplier().saturating_mul_int(deposit_limit)) - }, - } - } } impl<'a, T, E> Ext for Stack<'a, T, E> @@ -1821,7 +1756,7 @@ where { fn delegate_call( &mut self, - call_resources: &CallResources, + call_resources: &CallResources, address: H160, input_data: Vec, ) -> Result<(), ExecError> { @@ -1829,8 +1764,6 @@ where // This is for example the case for unknown code hashes or creating the frame fails. *self.last_frame_output_mut() = Default::default(); - let (weight, deposit_limit) = self.calc_limits(call_resources); - let top_frame = self.top_frame_mut(); let contract_info = top_frame.contract_info().clone(); let account_id = top_frame.account_id.clone(); @@ -1845,8 +1778,7 @@ where }), }, value, - weight, - deposit_limit, + call_resources, self.is_read_only(), &input_data, )? { @@ -1857,20 +1789,17 @@ where } } - fn terminate_if_same_tx(&mut self, beneficiary: &H160) -> Result { - let (account_id, contract_address, contract_info) = { - let frame = self.top_frame_mut(); - if frame.entry_point == ExportedFunction::Constructor { - return Err(Error::::TerminatedInConstructor.into()); - } - ( - frame.account_id.clone(), - T::AddressMapper::to_address(&frame.account_id), - frame.contract_info().clone(), - ) - }; - self.contracts_to_be_destroyed - .insert(contract_address, (contract_info.clone(), *beneficiary)); + fn terminate(&mut self, beneficiary: &H160) -> Result { + if self.is_recursive() { + return Err(Error::::TerminatedWhileReentrant.into()); + } + let frame = self.top_frame_mut(); + if frame.entry_point == ExportedFunction::Constructor { + return Err(Error::::TerminatedInConstructor.into()); + } + let info = frame.terminate(); + let beneficiary_account = T::AddressMapper::to_account_id(beneficiary); + frame.frame_meter.terminate(&info, beneficiary_account); if self.contracts_created.contains(&account_id) { Ok(CodeRemoved::Yes) @@ -1913,7 +1842,9 @@ where let deposit = StorageDeposit::Charge(new_base_deposit) .saturating_sub(&StorageDeposit::Charge(old_base_deposit)); - frame.nested_storage.charge_deposit(frame.account_id.clone(), deposit); + frame + .frame_meter + .charge_contract_deposit_and_transfer(frame.account_id.clone(), deposit); >::increment_refcount(hash)?; let removed = >::decrement_refcount(prev_hash)?; @@ -1957,8 +1888,7 @@ where { fn instantiate( &mut self, - weight_limit: Weight, - deposit_limit: U256, + call_resources: &CallResources, code: Code, value: U256, input_data: Vec, @@ -1987,8 +1917,7 @@ where input_data: input_data.as_ref(), }, value, - weight_limit, - deposit_limit.saturated_into::>(), + call_resources, self.is_read_only(), &input_data, )? @@ -2013,7 +1942,7 @@ where fn call( &mut self, - call_resources: &CallResources, + call_resources: &CallResources, dest_addr: &H160, value: U256, input_data: Vec, @@ -2029,8 +1958,6 @@ where // This is for example the case for balance transfers or when creating the frame fails. *self.last_frame_output_mut() = Default::default(); - let (weight, deposit_limit) = self.calc_limits(call_resources); - let try_call = || { // Enable read-only access if requested; cannot disable it if already set. let is_read_only = read_only || self.is_read_only(); @@ -2060,8 +1987,7 @@ where if let Some(executable) = self.push_frame( FrameArgs::Call { dest: dest.clone(), cached_info, delegated_call: None }, value, - weight, - deposit_limit, + call_resources, is_read_only, &input_data, )? { @@ -2096,7 +2022,7 @@ where &Origin::from_account_id(account_id), &dest, value, - &mut frame.nested_storage, + &mut frame.frame_meter, self.exec_config, ) }; @@ -2283,12 +2209,12 @@ where limits::PAYLOAD_BYTES } - fn gas_meter(&self) -> &WeightMeter { - &self.top_frame().nested_weight + fn gas_meter(&self) -> &FrameMeter { + &self.top_frame().frame_meter } - fn gas_meter_mut(&mut self) -> &mut WeightMeter { - &mut self.top_frame_mut().nested_weight + fn gas_meter_mut(&mut self) -> &mut FrameMeter { + &mut self.top_frame_mut().frame_meter } fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()> { @@ -2378,56 +2304,7 @@ where fn gas_left(&self) -> u64 { let frame = self.top_frame(); - // when using the txhold we know the overall available fee by looking at the tx credit - // we need to use that to limit gas_left because in that case no storage deposit limit is - // set - let max_by_credit_hold = if let Some((encoded_len, base_weight)) = - self.exec_config.collect_deposit_from_hold - { - // we work backwards: the gas_left is the overall fee minus what was already consumed - let (deposit_consumed, weight_consumed) = - self.frames.iter().chain(core::iter::once(&self.first_frame)).fold( - (StorageDeposit::default(), Weight::default()), - |(deposit, weight), frame| { - ( - deposit.saturating_add(&frame.nested_storage.consumed()), - weight.saturating_add(frame.nested_weight.weight_consumed()), - ) - }, - ); - let weight_fee_consumed = T::FeeInfo::tx_fee_from_weight( - encoded_len, - &weight_consumed.saturating_add(base_weight), - ); - let available = T::FeeInfo::remaining_txfee().saturating_sub(weight_fee_consumed); - deposit_consumed.available(&available) - } else { - BalanceOf::::max_value() - }; - - // this is the actual limit derived from what is set in the meter - let max_by_meter = { - use frame_support::traits::tokens::{Fortitude, Preservation}; - let weight_fee_available = - T::FeeInfo::weight_to_fee(&frame.nested_weight.weight_left()); - - // in the dry run no deposit limit is set. - // this means its only limited by the origins free balance - let deposit_available = self - .origin - .account_id() - .map(|acc| { - T::Currency::reducible_balance(acc, Preservation::Preserve, Fortitude::Polite) - }) - .unwrap_or(BalanceOf::::max_value()) - .min(frame.nested_storage.available()); - - weight_fee_available.saturating_add(deposit_available) - }; - - T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(max_by_credit_hold.min(max_by_meter)) - .saturated_into() + frame.frame_meter.eth_gas_left().unwrap_or_default().saturated_into::() } fn get_storage(&mut self, key: &Key) -> Option> { @@ -2451,14 +2328,14 @@ where frame.contract_info.get(&frame.account_id).write( key.into(), value, - Some(&mut frame.nested_storage), + Some(&mut frame.frame_meter), take_old, ) } fn charge_storage(&mut self, diff: &storage::Diff) { assert!(self.has_contract_info()); - self.top_frame_mut().nested_storage.charge(diff) + self.top_frame_mut().frame_meter.record_contract_storage_changes(diff) } } diff --git a/substrate/frame/revive/src/exec/tests.rs b/substrate/frame/revive/src/exec/tests.rs index be7e241a1d001..3c899cdbc3028 100644 --- a/substrate/frame/revive/src/exec/tests.rs +++ b/substrate/frame/revive/src/exec/tests.rs @@ -139,9 +139,9 @@ impl MockLoader { } impl Executable for MockExecutable { - fn from_storage( + fn from_storage( code_hash: H256, - _weight_meter: &mut WeightMeter, + _meter: &mut ResourceMeter, ) -> Result { Loader::mutate(|loader| { loader.map.get(&code_hash).cloned().ok_or(Error::::CodeNotFound.into()) @@ -1175,8 +1175,7 @@ fn instantiation_from_contract() { let (address, output) = ctx .ext .instantiate( - Weight::MAX, - U256::MAX, + CallResources::NoLimits, Code::Existing(dummy_ch), Pallet::::convert_native_to_evm(min_balance), vec![], @@ -1242,8 +1241,7 @@ fn instantiation_traps() { assert_matches!( ctx.ext.instantiate( - Weight::zero(), - U256::zero(), + Default::default(), Code::Existing(dummy_ch), value, vec![], @@ -1602,8 +1600,7 @@ fn nonce() { let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { ctx.ext .instantiate( - Weight::MAX, - U256::MAX, + CallResources::NoLimits, Code::Existing(fail_code), ctx.ext.minimum_balance() * 100, vec![], @@ -1619,8 +1616,7 @@ fn nonce() { let addr = ctx .ext .instantiate( - Weight::MAX, - U256::MAX, + CallResources::NoLimits, Code::Existing(success_code), ctx.ext.minimum_balance(), vec![], @@ -2334,7 +2330,7 @@ fn last_frame_output_works_on_instantiate() { // Successful instantiation should set the output let address = ctx .ext - .instantiate(Weight::MAX, U256::MAX, Code::Existing(ok_ch), value, vec![], None) + .instantiate(CallResources::NoLimits, Code::Existing(ok_ch), value, vec![], None) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -2344,7 +2340,7 @@ fn last_frame_output_works_on_instantiate() { // Balance transfers should reset the output ctx.ext .call( - &CallResources::Precise { weight: Weight::MAX, deposit_limit: U256::MAX }, + &CallResources::from_weight_and_deposit(Weight::MAX, U256::MAX), &address, Pallet::::convert_native_to_evm(1), vec![], @@ -2356,14 +2352,7 @@ fn last_frame_output_works_on_instantiate() { // Reverted instantiation should set the output ctx.ext - .instantiate( - Weight::zero(), - U256::zero(), - Code::Existing(revert_ch), - value, - vec![], - None, - ) + .instantiate(Default::default(), Code::Existing(revert_ch), value, vec![], None) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -2372,14 +2361,7 @@ fn last_frame_output_works_on_instantiate() { // Trapped instantiation should clear the output ctx.ext - .instantiate( - Weight::zero(), - U256::zero(), - Code::Existing(trap_ch), - value, - vec![], - None, - ) + .instantiate(Default::default(), Code::Existing(trap_ch), value, vec![], None) .unwrap_err(); assert_eq!( ctx.ext.last_frame_output(), @@ -2508,8 +2490,7 @@ fn last_frame_output_is_always_reset() { *ctx.ext.last_frame_output_mut() = output_revert(); assert_eq!( ctx.ext.instantiate( - Weight::zero(), - U256::zero(), + Default::default(), Code::Existing(invalid_code_hash), U256::zero(), vec![], @@ -2559,7 +2540,7 @@ fn immutable_data_access_checks_work() { // Constructors can not access the immutable data ctx.ext - .instantiate(Weight::MAX, U256::MAX, Code::Existing(dummy_ch), value, vec![], None) + .instantiate(CallResources::NoLimits, Code::Existing(dummy_ch), value, vec![], None) .unwrap(); exec_success() @@ -2719,7 +2700,7 @@ fn immutable_data_set_errors_with_empty_data() { let value = Pallet::::convert_native_to_evm(min_balance); ctx.ext - .instantiate(Weight::MAX, U256::MAX, Code::Existing(dummy_ch), value, vec![], None) + .instantiate(CallResources::NoLimits, Code::Existing(dummy_ch), value, vec![], None) .unwrap(); exec_success() diff --git a/substrate/frame/revive/src/impl_fungibles.rs b/substrate/frame/revive/src/impl_fungibles.rs index 47e66e124eaeb..d4ef744c22a46 100644 --- a/substrate/frame/revive/src/impl_fungibles.rs +++ b/substrate/frame/revive/src/impl_fungibles.rs @@ -24,7 +24,7 @@ #![cfg(any(feature = "std", feature = "runtime-benchmarks", test))] -use crate::OriginFor; +use crate::{metering::TransactionLimits, OriginFor}; use alloy_core::{ primitives::{Address, U256 as EU256}, sol_types::*, @@ -69,8 +69,11 @@ impl fungibles::Inspect<::AccountId> for P OriginFor::::signed(Self::checking_account()), asset_id, U256::zero(), - WEIGHT_LIMIT, - <::Currency as fungible::Inspect<_>>::total_issuance(), + TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: + <::Currency as fungible::Inspect<_>>::total_issuance(), + }, data, ExecConfig::new_substrate_tx(), ); @@ -104,8 +107,11 @@ impl fungibles::Inspect<::AccountId> for P OriginFor::::signed(account_id.clone()), asset_id, U256::zero(), - WEIGHT_LIMIT, - <::Currency as fungible::Inspect<_>>::total_issuance(), + TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: + <::Currency as fungible::Inspect<_>>::total_issuance(), + }, data, ExecConfig::new_substrate_tx(), ); @@ -173,8 +179,11 @@ impl fungibles::Mutate<::AccountId> for Pa OriginFor::::signed(who.clone()), asset_id, U256::zero(), - WEIGHT_LIMIT, - <::Currency as fungible::Inspect<_>>::total_issuance(), + TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: + <::Currency as fungible::Inspect<_>>::total_issuance(), + }, data, ExecConfig::new_substrate_tx(), ); @@ -209,8 +218,11 @@ impl fungibles::Mutate<::AccountId> for Pa OriginFor::::signed(Self::checking_account()), asset_id, U256::zero(), - WEIGHT_LIMIT, - <::Currency as fungible::Inspect<_>>::total_issuance(), + TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: + <::Currency as fungible::Inspect<_>>::total_issuance(), + }, data, ExecConfig::new_substrate_tx(), ); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 5cfc419b3f411..ba30748b06bcb 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -56,8 +56,8 @@ use crate::{ }, exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, metering::{ - storage::Meter as StorageMeter, - weight::{Token as WeightToken, WeightMeter as ContractWeightMeter}, + weight::Token as WeightToken, EthTxInfo, ResourceMeter, State, TransactionLimits, + TransactionMeter, }, storage::{AccountType, DeletionQueueManager}, tracing::if_tracing, @@ -93,7 +93,7 @@ use sp_runtime::{ BadOrigin, Bounded, Convert, Dispatchable, Saturating, UniqueSaturatedFrom, UniqueSaturatedInto, Zero, }, - AccountId32, DispatchError, FixedPointNumber, FixedU128, + AccountId32, DispatchError, FixedPointNumber, FixedU128, SaturatedConversion, }; pub use crate::{ @@ -140,6 +140,8 @@ const LOG_TARGET: &str = "runtime::revive"; #[frame_support::pallet] pub mod pallet { + use crate::metering::EthTxInfo; + use super::*; use frame_support::{pallet_prelude::*, traits::FindAuthor}; use frame_system::pallet_prelude::*; @@ -1123,8 +1125,10 @@ pub mod pallet { origin, dest, Pallet::::convert_native_to_evm(value), - weight_limit, - storage_deposit_limit, + TransactionLimits::WeightAndDeposit { + weight_limit, + deposit_limit: storage_deposit_limit, + }, data, ExecConfig::new_substrate_tx(), ); @@ -1164,8 +1168,10 @@ pub mod pallet { let mut output = Self::bare_instantiate( origin, Pallet::::convert_native_to_evm(value), - weight_limit, - storage_deposit_limit, + TransactionLimits::WeightAndDeposit { + weight_limit, + deposit_limit: storage_deposit_limit, + }, Code::Existing(code_hash), data, salt, @@ -1230,8 +1236,10 @@ pub mod pallet { let mut output = Self::bare_instantiate( origin, Pallet::::convert_native_to_evm(value), - weight_limit, - storage_deposit_limit, + TransactionLimits::WeightAndDeposit { + weight_limit, + deposit_limit: storage_deposit_limit, + }, Code::Upload(code), data, salt, @@ -1279,6 +1287,7 @@ pub mod pallet { origin: OriginFor, value: U256, weight_limit: Weight, + eth_gas_limit: U256, code: Vec, data: Vec, transaction_encoded: Vec, @@ -1290,6 +1299,7 @@ pub mod pallet { let mut call = Call::::eth_instantiate_with_code { value, weight_limit, + eth_gas_limit, code: code.clone(), data: data.clone(), transaction_encoded: transaction_encoded.clone(), @@ -1300,15 +1310,19 @@ pub mod pallet { let info = T::FeeInfo::dispatch_info(&call); let base_info = T::FeeInfo::base_dispatch_info(&mut call); drop(call); + + let extra_weight = base_info.total_weight(); let mut output = Self::bare_instantiate( origin, value, - weight_limit, - BalanceOf::::max_value(), + TransactionLimits::EthereumGas { + eth_gas_limit: eth_gas_limit.saturated_into(), + eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), + }, Code::Upload(code), data, None, - ExecConfig::new_eth_tx(effective_gas_price, encoded_len, base_info.total_weight()), + ExecConfig::new_eth_tx(effective_gas_price, encoded_len, extra_weight), ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -1332,6 +1346,7 @@ pub mod pallet { dest: H160, value: U256, weight_limit: Weight, + eth_gas_limit: U256, data: Vec, transaction_encoded: Vec, effective_gas_price: U256, @@ -1343,6 +1358,7 @@ pub mod pallet { dest, value, weight_limit, + eth_gas_limit, data: data.clone(), transaction_encoded: transaction_encoded.clone(), effective_gas_price, @@ -1352,15 +1368,20 @@ pub mod pallet { let info = T::FeeInfo::dispatch_info(&call); let base_info = T::FeeInfo::base_dispatch_info(&mut call); drop(call); + + let extra_weight = base_info.total_weight(); let mut output = Self::bare_call( origin, dest, value, - weight_limit, - BalanceOf::::max_value(), + TransactionLimits::EthereumGas { + eth_gas_limit: eth_gas_limit.saturated_into(), + eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), + }, data, - ExecConfig::new_eth_tx(effective_gas_price, encoded_len, base_info.total_weight()), + ExecConfig::new_eth_tx(effective_gas_price, encoded_len, extra_weight), ); + if let Ok(return_value) = &output.result { if return_value.did_revert() { output.result = Err(>::ContractReverted.into()); @@ -1523,40 +1544,51 @@ impl Pallet { origin: OriginFor, dest: H160, evm_value: U256, - weight_limit: Weight, - storage_deposit_limit: BalanceOf, + transaction_limits: TransactionLimits, data: Vec, exec_config: ExecConfig, ) -> ContractResult> { if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) { return contract_result; } - let mut weight_meter = ContractWeightMeter::new(weight_limit); + + let mut transaction_meter = match TransactionMeter::new(transaction_limits) { + Ok(transaction_meter) => transaction_meter, + Err(error) => + return ContractResult { + result: Err(error), + weight_consumed: Default::default(), + weight_required: Default::default(), + storage_deposit: Default::default(), + }, + }; + let mut storage_deposit = Default::default(); let try_call = || { let origin = ExecOrigin::from_runtime_origin(origin)?; - let mut storage_meter = StorageMeter::new(storage_deposit_limit); let result = ExecStack::>::run_call( origin.clone(), dest, - &mut weight_meter, - &mut storage_meter, + &mut transaction_meter, evm_value, data, &exec_config, )?; - storage_deposit = - storage_meter.try_into_deposit(&origin, &exec_config).inspect_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}"); - })?; + + storage_deposit = transaction_meter + .execute_postponed_deposits(&origin, &exec_config) + .inspect_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}"); + })?; Ok(result) }; let result = Self::run_guarded(try_call); + ContractResult { result: result.map_err(|r| r.error), - weight_consumed: weight_meter.weight_consumed(), - weight_required: weight_meter.weight_required(), + weight_consumed: transaction_meter.weight_consumed(), + weight_required: transaction_meter.weight_required(), storage_deposit, } } @@ -1580,8 +1612,7 @@ impl Pallet { pub fn bare_instantiate( origin: OriginFor, evm_value: U256, - weight_limit: Weight, - mut storage_deposit_limit: BalanceOf, + transaction_limits: TransactionLimits, code: Code, data: Vec, salt: Option<[u8; 32]>, @@ -1591,50 +1622,58 @@ impl Pallet { if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) { return contract_result; } - let mut weight_meter = ContractWeightMeter::new(weight_limit); + + let mut transaction_meter = match TransactionMeter::new(transaction_limits) { + Ok(transaction_meter) => transaction_meter, + Err(error) => + return ContractResult { + result: Err(error), + weight_consumed: Default::default(), + weight_required: Default::default(), + storage_deposit: Default::default(), + }, + }; + let mut storage_deposit = Default::default(); + let try_instantiate = || { let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?; if_tracing(|t| t.instantiate_code(&code, salt.as_ref())); - let (executable, upload_deposit) = match code { + let executable = match code { Code::Upload(code) if code.starts_with(&polkavm_common::program::BLOB_MAGIC) => { let upload_account = T::UploadOrigin::ensure_origin(origin)?; - let (executable, upload_deposit) = Self::try_upload_pvm_code( + let (executable, ..) = Self::try_upload_pvm_code( upload_account, code, - storage_deposit_limit, + &mut transaction_meter, &exec_config, )?; - storage_deposit_limit.saturating_reduce(upload_deposit); - (executable, upload_deposit) + executable }, Code::Upload(code) => if T::AllowEVMBytecode::get() { let origin = T::UploadOrigin::ensure_origin(origin)?; let executable = ContractBlob::from_evm_init_code(code, origin)?; - (executable, Default::default()) + executable } else { return Err(>::CodeRejected.into()) }, Code::Existing(code_hash) => - (ContractBlob::from_storage(code_hash, &mut weight_meter)?, Default::default()), + ContractBlob::from_storage(code_hash, &mut transaction_meter)?, }; let instantiate_origin = ExecOrigin::from_account_id(instantiate_account.clone()); - let mut storage_meter = StorageMeter::new(storage_deposit_limit); let result = ExecStack::>::run_instantiate( instantiate_account, executable, - &mut weight_meter, - &mut storage_meter, + &mut transaction_meter, evm_value, data, salt.as_ref(), &exec_config, ); - storage_deposit = storage_meter - .try_into_deposit(&instantiate_origin, &exec_config)? - .saturating_add(&StorageDeposit::Charge(upload_deposit)); + storage_deposit = + transaction_meter.execute_postponed_deposits(&instantiate_origin, &exec_config)?; result }; let output = Self::run_guarded(try_instantiate); @@ -1642,8 +1681,8 @@ impl Pallet { result: output .map(|(addr, result)| InstantiateReturnValue { result, addr }) .map_err(|e| e.error), - weight_consumed: weight_meter.weight_consumed(), - weight_required: weight_meter.weight_required(), + weight_consumed: transaction_meter.weight_consumed(), + weight_required: transaction_meter.weight_required(), storage_deposit, } } @@ -1712,15 +1751,10 @@ impl Pallet { // the dry-run might leave out certain fields // in those cases we skip the check that the caller has enough balance // to pay for the fees - let exec_config = { - let base_info = T::FeeInfo::base_dispatch_info(&mut call_info.call); - ExecConfig::new_eth_tx( - effective_gas_price, - call_info.encoded_len, - base_info.total_weight(), - ) - .with_dry_run() - }; + let base_info = T::FeeInfo::base_dispatch_info(&mut call_info.call); + let base_weight = base_info.total_weight(); + let exec_config = + ExecConfig::new_eth_tx(effective_gas_price, call_info.encoded_len, base_weight); // emulate transaction behavior let fees = call_info.tx_fee.saturating_add(call_info.storage_deposit); @@ -1773,8 +1807,10 @@ impl Pallet { OriginFor::::signed(origin), dest, value, - call_info.weight_limit, - BalanceOf::::max_value(), + TransactionLimits::EthereumGas { + eth_gas_limit: call_info.eth_gas_limit.saturated_into(), + eth_tx_info: EthTxInfo::new(call_info.encoded_len, base_weight), + }, input.clone(), exec_config, ); @@ -1813,8 +1849,10 @@ impl Pallet { let result = crate::Pallet::::bare_instantiate( OriginFor::::signed(origin), value, - call_info.weight_limit, - BalanceOf::::max_value(), + TransactionLimits::EthereumGas { + eth_gas_limit: call_info.eth_gas_limit.saturated_into(), + eth_tx_info: EthTxInfo::new(call_info.encoded_len, base_weight), + }, Code::Upload(code.clone()), data.clone(), None, @@ -2030,12 +2068,14 @@ impl Pallet { storage_deposit_limit: BalanceOf, ) -> CodeUploadResult> { let origin = T::UploadOrigin::ensure_origin(origin)?; - let (module, deposit) = Self::try_upload_pvm_code( - origin, - code, - storage_deposit_limit, - &ExecConfig::new_substrate_tx(), - )?; + + let mut meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit { + weight_limit: Default::default(), + deposit_limit: storage_deposit_limit, + })?; + + let (module, deposit) = + Self::try_upload_pvm_code(origin, code, &mut meter, &ExecConfig::new_substrate_tx())?; Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) } @@ -2173,15 +2213,14 @@ impl Pallet { } /// Uploads new code and returns the Vm binary contract blob and deposit amount collected. - pub fn try_upload_pvm_code( + fn try_upload_pvm_code( origin: T::AccountId, code: Vec, - storage_deposit_limit: BalanceOf, - exec_config: &ExecConfig, + meter: &mut ResourceMeter, + exec_config: &ExecConfig, ) -> Result<(ContractBlob, BalanceOf), DispatchError> { let mut module = ContractBlob::from_pvm_code(code, origin)?; - let deposit = module.store_code(exec_config, None)?; - ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); + let deposit = module.store_code(exec_config, meter)?; Ok((module, deposit)) } @@ -2693,8 +2732,10 @@ macro_rules! impl_runtime_apis_plus_revive_traits { ::RuntimeOrigin::signed(origin), dest, $crate::Pallet::::convert_native_to_evm(value), - weight_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + $crate::metering::TransactionLimits::WeightAndDeposit { + weight_limit: weight_limit.unwrap_or(blockweights.max_block), + deposit_limit: storage_deposit_limit.unwrap_or(u128::MAX), + }, input_data, $crate::ExecConfig::new_substrate_tx().with_dry_run(), ) @@ -2717,8 +2758,10 @@ macro_rules! impl_runtime_apis_plus_revive_traits { $crate::Pallet::::bare_instantiate( ::RuntimeOrigin::signed(origin), $crate::Pallet::::convert_native_to_evm(value), - weight_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + $crate::metering::TransactionLimits::WeightAndDeposit { + weight_limit: weight_limit.unwrap_or(blockweights.max_block), + deposit_limit: storage_deposit_limit.unwrap_or(u128::MAX), + }, code, data, salt, diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 164804925e74c..40fa3f3fb1921 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -17,3 +17,594 @@ pub mod storage; pub mod weight; + +use crate::{ + evm::fees::InfoT, exec::CallResources, storage::ContractInfo, vm::evm::Halt, BalanceOf, Config, + Error, ExecConfig, ExecOrigin as Origin, StorageDeposit, LOG_TARGET, +}; +use frame_support::DefaultNoBound; +use num_traits::Zero; + +use core::{fmt::Debug, marker::PhantomData, ops::ControlFlow}; +use sp_runtime::{FixedPointNumber, Saturating, Weight}; +use storage::{DepositOf, Diff, GenericMeter as GenericStorageMeter, Meter as RootStorageMeter}; +use weight::{ChargedAmount, Token, WeightMeter}; + +use sp_runtime::{DispatchError, DispatchResult, FixedU128, SaturatedConversion}; + +/// Used to implement a type state pattern for the meter. +/// +/// It is sealed and cannot be implemented outside of this module. +pub trait State: private::Sealed + Default + Debug {} + +/// State parameter that constitutes a meter that is in its root state. +#[derive(Default, Debug)] +pub struct Root; + +/// State parameter that constitutes a meter that is in its nested state. +/// Its value indicates whether the nested meter has its own limit. +#[derive(Default, Debug)] +pub struct Nested; + +impl State for Root {} +impl State for Nested {} + +mod private { + pub trait Sealed {} + impl Sealed for super::Root {} + impl Sealed for super::Nested {} +} + +pub type TransactionMeter = ResourceMeter; +pub type FrameMeter = ResourceMeter; + +#[derive(DefaultNoBound)] +pub struct ResourceMeter { + weight: WeightMeter, + deposit: GenericStorageMeter, + + eth_gas_limit: BalanceOf, + max_total_gas: DepositOf, + total_consumed_weight_before: Weight, + total_consumed_deposit_before: DepositOf, + + transaction_limits: TransactionLimits, + + _phantom: PhantomData, +} + +#[derive(Debug, Clone)] +pub struct EthTxInfo { + pub encoded_len: u32, + pub extra_weight: Weight, + _phantom: PhantomData, +} + +#[derive(Debug, Clone)] +pub enum TransactionLimits { + EthereumGas { eth_gas_limit: BalanceOf, eth_tx_info: EthTxInfo }, + WeightAndDeposit { weight_limit: Weight, deposit_limit: BalanceOf }, +} + +impl Default for TransactionLimits { + fn default() -> Self { + Self::WeightAndDeposit { + weight_limit: Default::default(), + deposit_limit: Default::default(), + } + } +} + +impl ResourceMeter { + pub fn charge_weight_token>( + &mut self, + token: Tok, + ) -> Result { + // TODO: optimize + let weight_left = self.weight_left().ok_or(>::OutOfGas)?; + + self.weight.charge(token, weight_left) + } + + pub fn charge_or_halt>( + &mut self, + token: Tok, + ) -> ControlFlow { + // TODO: optimize + let weight_left = self.weight_left().unwrap_or_default(); + + self.weight.charge_or_halt(token, weight_left) + } + + pub fn adjust_weight>(&mut self, charged_amount: ChargedAmount, token: Tok) { + self.weight.adjust_weight(charged_amount, token); + } + + pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> { + // TODO: optimize + let weight_left = self.weight_left().ok_or(>::OutOfGas)?; + let weight_consumed = self.weight.weight_consumed(); + + self.weight + .sync_from_executor(engine_fuel, weight_left.saturating_add(weight_consumed)) + } + + pub fn consume_all_weight(&mut self) { + // TODO: optimize + let weight_left = self.weight_left().unwrap_or_default(); + let weight_consumed = self.weight.weight_consumed(); + + self.weight.consume_all(weight_left.saturating_add(weight_consumed)); + } + + pub fn sync_to_executor(&mut self) -> polkavm::Gas { + // TODO: optimize + let weight_left = self.weight_left().unwrap_or_default(); + + self.weight.sync_to_executor(weight_left) + } + + pub fn charge_deposit(&mut self, deposit: &DepositOf) -> DispatchResult { + self.deposit.record_charge(deposit); + + if self.deposit.is_root { + if self.deposit_left().is_none() { + return Err(>::StorageDepositLimitExhausted.into()); + } + } + + Ok(()) + } + + pub fn new_nested(&self, limit: &CallResources) -> Result, DispatchError> { + let self_consumed_weight = self.weight.weight_consumed(); + let self_consumed_deposit = self.deposit.consumed(); + + let total_consumed_weight = + self.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + self.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + let (nested_gas_limit, nested_weight_limit, nested_deposit_limit, max_total_gas) = + match &self.transaction_limits { + TransactionLimits::EthereumGas { eth_tx_info, .. } => { + let max_total_gas = eth_tx_info.max_total_gas( + self.eth_gas_limit, + self.total_consumed_weight_before, + &self.total_consumed_deposit_before, + ); + + let total_gas_consumption = + eth_tx_info.gas_consumption(total_consumed_weight, &total_consumed_deposit); + + let StorageDeposit::Refund(gas_left) = + max_total_gas.saturating_sub(&total_gas_consumption) + else { + return Err(>::OutOfGas.into()); + }; + + if self.weight.weight_limit.is_none() && + matches!(limit, CallResources::NoLimits | CallResources::Ethereum(..)) + { + let nested_gas_limit = if let CallResources::Ethereum(gas) = limit { + gas_left.min(*gas) + } else { + gas_left + }; + (nested_gas_limit, None, None, max_total_gas) + } else { + let weight_left = eth_tx_info + .weight_remaining( + &max_total_gas, + total_consumed_weight, + &total_consumed_deposit, + ) + .ok_or(>::OutOfGas)?; + + let weight_left = match self.weight.weight_limit { + Some(weight_limit) => weight_left.min( + weight_limit + .checked_sub(&self_consumed_weight) + .ok_or(>::OutOfGas)?, + ), + None => weight_left, + }; + + let deposit_left: BalanceOf = + EthTxInfo::::deposit_remaining(gas_left); + let deposit_left = match self.deposit.limit { + Some(deposit_limit) => deposit_left.min( + self_consumed_deposit + .available(&deposit_limit) + .ok_or(>::StorageDepositLimitExhausted)?, + ), + None => deposit_left, + }; + + match limit { + CallResources::NoLimits => + (gas_left, Some(weight_left), Some(deposit_left), max_total_gas), + CallResources::Ethereum(gas) => ( + gas_left.min(*gas), + Some(weight_left), + Some(deposit_left), + max_total_gas, + ), + CallResources::Precise { weight, deposit_limit } => { + let nested_weight_limit = weight_left.min(*weight); + let nested_deposit_limit = deposit_left.min(*deposit_limit); + + let new_max_total_gas = eth_tx_info.gas_consumption( + total_consumed_weight.saturating_add(nested_weight_limit), + &total_consumed_deposit.saturating_add( + &StorageDeposit::Charge(nested_deposit_limit), + ), + ); + + let gas_limit = + new_max_total_gas.saturating_sub(&total_gas_consumption); + let DepositOf::::Refund(gas_limit) = gas_limit else { + return Err(>::OutOfGas.into()); + }; + + ( + gas_left.min(gas_limit), + Some(nested_weight_limit), + Some(nested_deposit_limit), + max_total_gas, + ) + }, + } + } + }, + + TransactionLimits::WeightAndDeposit { .. } => { + let weight_left = self + .weight + .weight_limit + .expect("Weight limits all always defined for WeightAndDeposit; qed") + .checked_sub(&self_consumed_weight) + .ok_or(>::OutOfGas)?; + + let deposit_limit = self + .deposit + .limit + .expect("Deposit limits all always defined for WeightAndDeposit; qed"); + let deposit_left = self_consumed_deposit + .available(&deposit_limit) + .ok_or(>::StorageDepositLimitExhausted)?; + + match limit { + CallResources::NoLimits => ( + Default::default(), + Some(weight_left), + Some(deposit_left), + Default::default(), + ), + + CallResources::Ethereum(gas) => { + let weight_gas = T::FeeInfo::weight_to_fee(&weight_left); + let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(deposit_left); + let gas_left = weight_gas.saturating_add(deposit_gas); + if (gas_left).is_zero() { + Err(>::OutOfGas)?; + } + + let ratio = FixedU128::from_rational( + gas_left.min(*gas).saturated_into(), + gas_left.saturated_into(), + ); + + let weight_limit = Weight::from_parts( + ratio.saturating_mul_int(weight_left.ref_time()), + ratio.saturating_mul_int(weight_left.proof_size()), + ); + let deposit_limit = ratio.saturating_mul_int(deposit_left); + + ( + Default::default(), + Some(weight_limit), + Some(deposit_limit), + Default::default(), + ) + }, + + CallResources::Precise { weight, deposit_limit } => ( + Default::default(), + Some(weight_left.min(*weight)), + Some(deposit_left.min(*deposit_limit)), + Default::default(), + ), + } + }, + }; + + Ok(FrameMeter:: { + weight: WeightMeter::new(nested_weight_limit), + deposit: self.deposit.nested(nested_deposit_limit), + eth_gas_limit: nested_gas_limit, + max_total_gas, + total_consumed_weight_before: total_consumed_weight, + total_consumed_deposit_before: total_consumed_deposit, + transaction_limits: self.transaction_limits.clone(), + _phantom: PhantomData, + }) + } + + pub fn absorb_weight_meter_only(&mut self, other: FrameMeter) { + self.weight.absorb_nested(other.weight); + } + + pub fn absorb_all_meters( + &mut self, + other: FrameMeter, + contract: &T::AccountId, + info: Option<&mut ContractInfo>, + ) { + self.weight.absorb_nested(other.weight); + self.deposit.absorb(other.deposit, contract, info); + } + + pub fn eth_gas_left(&self) -> Option> { + match &self.transaction_limits { + TransactionLimits::EthereumGas { eth_tx_info, .. } => { + let self_consumed_weight = self.weight.weight_consumed(); + let self_consumed_deposit = self.deposit.consumed(); + + let total_consumed_weight = + self.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + self.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + let total_gas_consumption = + eth_tx_info.gas_consumption(total_consumed_weight, &total_consumed_deposit); + + match self.max_total_gas.saturating_sub(&total_gas_consumption) { + StorageDeposit::Refund(gas_left) => Some(gas_left), + StorageDeposit::Charge(_) => { + log::debug!( target: LOG_TARGET, "Eth gas limit exhausted: {:?} > {:?}", total_gas_consumption, self.max_total_gas); + None + }, + } + }, + + TransactionLimits::WeightAndDeposit { .. } => { + match (self.weight_left(), self.deposit_left()) { + (Some(weight_left), Some(deposit_left)) => { + let weight_gas = T::FeeInfo::weight_to_fee(&weight_left); + let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(deposit_left); + + Some(weight_gas.saturating_add(deposit_gas)) + }, + _ => None, + } + }, + } + } + + pub fn weight_left(&self) -> Option { + match &self.transaction_limits { + TransactionLimits::EthereumGas { eth_tx_info, .. } => { + let self_consumed_weight = self.weight.weight_consumed(); + let self_consumed_deposit = self.deposit.consumed(); + + let total_consumed_weight = + self.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + self.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + let weight_left = eth_tx_info.weight_remaining( + &self.max_total_gas, + total_consumed_weight, + &total_consumed_deposit, + )?; + + Some(match self.weight.weight_limit { + Some(weight_limit) => + weight_left.min(weight_limit.checked_sub(&self_consumed_weight)?), + None => weight_left, + }) + }, + + TransactionLimits::WeightAndDeposit { .. } => { + let weight_limit = self + .weight + .weight_limit + .expect("Weight limits all always defined for WeightAndDeposit; qed"); + weight_limit.checked_sub(&self.weight.weight_consumed()) + }, + } + } + + pub fn deposit_left(&self) -> Option> { + match &self.transaction_limits { + TransactionLimits::EthereumGas { .. } => { + let eth_gas_left = self.eth_gas_left()?; + let deposit_left = EthTxInfo::::deposit_remaining(eth_gas_left); + + match self.deposit.limit { + Some(deposit_limit) => { + let deposit_available = self.deposit.consumed().available(&deposit_limit); + let Some(deposit_available) = deposit_available else { + log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", self.deposit.consumed(), deposit_limit); + return None; + }; + + Some(deposit_left.min(deposit_available)) + }, + None => Some(deposit_left), + } + }, + + TransactionLimits::WeightAndDeposit { .. } => { + let deposit_limit = self + .deposit + .limit + .expect("Deposit limits all always defined for WeightAndDeposit; qed"); + let deposit_available = self.deposit.consumed().available(&deposit_limit); + + if deposit_available.is_none() { + log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", self.deposit.consumed(), deposit_limit); + return None; + } + + deposit_available + }, + } + } + + pub fn weight_consumed(&self) -> Weight { + self.weight.weight_consumed() + } + + pub fn weight_required(&self) -> Weight { + self.weight.weight_required() + } +} + +impl EthTxInfo { + pub fn new(encoded_len: u32, extra_weight: Weight) -> Self { + Self { encoded_len, extra_weight, _phantom: PhantomData } + } + + pub fn gas_consumption( + &self, + consumed_weight: Weight, + consumed_deposit: &DepositOf, + ) -> DepositOf { + let fee_a = StorageDeposit::Refund(T::FeeInfo::fixed_fee(self.encoded_len)) + .saturating_sub(consumed_deposit) + .scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()); + + let fee_b = T::FeeInfo::weight_to_fee(&consumed_weight.saturating_add(self.extra_weight)); + + fee_a.saturating_add(&StorageDeposit::Refund(fee_b)) + } + + pub fn gas_remaining( + max_total_gas: &DepositOf, + total_gas_consumption: &DepositOf, + ) -> Option> { + match max_total_gas.saturating_sub(total_gas_consumption) { + StorageDeposit::Refund(amount) => Some(amount), + StorageDeposit::Charge(_) => None, + } + } + + pub fn max_total_gas( + &self, + eth_gas_limit: BalanceOf, + total_consumed_weight_before: Weight, + total_consumed_deposit_before: &DepositOf, + ) -> DepositOf { + self.gas_consumption(total_consumed_weight_before, total_consumed_deposit_before) + .saturating_add(&StorageDeposit::Refund(eth_gas_limit)) + } + + pub fn weight_remaining( + &self, + max_total_gas: &DepositOf, + total_weight_consumption: Weight, + total_deposit_consumption: &DepositOf, + ) -> Option { + let consumable_fee = max_total_gas.saturating_add( + &total_deposit_consumption + .saturating_add(&StorageDeposit::Charge(T::FeeInfo::fixed_fee(self.encoded_len))) + .scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()), + ); + + let StorageDeposit::Refund(consumable_fee) = consumable_fee else { + return None; + }; + + T::FeeInfo::fee_to_weight(consumable_fee) + .checked_sub(&total_weight_consumption.saturating_add(self.extra_weight)) + } + + pub fn deposit_remaining(gas_remaining: BalanceOf) -> BalanceOf { + T::FeeInfo::next_fee_multiplier().saturating_mul_int(gas_remaining) + } +} + +impl TransactionMeter { + pub fn new(transaction_limits: TransactionLimits) -> Result { + match &transaction_limits { + TransactionLimits::EthereumGas { eth_gas_limit, eth_tx_info } => { + let base_gas = + eth_tx_info.gas_consumption(Weight::default(), &DepositOf::::default()); + + let Some(gas_limit) = base_gas.available(ð_gas_limit) else { + return Err(>::StorageDepositNotEnoughFunds.into()); + }; + + Ok(Self { + weight: WeightMeter::new(None), + deposit: RootStorageMeter::new(None), + eth_gas_limit: gas_limit, + max_total_gas: StorageDeposit::Refund(*eth_gas_limit), + total_consumed_weight_before: Weight::default(), + total_consumed_deposit_before: DepositOf::::default(), + transaction_limits, + _phantom: PhantomData, + }) + }, + + TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } => Ok(Self { + weight: WeightMeter::new(Some(*weight_limit)), + deposit: RootStorageMeter::new(Some(*deposit_limit)), + eth_gas_limit: Default::default(), // ignore eth gas limit for Substrate executions + max_total_gas: Default::default(), // ignore eth gas limit for Substrate executions + total_consumed_weight_before: Weight::default(), + total_consumed_deposit_before: DepositOf::::default(), + transaction_limits, + _phantom: PhantomData, + }), + } + } + + pub fn execute_postponed_deposits( + &self, + origin: &Origin, + exec_config: &ExecConfig, + ) -> Result, DispatchError> { + if self.deposit_left().is_none() { + // Deposit limit exceeded + return Err(>::StorageDepositNotEnoughFunds.into()); + } + + self.deposit.execute_postponed_deposits(origin, exec_config) + } +} + +impl FrameMeter { + pub fn charge_contract_deposit_and_transfer( + &mut self, + contract: T::AccountId, + amount: DepositOf, + ) { + self.deposit.charge_deposit(contract, amount) + } + + pub fn terminate(&mut self, info: &ContractInfo, beneficiary: T::AccountId) { + self.deposit.terminate(info, beneficiary); + } + + pub fn record_contract_storage_changes(&mut self, diff: &Diff) { + self.deposit.charge(diff); + } + + /// [`Self::charge_contract_deposit_and_transfer`] and [`Self::record_contract_storage_changes`] + /// does not enforce the storage limit since we want to do this check as late as possible to + /// allow later refunds to offset earlier charges. + pub fn finalize(&mut self, info: Option<&mut ContractInfo>) -> Result<(), DispatchError> { + self.deposit.finalize_own_contributions(info); + + if self.deposit_left().is_none() { + return Err(>::StorageDepositLimitExhausted.into()); + } + + Ok(()) + } +} diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index 4edf7ee72ae2e..ca35590e6b479 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -17,13 +17,14 @@ //! This module contains functions to meter the storage deposit. +use super::{Nested, Root, State}; use crate::{ address::AddressMapper, storage::ContractInfo, AccountIdOf, AccountInfo, AccountInfoOf, BalanceOf, CodeInfo, Config, Error, ExecConfig, ExecOrigin as Origin, HoldReason, ImmutableDataOf, Inspect, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, }; use alloc::vec::Vec; -use core::{fmt::Debug, marker::PhantomData}; +use core::marker::PhantomData; use frame_support::{ storage::{with_transaction, TransactionOutcome}, traits::{ @@ -35,7 +36,7 @@ use frame_support::{ }; use sp_runtime::{ traits::{Saturating, Zero}, - DispatchError, DispatchResult, FixedPointNumber, FixedU128, + DispatchError, FixedPointNumber, FixedU128, }; /// Deposit that uses the native fungible's balance type. @@ -44,9 +45,6 @@ pub type DepositOf = Deposit>; /// A production root storage meter that actually charges from its origin. pub type Meter = RawMeter; -/// A production nested storage meter that actually charges from its origin. -pub type NestedMeter = RawMeter; - /// A production storage meter that actually charges from its origin. /// /// This can be used where we want to be generic over the state (Root vs. Nested). @@ -76,28 +74,11 @@ pub trait Ext { /// It uses [`frame_support::traits::fungible::Mutate`] in order to do accomplish the reserves. pub enum ReservingExt {} -/// Used to implement a type state pattern for the meter. -/// -/// It is sealed and cannot be implemented outside of this module. -pub trait State: private::Sealed {} - -/// State parameter that constitutes a meter that is in its root state. -#[derive(Default, Debug)] -pub struct Root; - -/// State parameter that constitutes a meter that is in its nested state. -/// Its value indicates whether the nested meter has its own limit. -#[derive(Default, Debug)] -pub struct Nested; - -impl State for Root {} -impl State for Nested {} - /// A type that allows the metering of consumed or freed storage of a single contract call stack. #[derive(DefaultNoBound, RuntimeDebugNoBound)] -pub struct RawMeter { +pub struct RawMeter { /// The limit of how much balance this meter is allowed to consume. - limit: BalanceOf, + pub limit: Option>, /// The amount of balance that was used in this meter and all of its already absorbed children. total_deposit: DepositOf, /// The amount of storage changes that were recorded in this meter alone. @@ -110,7 +91,7 @@ pub struct RawMeter { /// True if this is the root meter. /// /// Sometimes we cannot know at compile time. - is_root: bool, + pub is_root: bool, /// Type parameter only used in impls. _phantom: PhantomData<(E, S)>, } @@ -260,17 +241,16 @@ impl RawMeter where T: Config, E: Ext, - S: State + Default + Debug, + S: State, { /// Create a new child that has its `limit`. /// /// This is called whenever a new subcall is initiated in order to track the storage /// usage for this sub call separately. This is necessary because we want to exchange balance /// with the current contract we are interacting with. - pub fn nested(&self, limit: BalanceOf) -> RawMeter { + pub fn nested(&self, limit: Option>) -> RawMeter { debug_assert!(matches!(self.contract_state(), ContractState::Alive)); - - RawMeter { limit: self.available().min(limit), ..Default::default() } + RawMeter { limit, ..Default::default() } } /// Absorb a child that was spawned to handle a sub call. @@ -331,18 +311,9 @@ where /// /// This will not perform a charge. It just records it to reflect it in the /// total amount of storage required for a transaction. - pub fn record_charge(&mut self, amount: &DepositOf) -> DispatchResult { + pub fn record_charge(&mut self, amount: &DepositOf) { let total_deposit = self.total_deposit.saturating_add(&amount); - - // Limits are enforced at the end of each frame. But plain balance transfers - // do not sapwn a frame. This is specifically to enforce the limit for those. - if self.is_root && total_deposit.charge_or_zero() > self.limit { - log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit); - return Err(>::StorageDepositLimitExhausted.into()); - } - self.total_deposit = total_deposit; - Ok(()) } /// The amount of balance that this meter has consumed. @@ -353,13 +324,6 @@ where self.total_deposit.saturating_add(&self.own_contribution.update_contract(None)) } - /// The amount of balance still available from the current meter. - /// - /// This includes charges from the current frame but no refunds. - pub fn available(&self) -> BalanceOf { - self.consumed().available(&self.limit) - } - /// Returns the state of the currently executed contract. fn contract_state(&self) -> ContractState { match &self.own_contribution { @@ -383,7 +347,7 @@ where /// /// If the limit is larger than what the origin can afford we will just fail /// when collecting the deposits in `try_into_deposit`. - pub fn new(limit: BalanceOf) -> Self { + pub fn new(limit: Option>) -> Self { Self { limit, is_root: true, ..Default::default() } } @@ -393,8 +357,8 @@ where /// /// This drops the root meter in order to make sure it is only called when the whole /// execution did finish. - pub fn try_into_deposit( - mut self, + pub fn execute_postponed_deposits( + &self, origin: &Origin, exec_config: &ExecConfig, ) -> Result, DispatchError> { @@ -435,7 +399,7 @@ where }; try_charge().map_err(|_: DispatchError| >::StorageDepositNotEnoughFunds)?; - Ok(self.total_deposit) + Ok(self.total_deposit.clone()) } } @@ -460,7 +424,7 @@ impl> RawMeter { /// alese in order to be able to refund it. pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf) { // will not fail in a nested meter - self.record_charge(&amount).ok(); + self.record_charge(&amount); self.charges.push(Charge { contract, amount, state: ContractState::Alive }); } @@ -479,25 +443,14 @@ impl> RawMeter { }; } - /// [`Self::charge`] does not enforce the storage limit since we want to do this check as late - /// as possible to allow later refunds to offset earlier charges. - pub fn enforce_limit( - &mut self, - info: Option<&mut ContractInfo>, - ) -> Result<(), DispatchError> { + /// Determine the actual final charge from the own contributions + pub fn finalize_own_contributions(&mut self, info: Option<&mut ContractInfo>) { let deposit = self.own_contribution.update_contract(info); - let total_deposit = self.total_deposit.saturating_add(&deposit); + // We don't want to override a `Terminated` with a `Checked`. if matches!(self.contract_state(), ContractState::Alive) { self.own_contribution = Contribution::Checked(deposit); } - if let Deposit::Charge(amount) = total_deposit { - if amount > self.limit { - log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit); - return Err(>::StorageDepositLimitExhausted.into()); - } - } - Ok(()) } } @@ -556,7 +509,6 @@ fn terminate( contract: &T::AccountId, beneficiary: &T::AccountId, delete_code: &bool, -) -> Result<(), DispatchError> { fn terminate_inner( contract: &T::AccountId, beneficiary: &T::AccountId, @@ -614,8 +566,6 @@ mod private { #[cfg(test)] mod tests { use super::*; - use crate::{exec::AccountIdOf, test_utils::*, tests::Test}; - use frame_support::parameter_types; use pretty_assertions::assert_eq; type TestMeter = RawMeter; diff --git a/substrate/frame/revive/src/metering/weight.rs b/substrate/frame/revive/src/metering/weight.rs index a8e07266bc7ac..de574264dd6d6 100644 --- a/substrate/frame/revive/src/metering/weight.rs +++ b/substrate/frame/revive/src/metering/weight.rs @@ -14,13 +14,9 @@ // 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. -use crate::{exec::ExecError, vm::evm::Halt, weights::WeightInfo, Config, Error}; +use crate::{vm::evm::Halt, weights::WeightInfo, Config, Error}; use core::{marker::PhantomData, ops::ControlFlow}; -use frame_support::{ - dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo}, - weights::Weight, - DefaultNoBound, -}; +use frame_support::{weights::Weight, DefaultNoBound}; use sp_runtime::DispatchError; #[cfg(test)] @@ -44,11 +40,8 @@ struct EngineMeter { impl EngineMeter { /// Create a meter with the given fuel limit. - fn new(limit: Weight) -> Self { - Self { - fuel: limit.ref_time().saturating_div(Self::ref_time_per_fuel()), - _phantom: PhantomData, - } + fn new() -> Self { + Self { fuel: 0, _phantom: PhantomData } } /// Set the fuel left to the given value. @@ -61,13 +54,9 @@ impl EngineMeter { /// Charge the given amount of ref time. /// Returns the amount of fuel left. - fn charge_ref_time(&mut self, ref_time: u64) -> Result { - let amount = ref_time - .checked_div(Self::ref_time_per_fuel()) - .ok_or(Error::::InvalidSchedule)?; - - self.fuel.checked_sub(amount).ok_or_else(|| Error::::OutOfGas)?; - Ok(Syncable(self.fuel.try_into().map_err(|_| Error::::OutOfGas)?)) + fn sync_remaining_ref_time(&mut self, remaining_ref_time: u64) -> polkavm::Gas { + self.fuel = remaining_ref_time.saturating_div(Self::ref_time_per_fuel()); + self.fuel.try_into().unwrap_or(polkavm::Gas::MAX) } /// How much ref time does each PolkaVM gas correspond to. @@ -81,12 +70,6 @@ impl EngineMeter { } } -/// Used to capture the ref time left before entering a host function. -/// -/// Has to be consumed in order to sync back the gas after leaving the host function. -#[must_use] -pub struct RefTimeLeft(u64); - /// Resource that needs to be synced to the executor. /// /// Wrapped to make sure that the resource will be synced back to the executor. @@ -141,7 +124,7 @@ pub struct ErasedToken { #[derive(DefaultNoBound)] pub struct WeightMeter { - weight_limit: Weight, + pub weight_limit: Option, /// Amount of weight already consumed. Must be < `weight_limit`. weight_consumed: Weight, /// Due to `adjust_weight` and `nested` the `weight_consumed` can temporarily peak above its @@ -157,31 +140,18 @@ pub struct WeightMeter { } impl WeightMeter { - pub fn new(weight_limit: Weight) -> Self { + pub fn new(weight_limit: Option) -> Self { WeightMeter { weight_limit, weight_consumed: Default::default(), weight_consumed_highest: Default::default(), - engine_meter: EngineMeter::new(weight_limit), + engine_meter: EngineMeter::new(), _phantom: PhantomData, #[cfg(test)] tokens: Vec::new(), } } - /// Create a new weight meter by removing *all* the weight from the current meter. - /// - /// This should only be used by the primordial frame in a sequence of calls - every subsequent - /// frame should use [`nested`](Self::nested). - pub fn nested_take_all(&mut self) -> Self { - WeightMeter::new(self.weight_left()) - } - - /// Create a new weight meter for a nested call by removing weight from the current meter. - pub fn nested(&mut self, amount: Weight) -> Self { - WeightMeter::new(self.weight_left().min(amount)) - } - /// Absorb the remaining weight of a nested meter after we are done using it. pub fn absorb_nested(&mut self, nested: Self) { self.weight_consumed_highest = self @@ -201,7 +171,11 @@ impl WeightMeter { /// NOTE that amount isn't consumed if there is not enough weight. This is considered /// safe because we always charge weight before performing any resource-spending action. #[inline] - pub fn charge>(&mut self, token: Tok) -> Result { + pub fn charge>( + &mut self, + token: Tok, + weight_left: Weight, + ) -> Result { #[cfg(test)] { // Unconditionally add the token to the storage. @@ -212,14 +186,11 @@ impl WeightMeter { let amount = token.weight(); // It is OK to not charge anything on failure because we always charge _before_ we perform // any action - let consumed = { - let consumed = self.weight_consumed.saturating_add(amount); - if consumed.any_gt(self.weight_limit) { - Err(>::OutOfGas)?; - } - consumed - }; - self.weight_consumed = consumed; + if amount.any_gt(weight_left) { + Err(>::OutOfGas)?; + } + + self.weight_consumed = self.weight_consumed.saturating_add(amount); Ok(ChargedAmount(amount)) } @@ -227,8 +198,9 @@ impl WeightMeter { pub fn charge_or_halt>( &mut self, token: Tok, + weight_left: Weight, ) -> ControlFlow { - self.charge(token) + self.charge(token, weight_left) .map_or_else(|_| ControlFlow::Break(Error::::OutOfGas.into()), ControlFlow::Continue) } @@ -252,16 +224,19 @@ impl WeightMeter { pub fn sync_from_executor( &mut self, engine_fuel: polkavm::Gas, - ) -> Result { + weight_limit: Weight, + ) -> Result<(), DispatchError> { let weight_consumed = self .engine_meter .set_fuel(engine_fuel.try_into().map_err(|_| Error::::OutOfGas)?); + self.weight_consumed.saturating_accrue(weight_consumed); - if self.weight_consumed.any_gt(self.weight_limit) { - self.weight_consumed = self.weight_limit; + if self.weight_consumed.any_gt(weight_limit) { + self.weight_consumed = weight_limit; Err(>::OutOfGas)?; } - Ok(RefTimeLeft(self.weight_left().ref_time())) + + Ok(()) } /// Hand over the gas metering responsibility from this meter to the executor. @@ -272,9 +247,8 @@ impl WeightMeter { /// /// It is important that this does **not** actually sync with the executor. That has /// to be done by the caller. - pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result { - let ref_time_consumed = before.0.saturating_sub(self.weight_left().ref_time()); - self.engine_meter.charge_ref_time(ref_time_consumed) + pub fn sync_to_executor(&mut self, weight_left: Weight) -> polkavm::Gas { + self.engine_meter.sync_remaining_ref_time(weight_left.ref_time()) } /// Returns the amount of weight that is required to run the same call. @@ -291,41 +265,18 @@ impl WeightMeter { } /// Returns how much weight left from the initial budget. + #[cfg(test)] pub fn weight_left(&self) -> Weight { self.weight_limit.saturating_sub(self.weight_consumed) } - /// The amount of weight in terms of engine gas. - pub fn engine_fuel_left(&self) -> Result { - self.engine_meter.fuel.try_into().map_err(|_| >::OutOfGas.into()) - } - - /// Turn this WeightMeter into a DispatchResult that contains the actually used weight. - pub fn into_dispatch_result( - self, - result: Result, - base_weight: Weight, - ) -> DispatchResultWithPostInfo - where - E: Into, - { - let post_info = PostDispatchInfo { - actual_weight: Some(self.weight_consumed().saturating_add(base_weight)), - pays_fee: Default::default(), - }; - - result - .map(|_| post_info) - .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error }) - } - #[cfg(test)] pub fn tokens(&self) -> &[ErasedToken] { &self.tokens } - pub fn consume_all(&mut self) { - self.weight_consumed = self.weight_limit; + pub fn consume_all(&mut self, weight_limit: Weight) { + self.weight_consumed = weight_limit; } } diff --git a/substrate/frame/revive/src/precompiles.rs b/substrate/frame/revive/src/precompiles.rs index 6b5d55ba5fdd9..e9fce66a06a40 100644 --- a/substrate/frame/revive/src/precompiles.rs +++ b/substrate/frame/revive/src/precompiles.rs @@ -31,10 +31,7 @@ mod tests; pub use crate::{ exec::{ExecError, PrecompileExt as Ext, PrecompileWithInfoExt as ExtWithInfo}, - metering::{ - storage::Diff, - weight::{Token, WeightMeter}, - }, + metering::{storage::Diff, weight::Token}, vm::RuntimeCosts, AddressMapper, }; diff --git a/substrate/frame/revive/src/precompiles/builtin/blake2f.rs b/substrate/frame/revive/src/precompiles/builtin/blake2f.rs index bad0fa27f6134..907845c2c42de 100644 --- a/substrate/frame/revive/src/precompiles/builtin/blake2f.rs +++ b/substrate/frame/revive/src/precompiles/builtin/blake2f.rs @@ -46,7 +46,7 @@ impl PrimitivePrecompile for Blake2F { rounds_buf.copy_from_slice(&input[0..4]); let rounds: u32 = u32::from_be_bytes(rounds_buf); - env.gas_meter_mut().charge(RuntimeCosts::Blake2F(rounds))?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::Blake2F(rounds))?; // we use from_le_bytes below to effectively swap byte order to LE if architecture is BE diff --git a/substrate/frame/revive/src/precompiles/builtin/bn128.rs b/substrate/frame/revive/src/precompiles/builtin/bn128.rs index 1aad0fbedc40f..413a2ae62ac18 100644 --- a/substrate/frame/revive/src/precompiles/builtin/bn128.rs +++ b/substrate/frame/revive/src/precompiles/builtin/bn128.rs @@ -38,7 +38,7 @@ impl PrimitivePrecompile for Bn128Add { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge(RuntimeCosts::Bn128Add)?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::Bn128Add)?; let p1 = read_point(&input, 0)?; let p2 = read_point(&input, 64)?; @@ -66,7 +66,7 @@ impl PrimitivePrecompile for Bn128Mul { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge(RuntimeCosts::Bn128Mul)?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::Bn128Mul)?; let p = read_point(&input, 0)?; let fr = read_fr(&input, 64)?; @@ -99,12 +99,12 @@ impl PrimitivePrecompile for Bn128Pairing { } let ret_val = if input.is_empty() { - env.gas_meter_mut().charge(RuntimeCosts::Bn128Pairing(0))?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::Bn128Pairing(0))?; U256::one() } else { // (a, b_a, b_b - each 64-byte affine coordinates) let elements = input.len() / 192; - env.gas_meter_mut().charge(RuntimeCosts::Bn128Pairing(elements as u32))?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::Bn128Pairing(elements as u32))?; let mut vals = Vec::new(); for i in 0..elements { diff --git a/substrate/frame/revive/src/precompiles/builtin/ecrecover.rs b/substrate/frame/revive/src/precompiles/builtin/ecrecover.rs index c699c5f656ded..dcce32b7b40a6 100644 --- a/substrate/frame/revive/src/precompiles/builtin/ecrecover.rs +++ b/substrate/frame/revive/src/precompiles/builtin/ecrecover.rs @@ -35,7 +35,7 @@ impl PrimitivePrecompile for EcRecover { i: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge(RuntimeCosts::EcdsaRecovery)?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::EcdsaRecovery)?; let mut input = [0u8; 128]; let len = i.len().min(128); input[..len].copy_from_slice(&i[..len]); diff --git a/substrate/frame/revive/src/precompiles/builtin/identity.rs b/substrate/frame/revive/src/precompiles/builtin/identity.rs index bd0c35d26204d..250742fe8c063 100644 --- a/substrate/frame/revive/src/precompiles/builtin/identity.rs +++ b/substrate/frame/revive/src/precompiles/builtin/identity.rs @@ -35,7 +35,7 @@ impl PrimitivePrecompile for Identity { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge(RuntimeCosts::Identity(input.len() as _))?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::Identity(input.len() as _))?; Ok(input) } } diff --git a/substrate/frame/revive/src/precompiles/builtin/modexp.rs b/substrate/frame/revive/src/precompiles/builtin/modexp.rs index 6d18d29137f2c..1078fc1a31775 100644 --- a/substrate/frame/revive/src/precompiles/builtin/modexp.rs +++ b/substrate/frame/revive/src/precompiles/builtin/modexp.rs @@ -99,7 +99,7 @@ impl PrimitivePrecompile for Modexp { // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to // handle empty base first. let r = if base_len == 0 && mod_len == 0 { - env.gas_meter_mut().charge(RuntimeCosts::Modexp(MIN_GAS_COST))?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::Modexp(MIN_GAS_COST))?; BigUint::zero() } else { @@ -125,7 +125,7 @@ impl PrimitivePrecompile for Modexp { modulus.is_even(), ); - env.gas_meter_mut().charge(RuntimeCosts::Modexp(gas_cost))?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::Modexp(gas_cost))?; if modulus.is_zero() || modulus.is_one() { BigUint::zero() diff --git a/substrate/frame/revive/src/precompiles/builtin/ripemd160.rs b/substrate/frame/revive/src/precompiles/builtin/ripemd160.rs index 0e96d7a332bdc..2652918a542cd 100644 --- a/substrate/frame/revive/src/precompiles/builtin/ripemd160.rs +++ b/substrate/frame/revive/src/precompiles/builtin/ripemd160.rs @@ -36,7 +36,8 @@ impl PrimitivePrecompile for Ripemd160 { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge(RuntimeCosts::Ripemd160(input.len() as _))?; + env.gas_meter_mut() + .charge_weight_token(RuntimeCosts::Ripemd160(input.len() as _))?; let mut ret = [0u8; 32]; ret[12..32].copy_from_slice(&ripemd::Ripemd160::digest(input)); Ok(ret.to_vec()) diff --git a/substrate/frame/revive/src/precompiles/builtin/sha256.rs b/substrate/frame/revive/src/precompiles/builtin/sha256.rs index 64e43bfae334e..52b802bf8a5fb 100644 --- a/substrate/frame/revive/src/precompiles/builtin/sha256.rs +++ b/substrate/frame/revive/src/precompiles/builtin/sha256.rs @@ -35,7 +35,7 @@ impl PrimitivePrecompile for Sha256 { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge(RuntimeCosts::HashSha256(input.len() as _))?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::HashSha256(input.len() as _))?; let data = sp_io::hashing::sha2_256(&input).to_vec(); Ok(data) } diff --git a/substrate/frame/revive/src/precompiles/builtin/storage.rs b/substrate/frame/revive/src/precompiles/builtin/storage.rs index 7a087e668b8b4..a2590031f1cf5 100644 --- a/substrate/frame/revive/src/precompiles/builtin/storage.rs +++ b/substrate/frame/revive/src/precompiles/builtin/storage.rs @@ -68,7 +68,7 @@ impl BuiltinPrecompile for Storage { RuntimeCosts::ClearStorage(len) } }; - let charged = env.gas_meter_mut().charge(costs(max_size))?; + let charged = env.gas_meter_mut().charge_weight_token(costs(max_size))?; let key = decode_key(key.as_bytes_ref(), *isFixedKey) .map_err(|_| Error::Revert("failed decoding key".into()))?; let outcome = if transient { @@ -97,7 +97,7 @@ impl BuiltinPrecompile for Storage { RuntimeCosts::ContainsStorage(len) } }; - let charged = env.gas_meter_mut().charge(costs(max_size))?; + let charged = env.gas_meter_mut().charge_weight_token(costs(max_size))?; let key = decode_key(key.as_bytes_ref(), *isFixedKey) .map_err(|_| Error::Revert("failed decoding key".into()))?; let outcome = if transient { @@ -120,7 +120,7 @@ impl BuiltinPrecompile for Storage { RuntimeCosts::TakeStorage(len) } }; - let charged = env.gas_meter_mut().charge(costs(max_size))?; + let charged = env.gas_meter_mut().charge_weight_token(costs(max_size))?; let key = decode_key(key.as_bytes_ref(), *isFixedKey) .map_err(|_| Error::Revert("failed decoding key".into()))?; let outcome = if transient { diff --git a/substrate/frame/revive/src/precompiles/builtin/system.rs b/substrate/frame/revive/src/precompiles/builtin/system.rs index ac8d6647d26c3..4b4f17e9e30af 100644 --- a/substrate/frame/revive/src/precompiles/builtin/system.rs +++ b/substrate/frame/revive/src/precompiles/builtin/system.rs @@ -47,46 +47,48 @@ impl BuiltinPrecompile for System { ISystemCalls::terminate(_) if env.is_read_only() => Err(crate::Error::::StateChangeDenied.into()), ISystemCalls::hashBlake256(ISystem::hashBlake256Call { input }) => { - env.gas_meter_mut().charge(RuntimeCosts::HashBlake256(input.len() as u32))?; + env.gas_meter_mut() + .charge_weight_token(RuntimeCosts::HashBlake256(input.len() as u32))?; let output = sp_io::hashing::blake2_256(input.as_bytes_ref()); Ok(output.abi_encode()) }, ISystemCalls::hashBlake128(ISystem::hashBlake128Call { input }) => { - env.gas_meter_mut().charge(RuntimeCosts::HashBlake128(input.len() as u32))?; + env.gas_meter_mut() + .charge_weight_token(RuntimeCosts::HashBlake128(input.len() as u32))?; let output = sp_io::hashing::blake2_128(input.as_bytes_ref()); Ok(output.abi_encode()) }, ISystemCalls::toAccountId(ISystem::toAccountIdCall { input }) => { - env.gas_meter_mut().charge(RuntimeCosts::ToAccountId)?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::ToAccountId)?; let account_id = env.to_account_id(&H160::from_slice(input.as_slice())); Ok(account_id.encode().abi_encode()) }, ISystemCalls::callerIsOrigin(ISystem::callerIsOriginCall {}) => { - env.gas_meter_mut().charge(RuntimeCosts::CallerIsOrigin)?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::CallerIsOrigin)?; let is_origin = env.caller_is_origin(true); Ok(is_origin.abi_encode()) }, ISystemCalls::callerIsRoot(ISystem::callerIsRootCall {}) => { - env.gas_meter_mut().charge(RuntimeCosts::CallerIsRoot)?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::CallerIsRoot)?; let is_root = env.caller_is_root(true); Ok(is_root.abi_encode()) }, ISystemCalls::ownCodeHash(ISystem::ownCodeHashCall {}) => { - env.gas_meter_mut().charge(RuntimeCosts::OwnCodeHash)?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::OwnCodeHash)?; let caller = env.caller(); let addr = T::AddressMapper::to_address(caller.account_id()?); let output = env.code_hash(&addr.into()).0.abi_encode(); Ok(output) }, ISystemCalls::minimumBalance(ISystem::minimumBalanceCall {}) => { - env.gas_meter_mut().charge(RuntimeCosts::MinimumBalance)?; + env.gas_meter_mut().charge_weight_token(RuntimeCosts::MinimumBalance)?; let minimum_balance = env.minimum_balance(); Ok(minimum_balance.to_big_endian().abi_encode()) }, ISystemCalls::weightLeft(ISystem::weightLeftCall {}) => { - env.gas_meter_mut().charge(RuntimeCosts::WeightLeft)?; - let ref_time = env.gas_meter().weight_left().ref_time(); - let proof_size = env.gas_meter().weight_left().proof_size(); + env.gas_meter_mut().charge_weight_token(RuntimeCosts::WeightLeft)?; + let ref_time = env.gas_meter().weight_left().unwrap_or_default().ref_time(); + let proof_size = env.gas_meter().weight_left().unwrap_or_default().proof_size(); let res = (ref_time, proof_size); Ok(res.abi_encode()) }, diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 1875d5dc4fc88..e0853bab14a84 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -26,7 +26,7 @@ use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::{ traits::{One, Saturating, Zero}, - DispatchError, RuntimeDebug, + DispatchError, FixedPointNumber, FixedU128, RuntimeDebug, }; /// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and @@ -260,7 +260,7 @@ impl StorageDeposit { impl StorageDeposit where - Balance: Saturating + Ord + Copy, + Balance: frame_support::traits::tokens::Balance + Saturating + Ord + Copy, { /// This is essentially a saturating signed add. pub fn saturating_add(&self, rhs: &Self) -> Self { @@ -310,11 +310,27 @@ where /// # Note /// /// In case of a refund the return value can be larger than `limit`. - pub fn available(&self, limit: &Balance) -> Balance { + pub fn available(&self, limit: &Balance) -> Option { use StorageDeposit::*; match self { - Charge(amount) => limit.saturating_sub(*amount), - Refund(amount) => limit.saturating_add(*amount), + Charge(amount) => limit.checked_sub(amount), + Refund(amount) => Some(limit.saturating_add(*amount)), + } + } + + pub fn negate(&self) -> Self { + use StorageDeposit::*; + match self { + Charge(amount) => Refund(*amount), + Refund(amount) => Charge(*amount), + } + } + + pub fn scale_by_factor(&self, rhs: &FixedU128) -> Self { + use StorageDeposit::*; + match self { + Charge(amount) => Charge(rhs.saturating_mul_int(*amount)), + Refund(amount) => Refund(rhs.saturating_mul_int(*amount)), } } } diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index a46f372f5c23c..b664c18d6a59a 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -20,6 +20,7 @@ use crate::{ address::AddressMapper, exec::{AccountIdOf, Key}, + metering::FrameMeter, tracing::if_tracing, weights::WeightInfo, AccountInfoOf, BalanceOf, BalanceWithDust, Config, DeletionQueue, DeletionQueueCounter, Error, @@ -41,7 +42,7 @@ use sp_runtime::{ DispatchError, RuntimeDebug, }; -use crate::metering::storage::{Diff, NestedMeter}; +use crate::metering::storage::Diff; pub enum AccountIdOrAddress { /// An account that is a contract. @@ -275,7 +276,7 @@ impl ContractInfo { &self, key: &Key, new_value: Option>, - storage_meter: Option<&mut NestedMeter>, + frame_meter: Option<&mut FrameMeter>, take: bool, ) -> Result { log::trace!(target: crate::LOG_TARGET, "contract storage: writing value {:?} for key {:x?}", new_value, key); @@ -285,7 +286,7 @@ impl ContractInfo { t.storage_write(key, old, new_value.as_deref()); }); - self.write_raw(&hashed_key, new_value.as_deref(), storage_meter, take) + self.write_raw(&hashed_key, new_value.as_deref(), frame_meter, take) } /// Update a storage entry into a contract's kv storage. @@ -304,7 +305,7 @@ impl ContractInfo { &self, key: &[u8], new_value: Option<&[u8]>, - storage_meter: Option<&mut NestedMeter>, + frame_meter: Option<&mut FrameMeter>, take: bool, ) -> Result { let child_trie_info = &self.child_trie_info(); @@ -315,7 +316,7 @@ impl ContractInfo { (child::len(child_trie_info, key), None) }; - if let Some(storage_meter) = storage_meter { + if let Some(frame_meter) = frame_meter { let mut diff = Diff::default(); let key_len = key.len() as u32; match (old_len, new_value.as_ref().map(|v| v.len() as u32)) { @@ -335,7 +336,7 @@ impl ContractInfo { }, (None, None) => (), } - storage_meter.charge(&diff); + frame_meter.record_contract_storage_changes(&diff); } match &new_value { diff --git a/substrate/frame/revive/src/test_utils.rs b/substrate/frame/revive/src/test_utils.rs index c658ef26f282c..4f327c6f0ed80 100644 --- a/substrate/frame/revive/src/test_utils.rs +++ b/substrate/frame/revive/src/test_utils.rs @@ -73,6 +73,7 @@ pub const EVE_ADDR: H160 = H160(hex!("e21eecd6e51cbcda5b0c5207ae87e605839e70ef") pub const EVE_FALLBACK: AccountId32 = ee_extend(EVE_ADDR.0); pub const WEIGHT_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); +pub const ETH_GAS_LIMIT: u64 = 1_000_000_000_000; pub fn deposit_limit() -> BalanceOf { 10_000_000u32.into() diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index bafe978206a02..da8ace4542f56 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -15,11 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{deposit_limit, WEIGHT_LIMIT}; +use super::{deposit_limit, ETH_GAS_LIMIT, WEIGHT_LIMIT}; use crate::{ - address::AddressMapper, evm::TransactionSigned, AccountIdOf, BalanceOf, Code, Config, - ContractResult, ExecConfig, ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, - U256, + address::AddressMapper, evm::TransactionSigned, metering::TransactionLimits, AccountIdOf, + BalanceOf, Code, Config, ContractResult, ExecConfig, ExecReturnValue, InstantiateReturnValue, + OriginFor, Pallet, Weight, U256, }; use alloc::{vec, vec::Vec}; use frame_support::pallet_prelude::DispatchResultWithPostInfo; @@ -129,8 +129,7 @@ builder!( bare_instantiate( origin: OriginFor, evm_value: U256, - weight_limit: Weight, - storage_deposit_limit: BalanceOf, + transaction_limits: TransactionLimits, code: Code, data: Vec, salt: Option<[u8; 32]>, @@ -171,8 +170,10 @@ builder!( Self { origin, evm_value: Default::default(), - weight_limit: WEIGHT_LIMIT, - storage_deposit_limit: deposit_limit::(), + transaction_limits: TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: deposit_limit::() + }, code, data: vec![], salt: Some([0; 32]), @@ -209,8 +210,7 @@ builder!( origin: OriginFor, dest: H160, evm_value: U256, - weight_limit: Weight, - storage_deposit_limit: BalanceOf, + transaction_limits: TransactionLimits, data: Vec, exec_config: ExecConfig, ) -> ContractResult>; @@ -232,8 +232,10 @@ builder!( origin, dest, evm_value: Default::default(), - weight_limit: WEIGHT_LIMIT, - storage_deposit_limit: deposit_limit::(), + transaction_limits: TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: deposit_limit::() + }, data: vec![], exec_config: ExecConfig::new_substrate_tx(), } @@ -246,6 +248,7 @@ builder!( dest: H160, value: U256, weight_limit: Weight, + eth_gas_limit: U256, data: Vec, transaction_encoded: Vec, effective_gas_price: U256, @@ -259,6 +262,7 @@ builder!( dest, value: 0u32.into(), weight_limit: WEIGHT_LIMIT, + eth_gas_limit: ETH_GAS_LIMIT.into(), data: vec![], transaction_encoded: TransactionSigned::TransactionLegacySigned(Default::default()).signed_payload(), effective_gas_price: 0u32.into(), diff --git a/substrate/frame/revive/src/tests/precompiles.rs b/substrate/frame/revive/src/tests/precompiles.rs index dbe94f5f60581..4394973ea547d 100644 --- a/substrate/frame/revive/src/tests/precompiles.rs +++ b/substrate/frame/revive/src/tests/precompiles.rs @@ -90,7 +90,7 @@ impl Precompile for NoInfo { INoInfoCalls::errors(INoInfo::errorsCall {}) => Err(Error::Error(DispatchError::Other("precompile failed").into())), INoInfoCalls::consumeMaxGas(INoInfo::consumeMaxGasCall {}) => { - env.gas_meter_mut().charge(MaxGasToken)?; + env.gas_meter_mut().charge_weight_token(MaxGasToken)?; Ok(Vec::new()) }, INoInfoCalls::callRuntime(INoInfo::callRuntimeCall { call }) => { @@ -109,7 +109,7 @@ impl Precompile for NoInfo { }, INoInfoCalls::passData(INoInfo::passDataCall { inputLen }) => { env.call( - &CallResources::Precise { weight: Weight::MAX, deposit_limit: U256::MAX }, + &CallResources::from_weight_and_deposit(Weight::MAX, U256::MAX), &env.address(), 0.into(), vec![42; *inputLen as usize], diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index d93806d564d4c..9525e0887b4df 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -24,7 +24,7 @@ use crate::{ evm::{interpreter::Halt, util::as_usize_or_halt, Interpreter}, Ext, RuntimeCosts, }, - Code, Error, Pallet, Weight, H160, LOG_TARGET, U256, + Code, Error, Pallet, H160, LOG_TARGET, U256, }; use alloc::{vec, vec::Vec}; pub use call_helpers::{charge_call_gas, get_memory_in_and_out_ranges}; @@ -73,8 +73,7 @@ pub fn create( }; let call_result = interpreter.ext.instantiate( - Weight::from_parts(u64::MAX, u64::MAX), - U256::MAX, + &CallResources::NoLimits, Code::Upload(code), value, vec![], @@ -191,17 +190,18 @@ fn run_call<'a, E: Ext>( ) -> ControlFlow { let call_result = match scheme { CallScheme::Call | CallScheme::StaticCall => interpreter.ext.call( - &CallResources::Ethereum(gas_limit), + &CallResources::from_ethereum_gas(gas_limit), &callee, value, input, true, scheme.is_static_call(), ), - CallScheme::DelegateCall => - interpreter - .ext - .delegate_call(&CallResources::Ethereum(gas_limit), callee, input), + CallScheme::DelegateCall => interpreter.ext.delegate_call( + &CallResources::from_ethereum_gas(gas_limit), + callee, + input, + ), CallScheme::CallCode => { unreachable!() }, diff --git a/substrate/frame/revive/src/vm/evm/instructions/control.rs b/substrate/frame/revive/src/vm/evm/instructions/control.rs index 3dab142b57ffa..a95768ae5221f 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/control.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/control.rs @@ -127,6 +127,6 @@ pub fn stop(_interpreter: &mut Interpreter) -> ControlFlow { /// Invalid opcode. This opcode halts the execution. pub fn invalid(interpreter: &mut Interpreter) -> ControlFlow { - interpreter.ext.gas_meter_mut().consume_all(); + interpreter.ext.gas_meter_mut().consume_all_weight(); ControlFlow::Break(Error::::InvalidInstruction.into()) } diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index b14ab38b90c62..bdbf86fa701c0 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -27,10 +27,7 @@ pub use runtime_costs::RuntimeCosts; use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, frame_support::{ensure, error::BadOrigin, traits::tokens::Restriction}, - metering::{ - storage::NestedMeter, - weight::{Token, WeightMeter}, - }, + metering::{weight::Token, ResourceMeter, State}, weights::WeightInfo, AccountIdOf, BalanceOf, CodeInfoOf, CodeRemoved, Config, Error, ExecConfig, ExecError, HoldReason, Pallet, PristineCode, StorageDeposit, Weight, LOG_TARGET, @@ -193,10 +190,10 @@ impl ContractBlob { } /// Puts the module blob into storage, and returns the deposit collected for the storage. - pub fn store_code( + pub fn store_code( &mut self, - exec_config: &ExecConfig, - storage_meter: Option<&mut NestedMeter>, + exec_config: &ExecConfig, + meter: &mut ResourceMeter, ) -> Result, DispatchError> { let code_hash = *self.code_hash(); ensure!(code_hash != H256::zero(), >::CodeNotFound); @@ -224,9 +221,7 @@ impl ContractBlob { >::StorageDepositNotEnoughFunds })?; - if let Some(meter) = storage_meter { - meter.record_charge(&StorageDeposit::Charge(deposit))?; - } + meter.charge_deposit(&StorageDeposit::Charge(deposit))?; >::insert(code_hash, &self.code.to_vec()); *stored_code_info = Some(self.code_info.clone()); @@ -325,12 +320,12 @@ impl CodeInfo { } impl Executable for ContractBlob { - fn from_storage( + fn from_storage( code_hash: H256, - weight_meter: &mut WeightMeter, + meter: &mut ResourceMeter, ) -> Result { let code_info = >::get(code_hash).ok_or(Error::::CodeNotFound)?; - weight_meter.charge(CodeLoadToken::from_code_info(&code_info))?; + meter.charge_weight_token(CodeLoadToken::from_code_info(&code_info))?; let code = >::get(&code_hash).ok_or(Error::::CodeNotFound)?; Ok(Self { code, code_info, code_hash }) } diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index 2e57b28435ba1..c227714562c88 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -270,7 +270,7 @@ impl fmt::Display for TrapReason { /// a function won't work out. macro_rules! charge_gas { ($runtime:expr, $costs:expr) => {{ - $runtime.ext.gas_meter_mut().charge($costs) + $runtime.ext.gas_meter_mut().charge_weight_token($costs) }}; } @@ -687,7 +687,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { })?; } self.ext.call( - &CallResources::Precise { weight, deposit_limit }, + &CallResources::from_weight_and_deposit(weight, deposit_limit), &callee, value, input_data, @@ -700,7 +700,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { return Err(Error::::InvalidCallFlags.into()); } self.ext.delegate_call( - &CallResources::Precise { weight, deposit_limit }, + &CallResources::from_weight_and_deposit(weight, deposit_limit), callee, input_data, ) @@ -787,8 +787,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { memory.reset_interpreter_cache(); match self.ext.instantiate( - weight, - deposit_limit, + &CallResources::from_weight_and_deposit(weight, deposit_limit), Code::Existing(code_hash), value, input_data, @@ -838,7 +837,7 @@ impl<'a, E: Ext> PreparedCall<'a, E> { break exec_result; } }; - let _ = self.runtime.ext().gas_meter_mut().sync_from_executor(self.instance.gas())?; + self.runtime.ext().gas_meter_mut().sync_from_executor(self.instance.gas())?; exec_result } diff --git a/substrate/frame/revive/src/vm/pvm/env.rs b/substrate/frame/revive/src/vm/pvm/env.rs index 6e491c26ae01e..fde470a4b0edb 100644 --- a/substrate/frame/revive/src/vm/pvm/env.rs +++ b/substrate/frame/revive/src/vm/pvm/env.rs @@ -76,7 +76,7 @@ impl ContractBlob { .ok_or_else(|| >::CodeRejected)? .program_counter(); - let gas_limit_polkavm: polkavm::Gas = runtime.ext().gas_meter_mut().engine_fuel_left()?; + let gas_limit_polkavm: polkavm::Gas = runtime.ext().gas_meter_mut().sync_to_executor(); let mut instance = module.instantiate().map_err(|err| { log::debug!(target: LOG_TARGET, "failed to instantiate polkavm module: {err:?}"); @@ -845,7 +845,7 @@ pub mod env { /// See [`pallet_revive_uapi::HostFn::consume_all_gas`]. #[stable] fn consume_all_gas(&mut self, memory: &mut M) -> Result<(), TrapReason> { - self.ext.gas_meter_mut().consume_all(); + self.ext.gas_meter_mut().consume_all_weight(); Err(TrapReason::Return(ReturnData { flags: ReturnFlags::REVERT.bits(), data: Default::default(), From 43e1bd1cc70d0cb4b30178a724f1270266078831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:46:52 -0300 Subject: [PATCH 04/69] Resolve rebase conflicts --- .github/workflows/cmd.yml | 2 +- .github/workflows/tests-evm.yml | 9 +- Cargo.lock | 2 +- .../assets/asset-hub-westend/src/lib.rs | 2 +- .../assets/common/src/erc20_transactor.rs | 13 +- .../polkadot-omni-node/lib/src/nodes/mod.rs | 2 +- prdoc/pr_10018.prdoc | 23 -- substrate/frame/revive/Cargo.toml | 1 - substrate/frame/revive/rpc/src/lib.rs | 4 +- substrate/frame/revive/src/evm/call.rs | 3 +- substrate/frame/revive/src/evm/runtime.rs | 17 +- substrate/frame/revive/src/exec.rs | 89 ++++-- substrate/frame/revive/src/exec/mock_ext.rs | 20 +- substrate/frame/revive/src/lib.rs | 298 ++++++------------ substrate/frame/revive/src/limits.rs | 3 + substrate/frame/revive/src/metering/mod.rs | 19 +- .../frame/revive/src/metering/storage.rs | 18 +- .../revive/src/precompiles/builtin/system.rs | 3 +- .../frame/revive/src/test_utils/builder.rs | 4 +- .../frame/revive/src/tests/sol/contract.rs | 1 - .../revive/src/vm/evm/instructions/host.rs | 2 +- substrate/frame/revive/src/vm/mod.rs | 2 +- substrate/frame/revive/src/vm/pvm.rs | 2 +- .../frame/revive/src/vm/runtime_costs.rs | 5 +- 24 files changed, 231 insertions(+), 313 deletions(-) delete mode 100644 prdoc/pr_10018.prdoc diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 8d11cdd79c59d..441e0910e1b1e 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -268,7 +268,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: actions/create-github-app-token@db4a51e5b21b6b7c0c69dd6c93c5bf3142490885 # v1.34.1 + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 with: app-id: ${{ secrets.CMD_BOT_APP_ID }} private-key: ${{ secrets.CMD_BOT_APP_KEY }} diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index a958eed12b620..9dcafac106d60 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: paritytech/evm-test-suite - ref: 84e536af80513f87bc16f6c7b7dbf796fe9010ab + ref: c2422cace2fb8a4337fc1c704c49e458c8a79d6b path: evm-test-suite - uses: denoland/setup-deno@v1 @@ -68,13 +68,6 @@ jobs: echo "== Running evm tests ==" START_REVIVE_DEV_NODE=true START_ETH_RPC=true deno task test:evm - - name: Collect tests results - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: evm-test-suite-${{ github.sha }} - path: evm-test-suite/test-logs/matter-labs-tests.log - confirm-required-test-evm-jobs-passed: runs-on: ubuntu-latest name: All test misc tests passed diff --git a/Cargo.lock b/Cargo.lock index 264b63ab653c6..ae938b2016643 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13288,7 +13288,7 @@ dependencies = [ "hex-literal", "humantime-serde", "impl-trait-for-tuples", - "itertools 0.11.0", + "k256", "log", "num-bigint", "num-integer", 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 4083c738767ea..c39a2dab7fbab 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -144,7 +144,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_020_001, + spec_version: 1_020_004, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs b/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs index 7fa2559b26f25..3a7544c1339c8 100644 --- a/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs +++ b/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs @@ -21,6 +21,7 @@ use ethereum_standards::IERC20; use frame_support::traits::{fungible::Inspect, OriginTrait}; use frame_system::pallet_prelude::OriginFor; use pallet_revive::{ + metering::TransactionLimit, precompiles::alloy::{ primitives::{Address, U256 as EU256}, sol_types::SolCall, @@ -126,8 +127,10 @@ where OriginFor::::signed(who.clone()), asset_id, U256::zero(), - weight_limit, - StorageDepositLimit::get(), + TransactionLimit::WeightAndDeposit { + weight_limit, + deposit_limit: StorageDepositLimit::get(), + }, data, ExecConfig::new_substrate_tx(), ); @@ -185,8 +188,10 @@ where OriginFor::::signed(TransfersCheckingAccount::get()), asset_id, U256::zero(), - weight_limit, - StorageDepositLimit::get(), + TransactionLimit::WeightAndDeposit { + weight_limit, + deposit_limit: StorageDepositLimit::get(), + }, data, ExecConfig::new_substrate_tx(), ); diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs b/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs index d66387668bc97..a2a897c378cdc 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs @@ -19,4 +19,4 @@ pub mod aura; /// The current node version for cumulus official binaries, which takes the basic /// SemVer form `..`. It should correspond to the latest /// `polkadot` version of a stable release. -pub const NODE_VERSION: &'static str = "1.20.0"; +pub const NODE_VERSION: &'static str = "1.20.1"; diff --git a/prdoc/pr_10018.prdoc b/prdoc/pr_10018.prdoc deleted file mode 100644 index 6fad1e352df74..0000000000000 --- a/prdoc/pr_10018.prdoc +++ /dev/null @@ -1,23 +0,0 @@ -title: 'pallet_revive: Change EVM call opcodes to respect the gas limit passed' -doc: -- audience: Runtime Dev - description: |- - So far the EVM family of call opcodes did ignore the `gas` argument passed to them. The consequence was that we were not able to limit the resource usage of sub contract calls. This PR changes that. **Gas is now fully functional on the EVM backend.** - - The resources of any sub contract call are now effectively limited. This is both true for `Weight` and storage deposit. The algorithm works in a way that if you pass `x%` of the current `GAS` the the `CALL` opcode the sub call will have `x%` of currently available `Weight` and storage deposit available. This allows the caller to always make sure to execute code after retuning from a sub call. - - ### Changes to the gas meter - - I needed to change the gas meter to track `gas_consumed` instead of `gas_left`. Otherwise it is not possible to know the total amount of gas spent for a call stack that is not unwinded, yet. - - ### Followup - - Implement a new PVM syscall that takes the new unified gas instead of `Weight` and storage deposit limit - - Change resolc to use this new syscall - - Enable the test added here to run on resolc -crates: -- name: pallet-revive - bump: major -- name: pallet-revive-eth-rpc - bump: major -- name: pallet-revive-fixtures - bump: major diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 516c0a6638651..3c655612e490c 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -69,7 +69,6 @@ subxt-signer = { workspace = true, optional = true, features = ["unstable-eth"] alloy-consensus = { workspace = true, default-features = true } array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } -itertools = { workspace = true } pretty_assertions = { workspace = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 7117a09263687..74aa84f7a47f0 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -172,12 +172,12 @@ impl EthRpcServer for EthRpcServerImpl { let receiver = self.client.tx_notifier().map(|sender| sender.subscribe()); // Submit the transaction - let substrate_hash = self.client.submit(call).await.map_err(|err| { + self.client.submit(call).await.map_err(|err| { log::trace!(target: LOG_TARGET, "send_raw_transaction ethereum_hash: {hash:?} failed: {err:?}"); err })?; - log::debug!(target: LOG_TARGET, "send_raw_transaction ethereum_hash: {hash:?} substrate_hash: {substrate_hash:?}"); + log::debug!(target: LOG_TARGET, "send_raw_transaction with hash: {hash:?}"); // Wait for the transaction to be included in a block if automine is enabled if let Some(mut receiver) = receiver { diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs index 60ae13cef4b99..5a07d32253673 100644 --- a/substrate/frame/revive/src/evm/call.rs +++ b/substrate/frame/revive/src/evm/call.rs @@ -20,7 +20,7 @@ use crate::{ evm::{fees::InfoT, runtime::SetWeightLimit}, extract_code_and_data, BalanceOf, CallOf, Config, GenericTransaction, Pallet, Weight, Zero, - LOG_TARGET, RUNTIME_PALLETS_ADDR, U256, + LOG_TARGET, RUNTIME_PALLETS_ADDR, }; use alloc::vec::Vec; use codec::DecodeLimit; @@ -199,3 +199,4 @@ where })?.saturated_into(); Ok(CallInfo { call, weight_limit, encoded_len, tx_fee, storage_deposit, eth_gas_limit: gas }) +} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 7d71e59f29ece..88158e22f120c 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -314,7 +314,8 @@ pub trait EthExtra { })?; log::debug!(target: LOG_TARGET, "Decoded Ethereum transaction with signer: {signer_addr:?} nonce: {nonce:?}"); - let call_info = create_call::(tx, Some(encoded_len as u32))?; + let call_info = + create_call::(tx, Some((encoded_len as u32, payload.to_vec())), true)?; let storage_credit = ::Currency::withdraw( &signer, call_info.storage_deposit, @@ -342,7 +343,7 @@ pub trait EthExtra { weight_limit={} \ nonce={nonce:?}\ ", - call_info.gas, + call_info.eth_gas_limit, call_info.tx_fee, call_info.storage_deposit, call_info.weight_limit, @@ -517,6 +518,7 @@ mod test { extra, tx, self.dry_run.unwrap().weight_required, + signed_transaction, )) }) } @@ -525,11 +527,18 @@ mod test { #[test] fn check_eth_transact_call_works() { let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - let (expected_encoded_len, call, _, tx, weight_required) = builder.check().unwrap(); + let (expected_encoded_len, call, _, tx, weight_required, signed_transaction) = + builder.check().unwrap(); + let expected_effective_gas_price: u32 = ::NativeToEthRatio::get(); + + match call { + RuntimeCall::Contracts(crate::Call::eth_call:: { + dest, value, - data, weight_limit, eth_gas_limit, + data, + transaction_encoded, effective_gas_price, encoded_len, }) if dest == tx.to.unwrap() && diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 8336462a60642..66b37445934af 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -17,11 +17,7 @@ use crate::{ address::{self, AddressMapper}, - evm::{ - block_storage, - fees::{Combinator, InfoT}, - }, - gas::GasMeter, + evm::block_storage, limits, metering::{ storage, @@ -420,10 +416,10 @@ pub trait PrecompileExt: sealing::Sealed { /// Returns the maximum allowed size of a storage item. fn max_value_size(&self) -> u32; - /// Get an immutable reference to the nested weight meter. + /// Get an immutable reference to the nested resource meter of the frame. fn gas_meter(&self) -> &FrameMeter; - /// Get a mutable reference to the nested weight meter. + /// Get a mutable reference to the nested resource meter of the frame. fn gas_meter_mut(&mut self) -> &mut FrameMeter; /// Recovers ECDSA compressed public key based on signature and message hash. @@ -855,14 +851,21 @@ where ); }); - let result = Self::transfer_from_origin( - &origin, - &origin, - &dest, - value, - transaction_meter, - exec_config, - ); + let result = if let Some(mock_answer) = + exec_config.mock_handler.as_ref().and_then(|handler| { + handler.mock_call(T::AddressMapper::to_address(&dest), &input_data, value) + }) { + Ok(mock_answer) + } else { + Self::transfer_from_origin( + &origin, + &origin, + &dest, + value, + transaction_meter, + exec_config, + ) + }; if_tracing(|t| match result { Ok(ref output) => t.exit_child_span(&output, Weight::zero()), @@ -957,8 +960,16 @@ where input_data: &Vec, ) -> Result)>, ExecError> { origin.ensure_mapped()?; - let Some((first_frame, executable)) = - Self::new_frame(args, value, transaction_meter, &CallResources::NoLimits, false, true)? + let Some((first_frame, executable)) = Self::new_frame( + args, + value, + transaction_meter, + &CallResources::NoLimits, + false, + true, + input_data, + exec_config, + )? else { return Ok(None); }; @@ -1140,9 +1151,16 @@ where let frame = top_frame_mut!(self); let meter = &mut frame.frame_meter; - if let Some((frame, executable)) = - Self::new_frame(frame_args, value_transferred, meter, call_resources, read_only, false)? - { + if let Some((frame, executable)) = Self::new_frame( + frame_args, + value_transferred, + meter, + call_resources, + read_only, + false, + input_data, + self.exec_config, + )? { self.frames.try_push(frame).map_err(|_| Error::::MaxCallDepthReached)?; Ok(Some(executable)) } else { @@ -1521,7 +1539,7 @@ where to: &T::AccountId, value: U256, meter: &mut ResourceMeter, - exec_config: &ExecConfig, + exec_config: &ExecConfig, ) -> DispatchResult { fn transfer_with_dust( from: &AccountIdOf, @@ -1632,7 +1650,7 @@ where to: &T::AccountId, value: U256, meter: &mut ResourceMeter, - exec_config: &ExecConfig, + exec_config: &ExecConfig, ) -> ExecResult { // If the from address is root there is no account to transfer from, and therefore we can't // take any `value` other than 0. @@ -1727,7 +1745,7 @@ where // Only allow storage to be removed if the contract was created in the current tx. let delete_code = self.contracts_created.contains(&contract_account); - self.storage_meter.terminate_absorb( + self.transaction_meter.terminate_absorb( contract_account, contract_info, beneficiary_account.clone(), @@ -1789,17 +1807,20 @@ where } } - fn terminate(&mut self, beneficiary: &H160) -> Result { - if self.is_recursive() { - return Err(Error::::TerminatedWhileReentrant.into()); - } - let frame = self.top_frame_mut(); - if frame.entry_point == ExportedFunction::Constructor { - return Err(Error::::TerminatedInConstructor.into()); - } - let info = frame.terminate(); - let beneficiary_account = T::AddressMapper::to_account_id(beneficiary); - frame.frame_meter.terminate(&info, beneficiary_account); + fn terminate_if_same_tx(&mut self, beneficiary: &H160) -> Result { + let (account_id, contract_address, contract_info) = { + let frame = self.top_frame_mut(); + if frame.entry_point == ExportedFunction::Constructor { + return Err(Error::::TerminatedInConstructor.into()); + } + ( + frame.account_id.clone(), + T::AddressMapper::to_address(&frame.account_id), + frame.contract_info().clone(), + ) + }; + self.contracts_to_be_destroyed + .insert(contract_address, (contract_info.clone(), *beneficiary)); if self.contracts_created.contains(&account_id) { Ok(CodeRemoved::Yes) diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index d193d12268c96..07d72147b6190 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -21,7 +21,7 @@ use crate::{ AccountIdOf, CallResources, ExecError, Ext, Key, Origin, PrecompileExt, PrecompileWithInfoExt, }, - metering::weight::WeightMeter, + metering::{FrameMeter, TransactionLimits, TransactionMeter}, precompiles::Diff, storage::{ContractInfo, WriteOutcome}, transient_storage::TransientStorage, @@ -35,13 +35,19 @@ use sp_runtime::DispatchError; /// Mock implementation of the Ext trait that panics for all methods pub struct MockExt { - weight_meter: WeightMeter, + transaction_meter: TransactionMeter, + frame_meter: FrameMeter, _phantom: PhantomData, } impl MockExt { pub fn new() -> Self { - Self { weight_meter: WeightMeter::new(Weight::MAX), _phantom: PhantomData } + let transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: Balance::max_value(), + }); + let frame_meter: transaction_meter::new_nested(&CallResources::NoLimits); + Self { frame_meter, weight_meter, _phantom: PhantomData } } } @@ -157,12 +163,12 @@ impl PrecompileExt for MockExt { panic!("MockExt::max_value_size") } - fn gas_meter(&self) -> &WeightMeter { - &self.weight_meter + fn gas_meter(&self) -> &FrameMeter { + &self.frame_meter } - fn gas_meter_mut(&mut self) -> &mut WeightMeter { - &mut self.weight_meter + fn gas_meter_mut(&mut self) -> &mut FrameMeter { + &mut self.frame_meter } fn ecdsa_recover( diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index ba30748b06bcb..2079ea7029bbc 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -48,13 +48,11 @@ pub mod weights; use crate::{ evm::{ - block_hash::EthereumBlockBuilderIR, - block_storage, create_call, - fees::{Combinator, InfoT as FeeInfo}, - runtime::SetWeightLimit, - CallTracer, GenericTransaction, PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, + block_hash::EthereumBlockBuilderIR, block_storage, create_call, fees::InfoT as FeeInfo, + runtime::SetWeightLimit, CallTracer, GenericTransaction, PrestateTracer, Trace, Tracer, + TracerType, TYPE_EIP1559, }, - exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, + exec::{AccountIdOf, ExecError, Stack as ExecStack}, metering::{ weight::Token as WeightToken, EthTxInfo, ResourceMeter, State, TransactionLimits, TransactionMeter, @@ -907,7 +905,7 @@ pub mod pallet { let max_immutable_key_size: u64 = T::AccountId::max_encoded_len().try_into().unwrap(); let max_immutable_size: u64 = max_block_weight - .checked_div_per_component(&>::weight( + .checked_div_per_component(&>::weight( &RuntimeCosts::SetImmutableData(limits::IMMUTABLE_BYTES), )) .unwrap() @@ -925,11 +923,11 @@ pub mod pallet { // is not taken into account to simplify calculations, as it does not change much. let max_events_size = max_block_weight .checked_div_per_component( - &(>::weight(&RuntimeCosts::DepositEvent { + &(>::weight(&RuntimeCosts::DepositEvent { num_topic: 0, len: limits::EVENT_BYTES, }) - .saturating_add(>::weight( + .saturating_add(>::weight( &RuntimeCosts::HostFn, ))), ) @@ -1004,48 +1002,19 @@ pub mod pallet { memory_left.saturating_mul(TOTAL_MEMORY_DEVIDER.into()).abs() / 1024 ); - // Validators are configured to be able to use more memory than block builders. This is - // because in addition to `max_runtime_mem` they need to hold additional data in - // memory: PoV in multiple copies (1x encoded + 2x decoded) and all storage which - // includes emitted events. The assumption is that storage/events size - // can be a maximum of half of the validator runtime memory - max_runtime_mem. - let max_block_ref_time = T::BlockWeights::get() - .get(DispatchClass::Normal) - .max_total - .unwrap_or_else(|| T::BlockWeights::get().max_block) - .ref_time(); - let max_payload_size = limits::PAYLOAD_BYTES; - let max_key_size = - Key::try_from_var(alloc::vec![0u8; limits::STORAGE_KEY_BYTES as usize]) - .expect("Key of maximal size shall be created") - .hash() - .len() as u32; - - let max_immutable_key_size = T::AccountId::max_encoded_len() as u32; - let max_immutable_size: u32 = ((max_block_ref_time / - (>::weight(&RuntimeCosts::SetImmutableData( - limits::IMMUTABLE_BYTES, - )) - .ref_time())) - .saturating_mul(limits::IMMUTABLE_BYTES.saturating_add(max_immutable_key_size) as u64)) - .try_into() - .expect("Immutable data size too big"); - // We can use storage to store items using the available block ref_time with the // `set_storage` host function. - let max_storage_size: u32 = ((max_block_ref_time / - (>::weight(&RuntimeCosts::SetStorage { - new_bytes: max_payload_size, - old_bytes: 0, - }) - .ref_time())) - .saturating_mul(max_payload_size.saturating_add(max_key_size) as u64)) - .saturating_add(max_immutable_size.into()) - .try_into() - .expect("Storage size too big"); - - let max_pvf_mem: u32 = T::PVFMemory::get(); - let storage_size_limit = max_pvf_mem.saturating_sub(max_runtime_mem) / 2; + let max_storage_size = max_block_weight + .checked_div_per_component( + &>::weight(&RuntimeCosts::SetStorage { + new_bytes: limits::STORAGE_BYTES, + old_bytes: 0, + }) + .saturating_mul(u64::from(limits::STORAGE_BYTES).saturating_add(max_key_size)), + ) + .unwrap() + .saturating_add(max_immutable_size.into()) + .saturating_add(max_eth_block_builder_bytes.into()); assert!( max_storage_size < storage_size_limit, @@ -1053,26 +1022,8 @@ pub mod pallet { max_storage_size, storage_size_limit ); - - // We can use storage to store events using the available block ref_time with the - // `deposit_event` host function. The overhead of stored events, which is around 100B, - // is not taken into account to simplify calculations, as it does not change much. - let max_events_size: u32 = ((max_block_ref_time / - (>::weight(&RuntimeCosts::DepositEvent { - num_topic: 0, - len: max_payload_size, - }) - .saturating_add(>::weight(&RuntimeCosts::HostFn)) - .ref_time())) - .saturating_mul(max_payload_size as u64)) - .try_into() - .expect("Events size too big"); - max_events_size < storage_size_limit, - "Maximal events size {} exceeds the events limit {}", - max_events_size, - storage_size_limit - ); } + } #[pallet::call] impl Pallet { @@ -1086,6 +1037,7 @@ pub mod pallet { /// /// This call cannot be dispatched directly; attempting to do so will result in a failed /// transaction. It serves as a wrapper for an Ethereum transaction. When submitted, the + /// runtime converts it into a [`sp_runtime::generic::CheckedExtrinsic`] by recovering the /// signer and validating the transaction. #[allow(unused_variables)] #[pallet::call_index(0)] @@ -1263,16 +1215,16 @@ pub mod pallet { /// # Parameters /// /// * `value`: The balance to transfer from the `origin` to the newly created contract. - /// * `gas_limit`: The gas limit enforced when executing the constructor. - /// * `storage_deposit_limit`: The maximum amount of balance that can be charged/reserved - /// from the caller to pay for the storage consumed. + /// * `weight_limit`: The gas limit used to derive the transaction weight for transaction + /// payment + /// * `eth_gas_limit`: The Ethereum gas limit governing the resource usage of the execution /// * `code`: The contract code to deploy in raw bytes. /// * `data`: The input data to pass to the contract constructor. - /// * `salt`: Used for the address derivation. If `Some` is supplied then `CREATE2` - /// semantics are used. If `None` then `CRATE1` is used. /// * `transaction_encoded`: The RLP encoding of the signed Ethereum transaction, /// represented as [crate::evm::TransactionSigned], provided by the Ethereum wallet. This /// is used for building the Ethereum transaction root. + /// * effective_gas_price: the price of a unit of gas + /// * encoded len: the byte code size of the `eth_transact` extrinsic /// /// Calling this dispatchable ensures that the origin's nonce is bumped only once, /// via the `CheckNonce` transaction extension. In contrast, [`Self::instantiate_with_code`] @@ -1311,36 +1263,57 @@ pub mod pallet { let base_info = T::FeeInfo::base_dispatch_info(&mut call); drop(call); - let extra_weight = base_info.total_weight(); - let mut output = Self::bare_instantiate( - origin, - value, - TransactionLimits::EthereumGas { - eth_gas_limit: eth_gas_limit.saturated_into(), - eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), - }, - Code::Upload(code), - data, - None, - ExecConfig::new_eth_tx(effective_gas_price, encoded_len, extra_weight), - ); - if let Ok(retval) = &output.result { - if retval.result.did_revert() { - output.result = Err(>::ContractReverted.into()); + block_storage::with_ethereum_context::(transaction_encoded, || { + let extra_weight = base_info.total_weight(); + let mut output = Self::bare_instantiate( + origin, + value, + TransactionLimits::EthereumGas { + eth_gas_limit: eth_gas_limit.saturated_into(), + eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), + }, + Code::Upload(code), + data, + None, + ExecConfig::new_eth_tx(effective_gas_price, encoded_len, extra_weight), + ); + if let Ok(retval) = &output.result { + if retval.result.did_revert() { + output.result = Err(>::ContractReverted.into()); + } } - } - let result = dispatch_result( - output.result.map(|result| result.result), - output.weight_consumed, - base_info.call_weight, - ); - T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result) + let result = dispatch_result( + output.result.map(|result| result.result), + output.weight_consumed, + base_info.call_weight, + ); + let result = T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result); + (output.weight_consumed, result) + }) } /// Same as [`Self::call`], but intended to be dispatched **only** /// by an EVM transaction through the EVM compatibility layer. + /// + /// # Parameters + /// + /// * `dest`: The Ethereum address of the account to be called + /// * `value`: The balance to transfer from the `origin` to the newly created contract. + /// * `weight_limit`: The gas limit used to derive the transaction weight for transaction + /// payment + /// * `eth_gas_limit`: The Ethereum gas limit governing the resource usage of the execution + /// * `data`: The input data to pass to the contract constructor. + /// * `transaction_encoded`: The RLP encoding of the signed Ethereum transaction, + /// represented as [crate::evm::TransactionSigned], provided by the Ethereum wallet. This + /// is used for building the Ethereum transaction root. + /// * effective_gas_price: the price of a unit of gas + /// * encoded len: the byte code size of the `eth_transact` extrinsic #[pallet::call_index(11)] - #[pallet::weight(::WeightInfo::eth_call(Pallet::::has_dust(*value).into()).saturating_add(*weight_limit))] + #[pallet::weight( + T::WeightInfo::eth_call(Pallet::::has_dust(*value).into()) + .saturating_add(*weight_limit) + .saturating_add(T::WeightInfo::on_finalize_block_per_tx(transaction_encoded.len() as u32)) + )] pub fn eth_call( origin: OriginFor, dest: H160, @@ -1369,27 +1342,30 @@ pub mod pallet { let base_info = T::FeeInfo::base_dispatch_info(&mut call); drop(call); - let extra_weight = base_info.total_weight(); - let mut output = Self::bare_call( - origin, - dest, - value, - TransactionLimits::EthereumGas { - eth_gas_limit: eth_gas_limit.saturated_into(), - eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), - }, - data, - ExecConfig::new_eth_tx(effective_gas_price, encoded_len, extra_weight), - ); + block_storage::with_ethereum_context::(transaction_encoded, || { + let extra_weight = base_info.total_weight(); + let mut output = Self::bare_call( + origin, + dest, + value, + TransactionLimits::EthereumGas { + eth_gas_limit: eth_gas_limit.saturated_into(), + eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), + }, + data, + ExecConfig::new_eth_tx(effective_gas_price, encoded_len, extra_weight), + ); - if let Ok(return_value) = &output.result { - if return_value.did_revert() { - output.result = Err(>::ContractReverted.into()); + if let Ok(return_value) = &output.result { + if return_value.did_revert() { + output.result = Err(>::ContractReverted.into()); + } } - } - let result = - dispatch_result(output.result, output.weight_consumed, base_info.call_weight); - T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result) + let result = + dispatch_result(output.result, output.weight_consumed, base_info.call_weight); + let result = T::FeeInfo::ensure_not_overdrawn(encoded_len, &info, result); + (output.weight_consumed, result) + }) } /// Upload new `code` without instantiating a contract from it. @@ -1548,10 +1524,6 @@ impl Pallet { data: Vec, exec_config: ExecConfig, ) -> ContractResult> { - if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) { - return contract_result; - } - let mut transaction_meter = match TransactionMeter::new(transaction_limits) { Ok(transaction_meter) => transaction_meter, Err(error) => @@ -1618,11 +1590,6 @@ impl Pallet { salt: Option<[u8; 32]>, exec_config: ExecConfig, ) -> ContractResult> { - // Enforce EIP-3607 for top-level signed origins: deny signed contract addresses. - if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) { - return contract_result; - } - let mut transaction_meter = match TransactionMeter::new(transaction_limits) { Ok(transaction_meter) => transaction_meter, Err(error) => @@ -1754,7 +1721,8 @@ impl Pallet { let base_info = T::FeeInfo::base_dispatch_info(&mut call_info.call); let base_weight = base_info.total_weight(); let exec_config = - ExecConfig::new_eth_tx(effective_gas_price, call_info.encoded_len, base_weight); + ExecConfig::new_eth_tx(effective_gas_price, call_info.encoded_len, base_weight) + .with_dry_run(); // emulate transaction behavior let fees = call_info.tx_fee.saturating_add(call_info.storage_deposit); @@ -1928,6 +1896,9 @@ impl Pallet { storage_deposit={:?}\ ", dry_run.weight_required, + max_weight.saturating_sub(total_weight), + call_info.encoded_len, + dry_run.storage_deposit, ); dry_run.eth_gas = eth_gas; @@ -2217,7 +2188,7 @@ impl Pallet { origin: T::AccountId, code: Vec, meter: &mut ResourceMeter, - exec_config: &ExecConfig, + exec_config: &ExecConfig, ) -> Result<(ContractBlob, BalanceOf), DispatchError> { let mut module = ContractBlob::from_pvm_code(code, origin)?; let deposit = module.store_code(exec_config, meter)?; @@ -2248,79 +2219,6 @@ impl Pallet { T::FeeInfo::weight_to_fee(&weight).into() } - /// Ensure the origin has no code deplyoyed if it is a signed origin. - fn ensure_non_contract_if_signed( - origin: &OriginFor, - ) -> Result<(), ContractResult>> { - use crate::exec::is_precompile; - let Ok(who) = ensure_signed(origin.clone()) else { return Ok(()) }; - let address = >::to_address(&who); - - // EIP_1052: precompile can never be used as EOA. - if is_precompile::>(&address) { - log::debug!( - target: crate::LOG_TARGET, - "EIP-3607: reject externally-signed tx from precompile account {:?}", - address - ); - return Err(ContractResult { - result: Err(DispatchError::BadOrigin), - weight_consumed: Weight::default(), - weight_required: Weight::default(), - storage_deposit: Default::default(), - }); - } - - // Deployed code exists when hash is neither zero (no account) nor EMPTY_CODE_HASH - // (account exists but no code). - if >::is_contract(&address) { - log::debug!( - target: crate::LOG_TARGET, - "EIP-3607: reject externally-signed tx from contract account {:?}", - address - ); - return Err(ContractResult { - result: Err(DispatchError::BadOrigin), - weight_consumed: Weight::default(), - weight_required: Weight::default(), - storage_deposit: Default::default(), - }); - } - Ok(()) - } - - /// Pallet account, used to hold funds for contracts upload deposit. - pub fn account_id() -> T::AccountId { - use frame_support::PalletId; - use sp_runtime::traits::AccountIdConversion; - PalletId(*b"py/reviv").into_account_truncating() - } - - /// The address of the validator that produced the current block. - pub fn block_author() -> Option { - use frame_support::traits::FindAuthor; - - let digest = >::digest(); - let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); - - let account_id = T::FindAuthor::find_author(pre_runtime_digests)?; - Some(T::AddressMapper::to_address(&account_id)) - } - - /// Returns the code at `address`. - /// - /// This takes pre-compiles into account. - pub fn code(address: &H160) -> Vec { - use precompiles::{All, Precompiles}; - if let Some(code) = >::code(address.as_fixed_bytes()) { - return code.into() - } - AccountInfo::::load_contract(&address) - .and_then(|contract| >::get(contract.code_hash)) - .map(|code| code.into()) - .unwrap_or_default() - } - /// Transfer a deposit from some account to another. /// /// `from` is usually the transaction origin and `to` a contract or diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 68108846b5856..b99ea119be321 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -48,6 +48,9 @@ pub const CALL_STACK_DEPTH: u32 = 25; /// We set it to the same limit that ethereum has. It is unlikely to change. pub const NUM_EVENT_TOPICS: u32 = 4; +/// Maximum size of events (including topics) and storage values. +pub const PAYLOAD_BYTES: u32 = 416; + /// Maximum size of of the transaction payload /// /// Maximum code size during instantiation taken into account plus some overhead. diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 40fa3f3fb1921..a45ebe902b4de 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -565,9 +565,9 @@ impl TransactionMeter { } pub fn execute_postponed_deposits( - &self, + &mut self, origin: &Origin, - exec_config: &ExecConfig, + exec_config: &ExecConfig, ) -> Result, DispatchError> { if self.deposit_left().is_none() { // Deposit limit exceeded @@ -576,6 +576,17 @@ impl TransactionMeter { self.deposit.execute_postponed_deposits(origin, exec_config) } + + pub fn terminate_absorb( + &mut self, + contract_account: T::AccountId, + contract_info: &mut ContractInfo, + beneficiary: T::AccountId, + delete_code: bool, + ) { + self.deposit + .terminate_absorb(contract_account, contract_info, beneficiary, delete_code); + } } impl FrameMeter { @@ -587,10 +598,6 @@ impl FrameMeter { self.deposit.charge_deposit(contract, amount) } - pub fn terminate(&mut self, info: &ContractInfo, beneficiary: T::AccountId) { - self.deposit.terminate(info, beneficiary); - } - pub fn record_contract_storage_changes(&mut self, diff: &Diff) { self.deposit.charge(diff); } diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index ca35590e6b479..64250a414d56f 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -130,7 +130,7 @@ impl Diff { let info = if let Some(info) = info { info } else { - return bytes_deposit.saturating_add(&items_deposit); + return bytes_deposit.saturating_add(&items_deposit) }; // Refunds are calculated pro rata based on the accumulated storage within the contract @@ -300,9 +300,8 @@ where beneficiary: T::AccountId, delete_code: bool, ) { - use sp_runtime::traits::Bounded; // Create a nested storage meter and terminate the contract's storage. - let mut nested = self.nested(BalanceOf::::max_value()); + let mut nested = self.nested(None); nested.terminate(contract_info, beneficiary.clone(), delete_code); self.absorb(core::mem::take(&mut nested), &contract_account, Some(contract_info)); } @@ -358,7 +357,7 @@ where /// This drops the root meter in order to make sure it is only called when the whole /// execution did finish. pub fn execute_postponed_deposits( - &self, + &mut self, origin: &Origin, exec_config: &ExecConfig, ) -> Result, DispatchError> { @@ -371,7 +370,7 @@ where // Coalesce charges of the same contract. self.charges = { let mut coalesced: Vec> = Vec::with_capacity(self.charges.len()); - for ch in self.charges { + for ch in &self.charges { if let Some(last) = coalesced.last_mut() { if last.contract == ch.contract { // merge amounts (uses Deposit::saturating_add) @@ -384,7 +383,7 @@ where continue; } } - coalesced.push(ch); + coalesced.push(ch.clone()); } coalesced }; @@ -509,6 +508,7 @@ fn terminate( contract: &T::AccountId, beneficiary: &T::AccountId, delete_code: &bool, +) -> Result<(), DispatchError> { fn terminate_inner( contract: &T::AccountId, beneficiary: &T::AccountId, @@ -557,12 +557,6 @@ pub fn terminate_logic_for_benchmark( terminate::(contract, beneficiary, &true) } -mod private { - pub trait Sealed {} - impl Sealed for super::Root {} - impl Sealed for super::Nested {} -} - #[cfg(test)] mod tests { use super::*; diff --git a/substrate/frame/revive/src/precompiles/builtin/system.rs b/substrate/frame/revive/src/precompiles/builtin/system.rs index 4b4f17e9e30af..9ce62b0f98efe 100644 --- a/substrate/frame/revive/src/precompiles/builtin/system.rs +++ b/substrate/frame/revive/src/precompiles/builtin/system.rs @@ -94,7 +94,8 @@ impl BuiltinPrecompile for System { }, ISystemCalls::terminate(ISystem::terminateCall { beneficiary }) => { // no need to adjust gas because this always deletes code - env.gas_meter_mut().charge(RuntimeCosts::Terminate { code_removed: true })?; + env.gas_meter_mut() + .charge_weight_token(RuntimeCosts::Terminate { code_removed: true })?; let h160 = H160::from_slice(beneficiary.as_slice()); env.terminate_caller(&h160)?; Ok(Vec::new()) diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index da8ace4542f56..47924c0664f03 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -276,6 +276,7 @@ builder!( origin: OriginFor, value: U256, gas_limit: Weight, + eth_gas_limit: U256, code: Vec, data: Vec, transaction_encoded: Vec, @@ -288,7 +289,8 @@ builder!( Self { origin, value: 0u32.into(), - gas_limit: GAS_LIMIT, + gas_limit: WEIGHT_LIMIT, + eth_gas_limit: ETH_GAS_LIMIT.into(), code, data: vec![], transaction_encoded: TransactionSigned::Transaction4844Signed(Default::default()).signed_payload(), diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index 183765332ebc8..725b146f919e4 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -33,7 +33,6 @@ use frame_support::{ assert_err, traits::fungible::{Balanced, Mutate}, }; -use itertools::Itertools; use pallet_revive_fixtures::{compile_module_with_type, Callee, Caller, FixtureType}; use pallet_revive_uapi::ReturnFlags; use pretty_assertions::assert_eq; diff --git a/substrate/frame/revive/src/vm/evm/instructions/host.rs b/substrate/frame/revive/src/vm/evm/instructions/host.rs index 5ea07fb7180fe..8de7158bd6958 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/host.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/host.rs @@ -15,8 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{ - gas::Token, limits, + metering::weight::Token, storage::WriteOutcome, vec::Vec, vm::{ diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index bdbf86fa701c0..fd941c158eee0 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -192,7 +192,7 @@ impl ContractBlob { /// Puts the module blob into storage, and returns the deposit collected for the storage. pub fn store_code( &mut self, - exec_config: &ExecConfig, + exec_config: &ExecConfig, meter: &mut ResourceMeter, ) -> Result, DispatchError> { let code_hash = *self.code_hash(); diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index c227714562c88..f5bfe332ab02b 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -834,7 +834,7 @@ impl<'a, E: Ext> PreparedCall<'a, E> { if let Some(exec_result) = self.runtime.handle_interrupt(interrupt, &self.module, &mut self.instance) { - break exec_result; + break exec_result } }; self.runtime.ext().gas_meter_mut().sync_from_executor(self.instance.gas())?; diff --git a/substrate/frame/revive/src/vm/runtime_costs.rs b/substrate/frame/revive/src/vm/runtime_costs.rs index 67e0ef5217792..bd104e981060a 100644 --- a/substrate/frame/revive/src/vm/runtime_costs.rs +++ b/substrate/frame/revive/src/vm/runtime_costs.rs @@ -15,7 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{metering::weight::Token, weights::WeightInfo, Config}; +use crate::{ + limits, metering::weight::Token, weightinfo_extension::OnFinalizeBlockParts, + weights::WeightInfo, Config, +}; use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; /// Current approximation of the gas/s consumption considering From ef060f5e5a20587ba16969a0f2b3dc036aa9582f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:19:47 -0300 Subject: [PATCH 05/69] Fix conflicts and existing tests --- Cargo.lock | 1 + .../assets/common/src/erc20_transactor.rs | 7 +- substrate/frame/assets/precompiles/src/lib.rs | 2 +- substrate/frame/revive/Cargo.toml | 1 + substrate/frame/revive/rpc/src/example.rs | 1 - substrate/frame/revive/src/benchmarking.rs | 8 +- substrate/frame/revive/src/call_builder.rs | 23 +- substrate/frame/revive/src/evm/fees.rs | 16 + substrate/frame/revive/src/evm/runtime.rs | 10 +- substrate/frame/revive/src/exec.rs | 9 +- substrate/frame/revive/src/exec/mock_ext.rs | 20 +- substrate/frame/revive/src/exec/tests.rs | 413 ++++++++---------- substrate/frame/revive/src/impl_fungibles.rs | 7 +- substrate/frame/revive/src/lib.rs | 13 +- substrate/frame/revive/src/metering/mod.rs | 17 +- .../frame/revive/src/metering/storage.rs | 67 ++- substrate/frame/revive/src/metering/weight.rs | 39 +- substrate/frame/revive/src/primitives.rs | 12 + substrate/frame/revive/src/test_utils.rs | 2 +- substrate/frame/revive/src/tests/pvm.rs | 85 +++- .../frame/revive/src/tests/sol/contract.rs | 19 +- .../frame/revive/src/tests/sol/control.rs | 10 +- substrate/frame/revive/src/tests/sol/host.rs | 6 +- .../frame/revive/src/tests/sol/system.rs | 13 +- 24 files changed, 456 insertions(+), 345 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae938b2016643..1e9abbd135661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13288,6 +13288,7 @@ dependencies = [ "hex-literal", "humantime-serde", "impl-trait-for-tuples", + "itertools 0.11.0", "k256", "log", "num-bigint", diff --git a/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs b/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs index 3a7544c1339c8..e766f903cce37 100644 --- a/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs +++ b/cumulus/parachains/runtimes/assets/common/src/erc20_transactor.rs @@ -21,12 +21,11 @@ use ethereum_standards::IERC20; use frame_support::traits::{fungible::Inspect, OriginTrait}; use frame_system::pallet_prelude::OriginFor; use pallet_revive::{ - metering::TransactionLimit, precompiles::alloy::{ primitives::{Address, U256 as EU256}, sol_types::SolCall, }, - AddressMapper, ContractResult, ExecConfig, MomentOf, + AddressMapper, ContractResult, ExecConfig, MomentOf, TransactionLimits, }; use sp_core::{Get, H160, H256, U256}; use sp_runtime::Weight; @@ -127,7 +126,7 @@ where OriginFor::::signed(who.clone()), asset_id, U256::zero(), - TransactionLimit::WeightAndDeposit { + TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit: StorageDepositLimit::get(), }, @@ -188,7 +187,7 @@ where OriginFor::::signed(TransfersCheckingAccount::get()), asset_id, U256::zero(), - TransactionLimit::WeightAndDeposit { + TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit: StorageDepositLimit::get(), }, diff --git a/substrate/frame/assets/precompiles/src/lib.rs b/substrate/frame/assets/precompiles/src/lib.rs index 17cdd49fea30d..c39990013a2ec 100644 --- a/substrate/frame/assets/precompiles/src/lib.rs +++ b/substrate/frame/assets/precompiles/src/lib.rs @@ -167,7 +167,7 @@ where fn deposit_event(env: &mut impl Ext, event: IERC20Events) -> Result<(), Error> { let (topics, data) = event.into_log_data().split(); let topics = topics.into_iter().map(|v| H256(v.0)).collect::>(); - env.gas_meter_mut().charge(RuntimeCosts::DepositEvent { + env.gas_meter_mut().charge_weight_token(RuntimeCosts::DepositEvent { num_topic: topics.len() as u32, len: topics.len() as u32, })?; diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 3c655612e490c..516c0a6638651 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -69,6 +69,7 @@ subxt-signer = { workspace = true, optional = true, features = ["unstable-eth"] alloy-consensus = { workspace = true, default-features = true } array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } +itertools = { workspace = true } pretty_assertions = { workspace = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index d88bdb0d9919d..dbe0dcf2d6332 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -176,7 +176,6 @@ impl TransactionBuilder { .await .with_context(|| "Failed to fetch gas estimate")?; - println!("Gas estimate: {gas:?}"); let mut unsigned_tx = TransactionLegacyUnsigned { gas, nonce, diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index 65d275f41b50f..ae358283f3cbd 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -316,6 +316,7 @@ mod benchmarks { origin, evm_value, Weight::MAX, + U256::MAX, code, input, TransactionSigned::default().signed_payload(), @@ -476,6 +477,7 @@ mod benchmarks { instance.address, evm_value, Weight::MAX, + U256::MAX, data, TransactionSigned::default().signed_payload(), 0u32.into(), @@ -812,7 +814,7 @@ mod benchmarks { let mut call_setup = CallSetup::::default(); let (mut ext, _) = call_setup.ext(); - let weight_left_before = ext.gas_meter().weight_left(); + let weight_left_before = ext.gas_meter().weight_left().unwrap(); let result; #[block] { @@ -822,7 +824,7 @@ mod benchmarks { input_bytes, ); } - let weight_left_after = ext.gas_meter().weight_left(); + let weight_left_after = ext.gas_meter().weight_left().unwrap(); assert_ne!(weight_left_after.ref_time(), 0); assert!(weight_left_before.ref_time() > weight_left_after.ref_time()); @@ -1260,7 +1262,7 @@ mod benchmarks { let result; #[block] { - result = crate::storage::meter::terminate_logic_for_benchmark::( + result = crate::metering::storage::terminate_logic_for_benchmark::( &instance.account_id, &beneficiary, ); diff --git a/substrate/frame/revive/src/call_builder.rs b/substrate/frame/revive/src/call_builder.rs index ee054e3757961..71a5b868fc8a0 100644 --- a/substrate/frame/revive/src/call_builder.rs +++ b/substrate/frame/revive/src/call_builder.rs @@ -29,7 +29,7 @@ use crate::{ address::AddressMapper, exec::{ExportedFunction, Key, PrecompileExt, Stack}, limits, - metering::{storage::Meter as StorageMeter, weight::WeightMeter}, + metering::{TransactionLimits, TransactionMeter}, transient_storage::MeterEntry, vm::pvm::{PreparedCall, Runtime}, AccountInfo, BalanceOf, BalanceWithDust, Code, CodeInfoOf, Config, ContractBlob, ContractInfo, @@ -50,8 +50,7 @@ pub struct CallSetup { contract: Contract, dest: T::AccountId, origin: Origin, - weight_meter: WeightMeter, - storage_meter: StorageMeter, + transaction_meter: TransactionMeter, value: BalanceOf, data: Vec, transient_storage_size: u32, @@ -77,8 +76,6 @@ where let dest = contract.account_id.clone(); let origin = Origin::from_account_id(contract.caller.clone()); - let storage_meter = StorageMeter::new(default_deposit_limit::()); - #[cfg(feature = "runtime-benchmarks")] { // Whitelist contract account, as it is already accounted for in the call benchmark @@ -100,8 +97,11 @@ where contract, dest, origin, - weight_meter: WeightMeter::new(Weight::MAX), - storage_meter, + transaction_meter: TransactionMeter::new(TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: default_deposit_limit::(), + }) + .unwrap(), value: 0u32.into(), data: vec![], transient_storage_size: 0, @@ -111,7 +111,11 @@ where /// Set the meter's storage deposit limit. pub fn set_storage_deposit_limit(&mut self, balance: BalanceOf) { - self.storage_meter = StorageMeter::new(balance); + self.transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: balance, + }) + .unwrap(); } /// Set the call's origin. @@ -149,8 +153,7 @@ where let mut ext = StackExt::bench_new_call( T::AddressMapper::to_address(&self.dest), self.origin.clone(), - &mut self.weight_meter, - &mut self.storage_meter, + &mut self.transaction_meter, self.value, &self.exec_config, ); diff --git a/substrate/frame/revive/src/evm/fees.rs b/substrate/frame/revive/src/evm/fees.rs index 2a4ec341fb7d5..8fd5cdf96af6d 100644 --- a/substrate/frame/revive/src/evm/fees.rs +++ b/substrate/frame/revive/src/evm/fees.rs @@ -134,6 +134,11 @@ pub trait InfoT: seal::Sealed { Zero::zero() } + /// Convert a weight to an unadjusted fee using an average instead of maximum. + fn weight_to_fee_average(_weight: &Weight) -> BalanceOf { + Zero::zero() + } + /// Convert an unadjusted fee back to a weight. fn fee_to_weight(_fee: BalanceOf) -> Weight { Zero::zero() @@ -302,6 +307,17 @@ where ::WeightToFee::weight_to_fee(weight) } + // Convert a weight to an unadjusted fee using an average instead of maximum. + fn weight_to_fee_average(weight: &Weight) -> BalanceOf { + let ref_time_part = ::WeightToFee::REF_TIME_TO_FEE + .saturating_mul_int(weight.ref_time()); + let proof_size_part = ::WeightToFee::proof_size_to_fee() + .saturating_mul_int(weight.proof_size()); + + // saturated addition not required here but better to be defensive + ((ref_time_part >> 1).saturating_add(proof_size_part >> 1)).saturated_into() + } + /// Convert an unadjusted fee back to a weight. fn fee_to_weight(fee: BalanceOf) -> Weight { let ref_time = ::WeightToFee::REF_TIME_TO_FEE diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 88158e22f120c..e15a4f6480472 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -536,11 +536,11 @@ mod test { dest, value, weight_limit, - eth_gas_limit, data, transaction_encoded, effective_gas_price, encoded_len, + .. }) if dest == tx.to.unwrap() && value == tx.value.unwrap_or_default().as_u64().into() && data == tx.input.to_vec() && @@ -565,19 +565,21 @@ mod test { expected_code.clone(), expected_data.clone(), ); - let (expected_encoded_len, call, _, tx, weight_required) = builder.check().unwrap(); + let (expected_encoded_len, call, _, tx, weight_required, signed_transaction) = + builder.check().unwrap(); let expected_effective_gas_price: u32 = ::NativeToEthRatio::get(); let expected_value = tx.value.unwrap_or_default().as_u64().into(); match call { RuntimeCall::Contracts(crate::Call::eth_instantiate_with_code:: { value, + weight_limit, code, data, - weight_limit, - eth_gas_limit, + transaction_encoded, effective_gas_price, encoded_len, + .. }) if value == expected_value && code == expected_code && data == expected_data && diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 66b37445934af..a0284481a71bb 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -49,7 +49,7 @@ use frame_support::{ Time, }, weights::Weight, - Blake2_128Concat, BoundedVec, StorageHasher, + Blake2_128Concat, BoundedVec, DebugNoBound, StorageHasher, }; use frame_system::{ pallet_prelude::{BlockNumberFor, OriginFor}, @@ -196,6 +196,7 @@ impl Origin { /// Argument passed by a contact to describe the amount of resources allocated to a cross contact /// call. +#[derive(DebugNoBound)] pub enum CallResources { /// Resources are not limited NoLimits, @@ -924,8 +925,7 @@ where pub fn bench_new_call( dest: H160, origin: Origin, - weight_meter: &'a mut WeightMeter, - storage_meter: &'a mut storage::Meter, + transaction_meter: &'a mut TransactionMeter, value: BalanceOf, exec_config: &'a ExecConfig, ) -> (Self, E) { @@ -936,8 +936,7 @@ where delegated_call: None, }, origin, - weight_meter, - storage_meter, + transaction_meter, value.into(), exec_config, &Default::default(), diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index 07d72147b6190..d93180f4bd6bf 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -25,17 +25,17 @@ use crate::{ precompiles::Diff, storage::{ContractInfo, WriteOutcome}, transient_storage::TransientStorage, - Code, CodeRemoved, Config, ExecReturnValue, ImmutableData, + BalanceOf, Code, CodeRemoved, Config, ExecReturnValue, ImmutableData, }; use alloc::vec::Vec; use core::marker::PhantomData; use frame_support::weights::Weight; +use num_traits::Bounded; use sp_core::{H160, H256, U256}; use sp_runtime::DispatchError; /// Mock implementation of the Ext trait that panics for all methods pub struct MockExt { - transaction_meter: TransactionMeter, frame_meter: FrameMeter, _phantom: PhantomData, } @@ -44,10 +44,11 @@ impl MockExt { pub fn new() -> Self { let transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit { weight_limit: Weight::MAX, - deposit_limit: Balance::max_value(), - }); - let frame_meter: transaction_meter::new_nested(&CallResources::NoLimits); - Self { frame_meter, weight_meter, _phantom: PhantomData } + deposit_limit: BalanceOf::::max_value(), + }) + .unwrap(); + let frame_meter = transaction_meter.new_nested(&CallResources::NoLimits).unwrap(); + Self { frame_meter, _phantom: PhantomData } } } @@ -56,7 +57,7 @@ impl PrecompileExt for MockExt { fn call( &mut self, - _call_resources: &CallResources, + _call_resources: &CallResources, _to: &H160, _value: U256, _input_data: Vec, @@ -256,8 +257,7 @@ impl PrecompileExt for MockExt { impl PrecompileWithInfoExt for MockExt { fn instantiate( &mut self, - _gas_limit: Weight, - _deposit_limit: U256, + _call_resources: &CallResources, _code: Code, _value: U256, _input_data: Vec, @@ -270,7 +270,7 @@ impl PrecompileWithInfoExt for MockExt { impl Ext for MockExt { fn delegate_call( &mut self, - _call_resources: &CallResources, + _call_resources: &CallResources, _address: H160, _input_data: Vec, ) -> Result<(), ExecError> { diff --git a/substrate/frame/revive/src/exec/tests.rs b/substrate/frame/revive/src/exec/tests.rs index 3c899cdbc3028..3aae157f10611 100644 --- a/substrate/frame/revive/src/exec/tests.rs +++ b/substrate/frame/revive/src/exec/tests.rs @@ -24,7 +24,7 @@ use super::*; use crate::{ exec::ExportedFunction::*, - metering::{storage::Meter as StorageMeter, weight::WeightMeter}, + metering::TransactionMeter, test_utils::*, tests::{ test_utils::{get_balance, place_contract, set_balance}, @@ -141,7 +141,7 @@ impl MockLoader { impl Executable for MockExecutable { fn from_storage( code_hash: H256, - _meter: &mut ResourceMeter, + _meter: &mut ResourceMeter, ) -> Result { Loader::mutate(|loader| { loader.map.get(&code_hash).cloned().ok_or(Error::::CodeNotFound.into()) @@ -207,7 +207,8 @@ fn it_works() { } let value = 0; - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); + let exec_ch = MockLoader::insert(Call, |_ctx, _executable| { TestData::mutate(|data| data.push(1)); exec_success() @@ -215,14 +216,12 @@ fn it_works() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, exec_ch); - let mut storage_meter = StorageMeter::new(0); assert_matches!( MockStack::run_call( Origin::from_account_id(ALICE), BOB_ADDR, - &mut weight_meter, - &mut storage_meter, + &mut meter, value.into(), vec![], &ExecConfig::new_substrate_tx(), @@ -244,14 +243,15 @@ fn transfer_works() { let value = 55; let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(u64::MAX); + let mut meter = + TransactionMeter::::new_from_limits(Default::default(), u64::MAX).unwrap(); MockStack::transfer( &origin, &ALICE, &BOB, Pallet::::convert_native_to_evm(value), - &mut storage_meter, + &mut meter, &ExecConfig::new_substrate_tx(), ) .unwrap(); @@ -261,8 +261,11 @@ fn transfer_works() { assert_eq!(get_balance(&ALICE), 100 - value - min_balance); assert_eq!(get_balance(&BOB), min_balance + value); assert_eq!( - storage_meter - .try_into_deposit(&Origin::from_account_id(ALICE), &ExecConfig::new_substrate_tx()) + meter + .execute_postponed_deposits( + &Origin::from_account_id(ALICE), + &ExecConfig::new_substrate_tx() + ) .unwrap(), StorageDeposit::Charge(min_balance) ); @@ -278,7 +281,8 @@ fn transfer_to_nonexistent_account_works() { let ed = ::Currency::minimum_balance(); let value = 1024; let evm_value = Pallet::::convert_native_to_evm(value); - let mut storage_meter = StorageMeter::new(u64::MAX); + let mut meter = + TransactionMeter::::new_from_limits(Default::default(), u64::MAX).unwrap(); // Transfers to nonexistent accounts should work set_balance(&ALICE, ed * 2); @@ -289,7 +293,7 @@ fn transfer_to_nonexistent_account_works() { &BOB, &CHARLIE, evm_value, - &mut storage_meter, + &mut meter, &ExecConfig::new_substrate_tx(), )); assert_eq!(get_balance(&ALICE), ed); @@ -305,7 +309,7 @@ fn transfer_to_nonexistent_account_works() { &BOB, &DJANGO, evm_value, - &mut storage_meter, + &mut meter, &ExecConfig::new_substrate_tx(), ), >::StorageDepositNotEnoughFunds, @@ -320,7 +324,7 @@ fn transfer_to_nonexistent_account_works() { &BOB, &EVE, evm_value, - &mut storage_meter, + &mut meter, &ExecConfig::new_substrate_tx(), ), >::TransferFailed @@ -345,13 +349,12 @@ fn correct_transfer_on_call() { set_balance(&ALICE, 100); let balance = get_balance(&BOB_FALLBACK); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let _ = MockStack::run_call( origin.clone(), BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, evm_value.as_u64().into(), vec![], &ExecConfig::new_substrate_tx(), @@ -385,13 +388,12 @@ fn correct_transfer_on_delegate_call() { set_balance(&ALICE, 100); let balance = get_balance(&BOB_FALLBACK); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, evm_value.as_u64().into(), vec![], &ExecConfig::new_substrate_tx(), @@ -418,14 +420,13 @@ fn delegate_call_missing_contract() { set_balance(&ALICE, 100); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); // contract code missing should still succeed to mimic EVM behavior. assert_ok!(MockStack::run_call( origin.clone(), BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -436,8 +437,7 @@ fn delegate_call_missing_contract() { assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -459,13 +459,12 @@ fn changes_are_reverted_on_failing_call() { set_balance(&ALICE, 100); let balance = get_balance(&BOB); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let output = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, 55u64.into(), vec![], &ExecConfig::new_substrate_tx(), @@ -489,14 +488,15 @@ fn balance_too_low() { let ed = ::Currency::minimum_balance(); set_balance(&ALICE, ed * 2); set_balance(&from, ed + 99); - let mut storage_meter = StorageMeter::new(u64::MAX); + let mut meter = + TransactionMeter::::new_from_limits(Default::default(), u64::MAX).unwrap(); let result = MockStack::transfer( &Origin::from_account_id(ALICE), &from, &dest, Pallet::::convert_native_to_evm(100u64).as_u64().into(), - &mut storage_meter, + &mut meter, &ExecConfig::new_substrate_tx(), ); @@ -517,14 +517,13 @@ fn output_is_returned_on_success() { ExtBuilder::default().build().execute_with(|| { let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); place_contract(&BOB, return_ch); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -547,13 +546,12 @@ fn output_is_returned_on_failure() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, return_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -576,13 +574,12 @@ fn input_data_to_call() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, input_data_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![1, 2, 3, 4], &ExecConfig::new_substrate_tx(), @@ -604,17 +601,17 @@ fn input_data_to_instantiate() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); - let executable = - MockExecutable::from_storage(input_data_ch, &mut weight_meter).unwrap(); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); + + let executable = MockExecutable::from_storage(input_data_ch, &mut meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); - let mut storage_meter = StorageMeter::new(deposit_limit::()); let result = MockStack::run_instantiate( ALICE, executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, min_balance.into(), vec![1, 2, 3, 4], Some(&[0; 32]), @@ -655,13 +652,12 @@ fn max_depth() { set_balance(&BOB, 1); place_contract(&BOB, recurse_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, value.into(), vec![], &ExecConfig::new_substrate_tx(), @@ -710,13 +706,12 @@ fn caller_returns_proper_values() { place_contract(&BOB, bob_ch); place_contract(&CHARLIE, charlie_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -766,13 +761,12 @@ fn origin_returns_proper_values() { place_contract(&BOB, bob_ch); place_contract(&CHARLIE, charlie_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -805,12 +799,11 @@ fn to_account_id_returns_proper_values() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, bob_code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -841,13 +834,12 @@ fn code_hash_returns_proper_values() { ); place_contract(&BOB, bob_code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); // ALICE (not contract) -> BOB (contract) let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -867,13 +859,12 @@ fn own_code_hash_returns_proper_values() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, bob_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); // ALICE (not contract) -> BOB (contract) let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -903,13 +894,12 @@ fn caller_is_origin_returns_proper_values() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); // ALICE -> BOB (caller is origin) -> CHARLIE (caller is not origin) let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -929,13 +919,12 @@ fn root_caller_succeeds() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_bob); let origin = Origin::Root; - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); // root -> BOB (caller is root) let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -955,13 +944,12 @@ fn root_caller_does_not_succeed_when_value_not_zero() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_bob); let origin = Origin::Root; - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); // root -> BOB (caller is root) let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, 1u64.into(), vec![0], &ExecConfig::new_substrate_tx(), @@ -991,13 +979,12 @@ fn root_caller_succeeds_with_consecutive_calls() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::Root; - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); // root -> BOB (caller is root) -> CHARLIE (caller is not root) let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -1029,13 +1016,12 @@ fn address_returns_proper_values() { place_contract(&BOB, bob_ch); place_contract(&CHARLIE, charlie_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -1050,16 +1036,14 @@ fn refuse_instantiate_with_value_below_existential_deposit() { let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success()); ExtBuilder::default().existential_deposit(15).build().execute_with(|| { - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); - let executable = MockExecutable::from_storage(dummy_ch, &mut weight_meter).unwrap(); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); + let executable = MockExecutable::from_storage(dummy_ch, &mut meter).unwrap(); assert_matches!( MockStack::run_instantiate( ALICE, executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, U256::zero(), // <- zero value vec![], Some(&[0; 32]), @@ -1082,17 +1066,16 @@ fn instantiation_work_with_success_output() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); - let executable = MockExecutable::from_storage(dummy_ch, &mut weight_meter).unwrap(); set_balance(&ALICE, min_balance * 1000); - let mut storage_meter = StorageMeter::new(min_balance * 100); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, min_balance * 100).unwrap(); + let executable = MockExecutable::from_storage(dummy_ch, &mut meter).unwrap(); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( ALICE, executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, Pallet::::convert_native_to_evm(min_balance), vec![], Some(&[0 ;32]), @@ -1133,17 +1116,16 @@ fn instantiation_fails_with_failing_output() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); - let executable = MockExecutable::from_storage(dummy_ch, &mut weight_meter).unwrap(); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, min_balance * 100).unwrap(); + let executable = MockExecutable::from_storage(dummy_ch, &mut meter).unwrap(); set_balance(&ALICE, min_balance * 1000); - let mut storage_meter = StorageMeter::new(min_balance * 100); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( ALICE, executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, Pallet::::convert_native_to_evm(min_balance), vec![], Some(&[0; 32]), @@ -1175,7 +1157,7 @@ fn instantiation_from_contract() { let (address, output) = ctx .ext .instantiate( - CallResources::NoLimits, + &CallResources::NoLimits, Code::Existing(dummy_ch), Pallet::::convert_native_to_evm(min_balance), vec![], @@ -1198,14 +1180,14 @@ fn instantiation_from_contract() { set_balance(&ALICE, min_balance * 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(min_balance * 10); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, min_balance * 10).unwrap(); assert_matches!( MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, Pallet::::convert_native_to_evm(min_balance * 10), vec![], &ExecConfig::new_substrate_tx(), @@ -1241,7 +1223,7 @@ fn instantiation_traps() { assert_matches!( ctx.ext.instantiate( - Default::default(), + &Default::default(), Code::Existing(dummy_ch), value, vec![], @@ -1266,14 +1248,13 @@ fn instantiation_traps() { set_balance(&BOB_FALLBACK, 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(200); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 200).unwrap(); assert_matches!( MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -1295,17 +1276,17 @@ fn termination_from_instantiate_fails() { .existential_deposit(15) .build() .execute_with(|| { - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); - let executable = MockExecutable::from_storage(terminate_ch, &mut weight_meter).unwrap(); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); + let executable = MockExecutable::from_storage(terminate_ch, &mut meter).unwrap(); set_balance(&ALICE, 10_000); - let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_eq!( MockStack::run_instantiate( ALICE, executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, Pallet::::convert_native_to_evm(100u64), vec![], Some(&[0; 32]), @@ -1359,13 +1340,12 @@ fn in_memory_changes_not_discarded() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -1412,16 +1392,16 @@ fn recursive_call_during_constructor_is_balance_transfer() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); - let executable = MockExecutable::from_storage(code, &mut weight_meter).unwrap(); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); + let executable = MockExecutable::from_storage(code, &mut meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); - let mut storage_meter = StorageMeter::new(deposit_limit::()); let result = MockStack::run_instantiate( ALICE, executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, 10u64.into(), vec![], Some(&[0; 32]), @@ -1452,16 +1432,14 @@ fn cannot_send_more_balance_than_available_to_self() { .build() .execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); MockStack::run_call( origin, BOB_ADDR, - &mut weight_meter, - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -1486,14 +1464,13 @@ fn call_reentry_direct_recursion() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); // Calling another contract should succeed assert_ok!(MockStack::run_call( origin.clone(), BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), CHARLIE_ADDR.as_bytes().to_vec(), &ExecConfig::new_substrate_tx(), @@ -1504,8 +1481,7 @@ fn call_reentry_direct_recursion() { MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), BOB_ADDR.as_bytes().to_vec(), &ExecConfig::new_substrate_tx(), @@ -1539,15 +1515,14 @@ fn call_deny_reentry() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); // BOB -> CHARLIE -> BOB fails as BOB denies reentry. assert_err!( MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -1574,17 +1549,17 @@ fn minimum_balance_must_return_converted_balance() { .with_code_hashes(MockLoader::code_hashes()) .build() .execute_with(|| { - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); let succ_fail_executable = - MockExecutable::from_storage(succ_fail_code, &mut weight_meter).unwrap(); - let mut storage_meter = StorageMeter::new(deposit_limit::()); + MockExecutable::from_storage(succ_fail_code, &mut meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); assert_ok!(MockStack::run_instantiate( ALICE, succ_fail_executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, min_balance_evm_value, vec![], Some(&[0; 32]), @@ -1600,7 +1575,7 @@ fn nonce() { let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { ctx.ext .instantiate( - CallResources::NoLimits, + &CallResources::NoLimits, Code::Existing(fail_code), ctx.ext.minimum_balance() * 100, vec![], @@ -1616,7 +1591,7 @@ fn nonce() { let addr = ctx .ext .instantiate( - CallResources::NoLimits, + &CallResources::NoLimits, Code::Existing(success_code), ctx.ext.minimum_balance(), vec![], @@ -1650,25 +1625,24 @@ fn nonce() { let min_balance = ::Currency::minimum_balance(); let min_balance_evm_value: U256 = Pallet::::convert_native_to_evm(min_balance); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); - let fail_executable = - MockExecutable::from_storage(fail_code, &mut weight_meter).unwrap(); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); + let fail_executable = MockExecutable::from_storage(fail_code, &mut meter).unwrap(); let success_executable = - MockExecutable::from_storage(success_code, &mut weight_meter).unwrap(); + MockExecutable::from_storage(success_code, &mut meter).unwrap(); let succ_fail_executable = - MockExecutable::from_storage(succ_fail_code, &mut weight_meter).unwrap(); + MockExecutable::from_storage(succ_fail_code, &mut meter).unwrap(); let succ_succ_executable = - MockExecutable::from_storage(succ_succ_code, &mut weight_meter).unwrap(); + MockExecutable::from_storage(succ_succ_code, &mut meter).unwrap(); set_balance(&ALICE, min_balance * 10_000); set_balance(&BOB, min_balance * 10_000); - let mut storage_meter = StorageMeter::new(deposit_limit::()); // fail should not increment MockStack::run_instantiate( ALICE, fail_executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, min_balance_evm_value * 100, vec![], Some(&[0; 32]), @@ -1680,8 +1654,7 @@ fn nonce() { assert_ok!(MockStack::run_instantiate( ALICE, success_executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, min_balance_evm_value * 100, vec![], Some(&[0; 32]), @@ -1692,8 +1665,7 @@ fn nonce() { assert_ok!(MockStack::run_instantiate( ALICE, succ_fail_executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, min_balance_evm_value * 200, vec![], Some(&[0; 32]), @@ -1704,8 +1676,7 @@ fn nonce() { assert_ok!(MockStack::run_instantiate( ALICE, succ_succ_executable, - &mut weight_meter, - &mut storage_meter, + &mut meter, min_balance_evm_value * 200, vec![], Some(&[0; 32]), @@ -1764,16 +1735,17 @@ fn set_storage_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); + set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(deposit_limit::()); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut weight_meter, - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -1862,16 +1834,16 @@ fn set_storage_varsized_key_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(deposit_limit::()); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut weight_meter, - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -1900,16 +1872,16 @@ fn get_storage_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(deposit_limit::()); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut weight_meter, - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -1938,16 +1910,16 @@ fn get_storage_size_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(deposit_limit::()); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut weight_meter, - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -1987,16 +1959,16 @@ fn get_storage_varsized_key_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(deposit_limit::()); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut weight_meter, - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2036,16 +2008,16 @@ fn get_storage_size_varsized_key_works() { ExtBuilder::default().build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(deposit_limit::()); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut weight_meter, - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2114,12 +2086,13 @@ fn set_transient_storage_works() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(deposit_limit::()); + let mut meter = + TransactionMeter::::new_from_limits(WEIGHT_LIMIT, deposit_limit::()) + .unwrap(); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2175,13 +2148,12 @@ fn get_transient_storage_works() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -2214,12 +2186,11 @@ fn get_transient_storage_size_works() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); assert_ok!(MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2267,13 +2238,12 @@ fn rollback_transient_storage_works() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -2299,12 +2269,11 @@ fn ecdsa_to_eth_address_returns_proper_value() { place_contract(&BOB, bob_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2330,7 +2299,7 @@ fn last_frame_output_works_on_instantiate() { // Successful instantiation should set the output let address = ctx .ext - .instantiate(CallResources::NoLimits, Code::Existing(ok_ch), value, vec![], None) + .instantiate(&CallResources::NoLimits, Code::Existing(ok_ch), value, vec![], None) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -2352,7 +2321,7 @@ fn last_frame_output_works_on_instantiate() { // Reverted instantiation should set the output ctx.ext - .instantiate(Default::default(), Code::Existing(revert_ch), value, vec![], None) + .instantiate(&Default::default(), Code::Existing(revert_ch), value, vec![], None) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -2361,7 +2330,7 @@ fn last_frame_output_works_on_instantiate() { // Trapped instantiation should clear the output ctx.ext - .instantiate(Default::default(), Code::Existing(trap_ch), value, vec![], None) + .instantiate(&Default::default(), Code::Existing(trap_ch), value, vec![], None) .unwrap_err(); assert_eq!( ctx.ext.last_frame_output(), @@ -2381,13 +2350,12 @@ fn last_frame_output_works_on_instantiate() { set_balance(&BOB, 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(200); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 200).unwrap(); MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2441,13 +2409,12 @@ fn last_frame_output_works_on_nested_call() { place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), @@ -2490,7 +2457,7 @@ fn last_frame_output_is_always_reset() { *ctx.ext.last_frame_output_mut() = output_revert(); assert_eq!( ctx.ext.instantiate( - Default::default(), + &Default::default(), Code::Existing(invalid_code_hash), U256::zero(), vec![], @@ -2506,13 +2473,12 @@ fn last_frame_output_is_always_reset() { ExtBuilder::default().build().execute_with(|| { place_contract(&BOB, code_bob); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); let result = MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2540,7 +2506,13 @@ fn immutable_data_access_checks_work() { // Constructors can not access the immutable data ctx.ext - .instantiate(CallResources::NoLimits, Code::Existing(dummy_ch), value, vec![], None) + .instantiate( + &CallResources::NoLimits, + Code::Existing(dummy_ch), + value, + vec![], + None, + ) .unwrap(); exec_success() @@ -2555,13 +2527,12 @@ fn immutable_data_access_checks_work() { set_balance(&BOB, 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(200); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 200).unwrap(); MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2608,7 +2579,7 @@ fn correct_immutable_data_in_delegate_call() { place_contract(&CHARLIE, charlie_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(200); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 200).unwrap(); // Place unique immutable data for each contract >::insert::<_, ImmutableData>( @@ -2623,8 +2594,7 @@ fn correct_immutable_data_in_delegate_call() { MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2655,14 +2625,12 @@ fn immutable_data_set_overrides() { .execute_with(|| { set_balance(&ALICE, 1000); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(200); - let mut weight_meter = WeightMeter::::new(WEIGHT_LIMIT); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 200).unwrap(); let addr = MockStack::run_instantiate( ALICE, - MockExecutable::from_storage(hash, &mut weight_meter).unwrap(), - &mut weight_meter, - &mut storage_meter, + MockExecutable::from_storage(hash, &mut meter).unwrap(), + &mut meter, U256::zero(), vec![], None, @@ -2674,8 +2642,7 @@ fn immutable_data_set_overrides() { MockStack::run_call( origin, addr, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2700,7 +2667,13 @@ fn immutable_data_set_errors_with_empty_data() { let value = Pallet::::convert_native_to_evm(min_balance); ctx.ext - .instantiate(CallResources::NoLimits, Code::Existing(dummy_ch), value, vec![], None) + .instantiate( + &CallResources::NoLimits, + Code::Existing(dummy_ch), + value, + vec![], + None, + ) .unwrap(); exec_success() @@ -2715,13 +2688,12 @@ fn immutable_data_set_errors_with_empty_data() { set_balance(&BOB, 100); place_contract(&BOB, instantiator_ch); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(200); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 200).unwrap(); MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![], &ExecConfig::new_substrate_tx(), @@ -2775,13 +2747,12 @@ fn block_hash_returns_proper_values() { place_contract(&BOB, bob_code_hash); let origin = Origin::from_account_id(ALICE); - let mut storage_meter = StorageMeter::new(0); + let mut meter = TransactionMeter::::new_from_limits(WEIGHT_LIMIT, 0).unwrap(); assert_matches!( MockStack::run_call( origin, BOB_ADDR, - &mut WeightMeter::::new(WEIGHT_LIMIT), - &mut storage_meter, + &mut meter, U256::zero(), vec![0], &ExecConfig::new_substrate_tx(), diff --git a/substrate/frame/revive/src/impl_fungibles.rs b/substrate/frame/revive/src/impl_fungibles.rs index d4ef744c22a46..202298f4a806b 100644 --- a/substrate/frame/revive/src/impl_fungibles.rs +++ b/substrate/frame/revive/src/impl_fungibles.rs @@ -45,7 +45,7 @@ use sp_runtime::{traits::AccountIdConversion, DispatchError}; use super::{address::AddressMapper, pallet, Config, ContractResult, ExecConfig, Pallet, Weight}; use ethereum_standards::IERC20; -const WEIGHT_LIMIT: Weight = Weight::from_parts(1_000_000_000, 100_000); +const WEIGHT_LIMIT: Weight = Weight::from_parts(10_000_000_000, 100_000); impl Pallet { // Test checking account for the `fungibles::*` implementation. @@ -407,7 +407,10 @@ mod tests { RuntimeOrigin::signed(checking_account.clone()), Code::Upload(code), ) - .storage_deposit_limit(1_000_000_000_000) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: 1_000_000_000_000, + }) .data(constructor_data) .build_and_unwrap_contract(); assert_eq!( diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 2079ea7029bbc..ca8e3cdc7d590 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -53,10 +53,7 @@ use crate::{ TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, Stack as ExecStack}, - metering::{ - weight::Token as WeightToken, EthTxInfo, ResourceMeter, State, TransactionLimits, - TransactionMeter, - }, + metering::State, storage::{AccountType, DeletionQueueManager}, tracing::if_tracing, vm::{pvm::extract_code_and_data, CodeInfo, RuntimeCosts}, @@ -101,6 +98,10 @@ pub use crate::{ debug::DebugSettings, evm::{block_hash::ReceiptGasInfo, Address as EthAddress, Block as EthBlock, ReceiptInfo}, exec::{DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin}, + metering::{ + weight::Token as WeightToken, EthTxInfo, FrameMeter, ResourceMeter, TransactionLimits, + TransactionMeter, + }, pallet::{genesis, *}, storage::{AccountInfo, ContractInfo}, vm::ContractBlob, @@ -2630,7 +2631,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { ::RuntimeOrigin::signed(origin), dest, $crate::Pallet::::convert_native_to_evm(value), - $crate::metering::TransactionLimits::WeightAndDeposit { + $crate::TransactionLimits::WeightAndDeposit { weight_limit: weight_limit.unwrap_or(blockweights.max_block), deposit_limit: storage_deposit_limit.unwrap_or(u128::MAX), }, @@ -2656,7 +2657,7 @@ macro_rules! impl_runtime_apis_plus_revive_traits { $crate::Pallet::::bare_instantiate( ::RuntimeOrigin::signed(origin), $crate::Pallet::::convert_native_to_evm(value), - $crate::metering::TransactionLimits::WeightAndDeposit { + $crate::TransactionLimits::WeightAndDeposit { weight_limit: weight_limit.unwrap_or(blockweights.max_block), deposit_limit: storage_deposit_limit.unwrap_or(u128::MAX), }, diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index a45ebe902b4de..c65b579f37403 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -22,7 +22,7 @@ use crate::{ evm::fees::InfoT, exec::CallResources, storage::ContractInfo, vm::evm::Halt, BalanceOf, Config, Error, ExecConfig, ExecOrigin as Origin, StorageDeposit, LOG_TARGET, }; -use frame_support::DefaultNoBound; +use frame_support::{DebugNoBound, DefaultNoBound}; use num_traits::Zero; use core::{fmt::Debug, marker::PhantomData, ops::ControlFlow}; @@ -73,14 +73,14 @@ pub struct ResourceMeter { _phantom: PhantomData, } -#[derive(Debug, Clone)] +#[derive(DebugNoBound, Clone)] pub struct EthTxInfo { pub encoded_len: u32, pub extra_weight: Weight, _phantom: PhantomData, } -#[derive(Debug, Clone)] +#[derive(DebugNoBound, Clone)] pub enum TransactionLimits { EthereumGas { eth_gas_limit: BalanceOf, eth_tx_info: EthTxInfo }, WeightAndDeposit { weight_limit: Weight, deposit_limit: BalanceOf }, @@ -283,7 +283,7 @@ impl ResourceMeter { ), CallResources::Ethereum(gas) => { - let weight_gas = T::FeeInfo::weight_to_fee(&weight_left); + let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() .saturating_mul_int(deposit_left); let gas_left = weight_gas.saturating_add(deposit_gas); @@ -372,7 +372,7 @@ impl ResourceMeter { TransactionLimits::WeightAndDeposit { .. } => { match (self.weight_left(), self.deposit_left()) { (Some(weight_left), Some(deposit_left)) => { - let weight_gas = T::FeeInfo::weight_to_fee(&weight_left); + let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() .saturating_mul_int(deposit_left); @@ -564,6 +564,13 @@ impl TransactionMeter { } } + pub fn new_from_limits( + weight_limit: Weight, + deposit_limit: BalanceOf, + ) -> Result { + Self::new(TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit }) + } + pub fn execute_postponed_deposits( &mut self, origin: &Origin, diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index 64250a414d56f..bdc462732b09b 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -39,6 +39,9 @@ use sp_runtime::{ DispatchError, FixedPointNumber, FixedU128, }; +#[cfg(test)] +use num_traits::Bounded; + /// Deposit that uses the native fungible's balance type. pub type DepositOf = Deposit>; @@ -248,8 +251,14 @@ where /// This is called whenever a new subcall is initiated in order to track the storage /// usage for this sub call separately. This is necessary because we want to exchange balance /// with the current contract we are interacting with. - pub fn nested(&self, limit: Option>) -> RawMeter { + pub fn nested(&self, mut limit: Option>) -> RawMeter { debug_assert!(matches!(self.contract_state(), ContractState::Alive)); + + match (limit, self.limit) { + (Some(new_limit), Some(old_limit)) => limit = Some(new_limit.min(old_limit)), + _ => (), + } + RawMeter { limit, ..Default::default() } } @@ -334,6 +343,16 @@ where _ => ContractState::Alive, } } + + /// The amount of balance still available from the current meter. + /// + /// This includes charges from the current frame but no refunds. + #[cfg(test)] + pub fn available(&self) -> BalanceOf { + self.consumed() + .available(&self.limit.unwrap_or(BalanceOf::::max_value())) + .unwrap_or_default() + } } /// Functions that only apply to the root state. @@ -560,6 +579,8 @@ pub fn terminate_logic_for_benchmark( #[cfg(test)] mod tests { use super::*; + use crate::{exec::AccountIdOf, test_utils::*, tests::Test}; + use frame_support::parameter_types; use pretty_assertions::assert_eq; type TestMeter = RawMeter; @@ -643,7 +664,7 @@ mod tests { fn new_reserves_balance_works() { clear_ext(); - TestMeter::new(1_000); + TestMeter::new(Some(1_000)); assert_eq!(TestExtTestValue::get(), TestExt { ..Default::default() }) } @@ -655,9 +676,9 @@ mod tests { fn nested_zero_limit_requested() { clear_ext(); - let meter = TestMeter::new(1_000); + let meter = TestMeter::new(Some(1_000)); assert_eq!(meter.available(), 1_000); - let nested0 = meter.nested(BalanceOf::::zero()); + let nested0 = meter.nested(Some(BalanceOf::::zero())); assert_eq!(nested0.available(), 0); } @@ -665,9 +686,9 @@ mod tests { fn nested_some_limit_requested() { clear_ext(); - let meter = TestMeter::new(1_000); + let meter = TestMeter::new(Some(1_000)); assert_eq!(meter.available(), 1_000); - let nested0 = meter.nested(500); + let nested0 = meter.nested(Some(500)); assert_eq!(nested0.available(), 500); } @@ -675,9 +696,9 @@ mod tests { fn nested_all_limit_requested() { clear_ext(); - let meter = TestMeter::new(1_000); + let meter = TestMeter::new(Some(1_000)); assert_eq!(meter.available(), 1_000); - let nested0 = meter.nested(1_000); + let nested0 = meter.nested(Some(1_000)); assert_eq!(nested0.available(), 1_000); } @@ -685,9 +706,9 @@ mod tests { fn nested_over_limit_requested() { clear_ext(); - let meter = TestMeter::new(1_000); + let meter = TestMeter::new(Some(1_000)); assert_eq!(meter.available(), 1_000); - let nested0 = meter.nested(2_000); + let nested0 = meter.nested(Some(2_000)); assert_eq!(nested0.available(), 1_000); } @@ -695,11 +716,11 @@ mod tests { fn empty_charge_works() { clear_ext(); - let mut meter = TestMeter::new(1_000); + let mut meter = TestMeter::new(Some(1_000)); assert_eq!(meter.available(), 1_000); // an empty charge does not create a `Charge` entry - let mut nested0 = meter.nested(BalanceOf::::zero()); + let mut nested0 = meter.nested(Some(BalanceOf::::zero())); nested0.charge(&Default::default()); meter.absorb(nested0, &BOB, None); @@ -739,7 +760,7 @@ mod tests { for test_case in test_cases { clear_ext(); - let mut meter = TestMeter::new(100); + let mut meter = TestMeter::new(Some(100)); assert_eq!(meter.available(), 100); let mut nested0_info = new_info(StorageInfo { @@ -749,7 +770,7 @@ mod tests { items_deposit: 10, immutable_data_len: 0, }); - let mut nested0 = meter.nested(BalanceOf::::zero()); + let mut nested0 = meter.nested(Some(BalanceOf::::zero())); nested0.charge(&Diff { bytes_added: 108, bytes_removed: 5, @@ -765,7 +786,7 @@ mod tests { items_deposit: 20, immutable_data_len: 0, }); - let mut nested1 = nested0.nested(BalanceOf::::zero()); + let mut nested1 = nested0.nested(Some(BalanceOf::::zero())); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info)); @@ -776,16 +797,16 @@ mod tests { items_deposit: 20, immutable_data_len: 0, }); - let mut nested2 = nested0.nested(BalanceOf::::zero()); + let mut nested2 = nested0.nested(Some(BalanceOf::::zero())); nested2.charge(&Diff { items_removed: 7, ..Default::default() }); nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info)); - nested0.enforce_limit(Some(&mut nested0_info)).unwrap(); + nested0.finalize_own_contributions(Some(&mut nested0_info)); meter.absorb(nested0, &BOB, Some(&mut nested0_info)); assert_eq!( meter - .try_into_deposit(&test_case.origin, &ExecConfig::new_substrate_tx()) + .execute_postponed_deposits(&test_case.origin, &ExecConfig::new_substrate_tx()) .unwrap(), test_case.deposit ); @@ -834,10 +855,10 @@ mod tests { for test_case in test_cases { clear_ext(); - let mut meter = TestMeter::new(1_000); + let mut meter = TestMeter::new(Some(1_000)); assert_eq!(meter.available(), 1_000); - let mut nested0 = meter.nested(BalanceOf::::max_value()); + let mut nested0 = meter.nested(Some(BalanceOf::::max_value())); nested0.charge(&Diff { bytes_added: 5, bytes_removed: 1, @@ -853,17 +874,17 @@ mod tests { items_deposit: 20, immutable_data_len: 0, }); - let mut nested1 = nested0.nested(BalanceOf::::max_value()); + let mut nested1 = nested0.nested(Some(BalanceOf::::max_value())); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); nested1.terminate(&nested1_info, CHARLIE, true); - nested0.enforce_limit(Some(&mut nested1_info)).unwrap(); + nested0.finalize_own_contributions(Some(&mut nested1_info)); nested0.absorb(nested1, &CHARLIE, None); meter.absorb(nested0, &BOB, None); assert_eq!( meter - .try_into_deposit(&test_case.origin, &ExecConfig::new_substrate_tx()) + .execute_postponed_deposits(&test_case.origin, &ExecConfig::new_substrate_tx()) .unwrap(), test_case.deposit ); diff --git a/substrate/frame/revive/src/metering/weight.rs b/substrate/frame/revive/src/metering/weight.rs index de574264dd6d6..a3e7464859224 100644 --- a/substrate/frame/revive/src/metering/weight.rs +++ b/substrate/frame/revive/src/metering/weight.rs @@ -264,10 +264,14 @@ impl WeightMeter { self.weight_consumed } + pub fn consume_all(&mut self, weight_limit: Weight) { + self.weight_consumed = weight_limit; + } + /// Returns how much weight left from the initial budget. #[cfg(test)] pub fn weight_left(&self) -> Weight { - self.weight_limit.saturating_sub(self.weight_consumed) + self.weight_limit.unwrap().saturating_sub(self.weight_consumed) } #[cfg(test)] @@ -275,8 +279,9 @@ impl WeightMeter { &self.tokens } - pub fn consume_all(&mut self, weight_limit: Weight) { - self.weight_consumed = weight_limit; + #[cfg(test)] + pub fn nested(&mut self, amount: Weight) -> Self { + Self::new(Some(self.weight_left().min(amount))) } } @@ -333,14 +338,14 @@ mod tests { #[test] fn it_works() { - let weight_meter = WeightMeter::::new(Weight::from_parts(50000, 0)); + let weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0))); assert_eq!(weight_meter.weight_left(), Weight::from_parts(50000, 0)); } #[test] fn tracing() { - let mut weight_meter = WeightMeter::::new(Weight::from_parts(50000, 0)); - assert!(!weight_meter.charge(SimpleToken(1)).is_err()); + let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0))); + assert!(!weight_meter.charge(SimpleToken(1), weight_meter.weight_left()).is_err()); let mut tokens = weight_meter.tokens().iter(); match_tokens!(tokens, SimpleToken(1),); @@ -349,8 +354,8 @@ mod tests { // This test makes sure that nothing can be executed if there is no weight. #[test] fn refuse_to_execute_anything_if_zero() { - let mut weight_meter = WeightMeter::::new(Weight::zero()); - assert!(weight_meter.charge(SimpleToken(1)).is_err()); + let mut weight_meter = WeightMeter::::new(Some(Weight::zero())); + assert!(weight_meter.charge(SimpleToken(1), weight_meter.weight_left()).is_err()); } /// Previously, passing a `Weight` of 0 to `nested` would consume all of the meter's current @@ -360,7 +365,7 @@ mod tests { #[test] fn nested_zero_weight_requested() { let test_weight = 50000.into(); - let mut weight_meter = WeightMeter::::new(test_weight); + let mut weight_meter = WeightMeter::::new(Some(test_weight)); let weight_for_nested_call = weight_meter.nested(0.into()); assert_eq!(weight_meter.weight_left(), 50000.into()); @@ -370,7 +375,7 @@ mod tests { #[test] fn nested_some_weight_requested() { let test_weight = 50000.into(); - let mut weight_meter = WeightMeter::::new(test_weight); + let mut weight_meter = WeightMeter::::new(Some(test_weight)); let weight_for_nested_call = weight_meter.nested(10000.into()); assert_eq!(weight_meter.weight_consumed(), 0.into()); @@ -380,7 +385,7 @@ mod tests { #[test] fn nested_all_weight_requested() { let test_weight = Weight::from_parts(50000, 50000); - let mut weight_meter = WeightMeter::::new(test_weight); + let mut weight_meter = WeightMeter::::new(Some(test_weight)); let weight_for_nested_call = weight_meter.nested(test_weight); assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); @@ -390,7 +395,7 @@ mod tests { #[test] fn nested_excess_weight_requested() { let test_weight = Weight::from_parts(50000, 50000); - let mut weight_meter = WeightMeter::::new(test_weight); + let mut weight_meter = WeightMeter::::new(Some(test_weight)); let weight_for_nested_call = weight_meter.nested(test_weight + 10000.into()); assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); @@ -400,20 +405,20 @@ mod tests { // Make sure that the weight meter does not charge in case of overcharge #[test] fn overcharge_does_not_charge() { - let mut weight_meter = WeightMeter::::new(Weight::from_parts(200, 0)); + let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(200, 0))); // The first charge is should lead to OOG. - assert!(weight_meter.charge(SimpleToken(300)).is_err()); + assert!(weight_meter.charge(SimpleToken(300), weight_meter.weight_left()).is_err()); // The weight meter should still contain the full 200. - assert!(weight_meter.charge(SimpleToken(200)).is_ok()); + assert!(weight_meter.charge(SimpleToken(200), weight_meter.weight_left()).is_ok()); } // Charging the exact amount that the user paid for should be // possible. #[test] fn charge_exact_amount() { - let mut weight_meter = WeightMeter::::new(Weight::from_parts(25, 0)); - assert!(!weight_meter.charge(SimpleToken(25)).is_err()); + let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(25, 0))); + assert!(!weight_meter.charge(SimpleToken(25), weight_meter.weight_left()).is_err()); } } diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index e0853bab14a84..eeebcf15eaee1 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -410,6 +410,18 @@ impl ExecConfig { self.is_dry_run = true; self } + + /// Almost clone for testing (does not clone mock_handler) + #[cfg(test)] + pub fn clone(&self) -> Self { + Self { + bump_nonce: self.bump_nonce, + collect_deposit_from_hold: self.collect_deposit_from_hold, + effective_gas_price: self.effective_gas_price, + is_dry_run: self.is_dry_run, + mock_handler: None, + } + } } /// Indicates whether the code was removed after the last refcount was decremented. diff --git a/substrate/frame/revive/src/test_utils.rs b/substrate/frame/revive/src/test_utils.rs index 4f327c6f0ed80..8f254acff9ad0 100644 --- a/substrate/frame/revive/src/test_utils.rs +++ b/substrate/frame/revive/src/test_utils.rs @@ -72,7 +72,7 @@ pub const EVE: AccountId32 = AccountId32::new([5u8; 32]); pub const EVE_ADDR: H160 = H160(hex!("e21eecd6e51cbcda5b0c5207ae87e605839e70ef")); pub const EVE_FALLBACK: AccountId32 = ee_extend(EVE_ADDR.0); -pub const WEIGHT_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); +pub const WEIGHT_LIMIT: Weight = Weight::from_parts(500_000_000_000, 10 * 1024 * 1024); pub const ETH_GAS_LIMIT: u64 = 1_000_000_000_000; pub fn deposit_limit() -> BalanceOf { diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 0c8cda076a0bf..d5cef0020cd13 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -27,12 +27,13 @@ use crate::{ evm::{fees::InfoT, CallTrace, CallTracer, CallType}, exec::Key, limits, + metering::TransactionLimits, precompiles::alloy::sol_types::{ sol_data::{Bool, FixedBytes}, SolType, }, storage::{DeletionQueueManager, WriteOutcome}, - test_utils::builder::Contract, + test_utils::{builder::Contract, WEIGHT_LIMIT}, tests::{ builder, initialize_block, test_utils::*, Balances, CodeHashLockupDepositPercent, Contracts, DepositPerByte, DepositPerItem, ExtBuilder, InstantiateAccount, RuntimeCall, @@ -261,14 +262,23 @@ fn deposit_limit_enforced_on_plain_transfer() { // sending balance to a new account should fail when the limit is lower than the ed let result = builder::bare_call(CHARLIE_ADDR) .native_value(1) - .storage_deposit_limit(190) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: Default::default(), + deposit_limit: 190, + }) .build(); assert_err!(result.result, >::StorageDepositLimitExhausted); assert_eq!(result.storage_deposit, StorageDeposit::Charge(0)); assert_eq!(get_balance(&CHARLIE), 0); // works when the account is prefunded - let result = builder::bare_call(BOB_ADDR).native_value(1).storage_deposit_limit(0).build(); + let result = builder::bare_call(BOB_ADDR) + .native_value(1) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: Default::default(), + deposit_limit: 0, + }) + .build(); assert_ok!(result.result); assert_eq!(result.storage_deposit, StorageDeposit::Charge(0)); assert_eq!(get_balance(&BOB), 1_000_001); @@ -276,7 +286,10 @@ fn deposit_limit_enforced_on_plain_transfer() { // also works allowing enough deposit let result = builder::bare_call(CHARLIE_ADDR) .native_value(1) - .storage_deposit_limit(200) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: Default::default(), + deposit_limit: 200, + }) .build(); assert_ok!(result.result); assert_eq!(result.storage_deposit, StorageDeposit::Charge(200)); @@ -444,7 +457,7 @@ fn deposit_event_max_value_limit() { // Call contract with too large a evene size assert_err_ignore_postinfo!( builder::call(addr) - .gas_limit(Weight::from_parts(u64::MAX, u64::MAX)) + .weight_limit(Weight::from_parts(u64::MAX, u64::MAX)) .data((limits::EVENT_BYTES + 1).encode()) .build(), Error::::ValueTooLarge, @@ -1942,24 +1955,30 @@ fn gas_estimation_for_subcalls() { // Make the same call using the estimated gas. Should succeed. let result = builder::bare_call(addr_caller) - .weight_limit(result_orig.weight_required) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: result_orig.weight_required, + deposit_limit: result_orig.storage_deposit.charge_or_zero().into(), + }) .data(input.clone()) .build(); assert_ok!(&result.result); // Check that it fails with too little ref_time let result = builder::bare_call(addr_caller) - .weight_limit(result_orig.weight_required.sub_ref_time(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: result_orig.weight_required.sub_ref_time(1), + deposit_limit: result_orig.storage_deposit.charge_or_zero().into(), + }) .data(input.clone()) .build(); assert_err!(result.result, >::OutOfGas); // Check that it fails with too little proof_size let result = builder::bare_call(addr_caller) - .weight_limit(result_orig.weight_required.sub_proof_size(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: result_orig.weight_required.sub_proof_size(1), + deposit_limit: result_orig.storage_deposit.charge_or_zero().into(), + }) .data(input.clone()) .build(); assert_err!(result.result, >::OutOfGas); @@ -2696,7 +2715,10 @@ fn storage_deposit_limit_is_enforced() { assert_err!( builder::bare_instantiate(Code::Upload(binary.clone())) // expected deposit is 2 * ed + 3 for the call - .storage_deposit_limit((2 * min_balance + 3 - 1).into()) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: Default::default(), + deposit_limit: (2 * min_balance + 3 - 1).into() + }) .build() .result, >::StorageDepositLimitExhausted, @@ -2796,7 +2818,10 @@ fn deposit_limit_in_nested_calls() { // that the nested call should have a deposit limit of at least 2 Balance. The // sub-call should be rolled back, which is covered by the next test case. let ret = builder::bare_call(addr_caller) - .storage_deposit_limit(u64::MAX) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: u64::MAX, + }) .data((102u32, &addr_callee, U256::from(1u64)).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); @@ -2821,11 +2846,21 @@ fn deposit_limit_in_nested_calls() { .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); + // Free up a bit of storage in the callee but not enough for the caller to + // create a new item + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .storage_deposit_limit(78) + .data((95u32, &addr_callee, U256::from(1u64)).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + // Free up enough storage in the callee so that the caller can create a new item // We set the special deposit limit of 1 Balance for the nested call, which isn't // enforced as callee frees up storage. This should pass. assert_ok!(builder::call(addr_caller) - .storage_deposit_limit(1) + .storage_deposit_limit(78) .data((0u32, &addr_callee, U256::from(1u64)).encode()) .build()); }); @@ -2875,7 +2910,10 @@ fn deposit_limit_in_nested_instantiate() { // Sub calls return first to they are checked first. let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(0) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: 155, + }) .data((&code_hash_callee, 100u32, &U256::MAX.to_little_endian()).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); @@ -2887,21 +2925,25 @@ fn deposit_limit_in_nested_instantiate() { // Same as above but stores one byte in both caller and callee. let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(caller_min_deposit + 1) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: caller_min_deposit + 1, + }) .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit)).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on the instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); - println!("caller={caller_min_deposit:?} callee={callee_min_deposit:?}"); - // Fail in the caller with bytes. // // Same as above but stores one byte in both caller and callee. let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(caller_min_deposit + 2) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: caller_min_deposit + 2, + }) .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit + 1)).encode()) .build(); assert_err!(ret.result, >::StorageDepositLimitExhausted); @@ -2911,7 +2953,10 @@ fn deposit_limit_in_nested_instantiate() { // Set enough deposit limit for the child instantiate. This should succeed. let result = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit((caller_min_deposit + 3).into()) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: (caller_min_deposit + 3).into(), + }) .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit + 1)).encode()) .build(); diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index 725b146f919e4..78829e9aec127 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -20,10 +20,13 @@ use core::iter; use crate::{ + address::AddressMapper, evm::{decode_revert_reason, fees::InfoT}, - test_utils::{builder::Contract, deposit_limit, ALICE, ALICE_ADDR, WEIGHT_LIMIT}, - tests::{builder, ExtBuilder, Test}, - BalanceOf, Code, Config, DispatchError, Error, ExecConfig, + metering::TransactionLimits, + test_utils::{builder::Contract, deposit_limit, ALICE, ALICE_ADDR, BOB_ADDR, WEIGHT_LIMIT}, + tests::{builder, ExtBuilder, MockHandlerImpl, Test}, + BalanceOf, Code, Config, DelegateInfo, DispatchError, Error, ExecConfig, ExecOrigin, + ExecReturnValue, }; use alloy_core::{ primitives::{Bytes, FixedBytes}, @@ -33,6 +36,7 @@ use frame_support::{ assert_err, traits::fungible::{Balanced, Mutate}, }; +use itertools::Itertools; use pallet_revive_fixtures::{compile_module_with_type, Callee, Caller, FixtureType}; use pallet_revive_uapi::ReturnFlags; use pretty_assertions::assert_eq; @@ -713,7 +717,7 @@ fn subcall_effectively_limited_substrate_tx(caller_type: FixtureType, callee_typ ]; for ((case, config), call_type) in - test_cases.iter().cartesian_product(configs).cartesian_product(call_types) + test_cases.iter().cartesian_product(&configs).cartesian_product(call_types) { // the storage stuff won't work on static or delegate call if case.is_store_call && call_type != 0 { @@ -746,8 +750,11 @@ fn subcall_effectively_limited_substrate_tx(caller_type: FixtureType, callee_typ } .abi_encode(), ) - .exec_config(config) - .storage_deposit_limit(case.deposit_limit) + .exec_config(config.clone()) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: case.deposit_limit, + }) .build(); let result = output.result.map(|result| { diff --git a/substrate/frame/revive/src/tests/sol/control.rs b/substrate/frame/revive/src/tests/sol/control.rs index 43e641be56af0..0c953f496b6f0 100644 --- a/substrate/frame/revive/src/tests/sol/control.rs +++ b/substrate/frame/revive/src/tests/sol/control.rs @@ -16,6 +16,7 @@ // limitations under the License. use crate::{ + metering::TransactionLimits, test_utils::{builder::Contract, ALICE}, tests::{builder, sol::make_initcode_from_runtime_code, ExtBuilder, Test}, Code, Config, Error, U256, @@ -253,8 +254,13 @@ fn invalid_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let output = - builder::bare_call(addr).weight_limit(expected_gas.into()).data(vec![]).build(); + let output = builder::bare_call(addr) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: expected_gas.into(), + deposit_limit: Default::default(), + }) + .data(vec![]) + .build(); let result = output.result; assert_err!(result, Error::::InvalidInstruction); diff --git a/substrate/frame/revive/src/tests/sol/host.rs b/substrate/frame/revive/src/tests/sol/host.rs index eaef4fa753797..6044a27931531 100644 --- a/substrate/frame/revive/src/tests/sol/host.rs +++ b/substrate/frame/revive/src/tests/sol/host.rs @@ -18,6 +18,7 @@ //! The pallet-revive shared VM integration test suite. use crate::{ address::AddressMapper, + metering::TransactionLimits, test_utils::{builder::Contract, ALICE, BOB, BOB_ADDR}, tests::{builder, test_utils, ExtBuilder, RuntimeEvent, Test}, Code, Config, Error, Key, System, H256, U256, @@ -471,7 +472,10 @@ fn logs_work(fixture_type: FixtureType) { initialize_block(2); let result = builder::bare_call(addr) - .gas_limit(crate::Weight::from_parts(100_000_000_000_000, 50 * 1024 * 1024)) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: crate::Weight::from_parts(100_000_000_000_000, 50 * 1024 * 1024), + deposit_limit: Default::default(), + }) .data(Host::HostCalls::logOps(Host::logOpsCall {}).abi_encode()) .build_and_unwrap_result(); assert!(!result.did_revert(), "test reverted"); diff --git a/substrate/frame/revive/src/tests/sol/system.rs b/substrate/frame/revive/src/tests/sol/system.rs index 7e3788dfcfe1f..90f7ee13073bc 100644 --- a/substrate/frame/revive/src/tests/sol/system.rs +++ b/substrate/frame/revive/src/tests/sol/system.rs @@ -19,9 +19,9 @@ use crate::{ evm::fees::InfoT, - test_utils::{builder::Contract, ALICE, ALICE_ADDR, WEIGHT_LIMIT}, + test_utils::{builder::Contract, deposit_limit, ALICE, ALICE_ADDR, WEIGHT_LIMIT}, tests::{builder, Contracts, ExtBuilder, Test}, - Code, Config, ExecConfig, U256, + Code, Config, ExecConfig, TransactionLimits, TransactionMeter, U256, }; use alloy_core::sol_types::SolCall; use frame_support::traits::fungible::{Balanced, Mutate}; @@ -213,7 +213,13 @@ fn gas_works(fixture_type: FixtureType) { builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); // enable txhold collection which we expect to be on when using the evm backend - let hold_initial = ::FeeInfo::weight_to_fee(&WEIGHT_LIMIT); + let limits = TransactionLimits::WeightAndDeposit { + weight_limit: WEIGHT_LIMIT, + deposit_limit: deposit_limit::(), + }; + let hold_initial = + TransactionMeter::::new(limits.clone()).unwrap().eth_gas_left().unwrap(); + ::FeeInfo::deposit_txfee(::Currency::issue(hold_initial)); let mut exec_config = ExecConfig::new_substrate_tx(); exec_config.collect_deposit_from_hold = Some((0u32.into(), Default::default())); @@ -221,6 +227,7 @@ fn gas_works(fixture_type: FixtureType) { let result = builder::bare_call(addr) .data(SystemFixture::gasCall {}.abi_encode()) .exec_config(exec_config) + .transaction_limits(limits) .build_and_unwrap_result(); let gas_left: u64 = SystemFixture::gasCall::abi_decode_returns(&result.data) From 6a8afd8ac707ee0b7415134a29a0de482c4ecd6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sun, 2 Nov 2025 21:39:28 -0300 Subject: [PATCH 06/69] Use max storage deposit for dry runs --- .../revive/fixtures/contracts/Deposit.sol | 70 +++++ substrate/frame/revive/src/evm/fees.rs | 2 +- substrate/frame/revive/src/lib.rs | 41 +-- substrate/frame/revive/src/metering/mod.rs | 12 + .../frame/revive/src/metering/storage.rs | 285 +++++++++++++++++- substrate/frame/revive/src/metering/tests.rs | 70 +++++ substrate/frame/revive/src/primitives.rs | 24 +- 7 files changed, 464 insertions(+), 40 deletions(-) create mode 100644 substrate/frame/revive/fixtures/contracts/Deposit.sol create mode 100644 substrate/frame/revive/src/metering/tests.rs diff --git a/substrate/frame/revive/fixtures/contracts/Deposit.sol b/substrate/frame/revive/fixtures/contracts/Deposit.sol new file mode 100644 index 0000000000000..47f2c1db0a55f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/Deposit.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.20; + +contract Deposit { + uint256 a; + uint256 b; + + function clearStorageSlot(uint256 slot) internal { + address storagePrecompile = 0x0000000000000000000000000000000000000901; + bytes memory key = abi.encodePacked(bytes32(slot)); + (bool _success, ) = storagePrecompile.delegatecall( + abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, key) + ); + } + + function clear() external { + clearStorageSlot(0); + clearStorageSlot(1); + } + + function c() external { + address targetAddress = 0x0000000000000000000000000000000000000901; + + a = 2; + b = 3; + + bytes32 key = bytes32(bytes1(0x01)) >> 248; + bytes memory keyBytes = abi.encodePacked(key); + + (bool success, ) = targetAddress.delegatecall( + abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, keyBytes) + ); + } + + function d() external { + this.x(); + + address targetAddress = 0x0000000000000000000000000000000000000901; + bytes32 key = bytes32(bytes1(0x01)) >> 248; + bytes memory keyBytes = abi.encodePacked(key); + + (bool success, ) = targetAddress.delegatecall( + abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, keyBytes) + ); + } + + function e() external { + a = 2; + b = 3; + + this.y(); + } + + + function x() external { + a = 2; + b = 3; + } + + function y() external { + address targetAddress = 0x0000000000000000000000000000000000000901; + + bytes32 key = bytes32(bytes1(0x01)) >> 248; + bytes memory keyBytes = abi.encodePacked(key); + + (bool success, ) = targetAddress.delegatecall( + abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, keyBytes) + ); + } +} \ No newline at end of file diff --git a/substrate/frame/revive/src/evm/fees.rs b/substrate/frame/revive/src/evm/fees.rs index 8fd5cdf96af6d..80748c3e8340a 100644 --- a/substrate/frame/revive/src/evm/fees.rs +++ b/substrate/frame/revive/src/evm/fees.rs @@ -315,7 +315,7 @@ where .saturating_mul_int(weight.proof_size()); // saturated addition not required here but better to be defensive - ((ref_time_part >> 1).saturating_add(proof_size_part >> 1)).saturated_into() + ((ref_time_part / 2).saturating_add(proof_size_part / 2)).saturated_into() } /// Convert an unadjusted fee back to a weight. diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index ca8e3cdc7d590..7c84ba4d0e6eb 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1527,17 +1527,9 @@ impl Pallet { ) -> ContractResult> { let mut transaction_meter = match TransactionMeter::new(transaction_limits) { Ok(transaction_meter) => transaction_meter, - Err(error) => - return ContractResult { - result: Err(error), - weight_consumed: Default::default(), - weight_required: Default::default(), - storage_deposit: Default::default(), - }, + Err(error) => return ContractResult { result: Err(error), ..Default::default() }, }; - let mut storage_deposit = Default::default(); - let try_call = || { let origin = ExecOrigin::from_runtime_origin(origin)?; let result = ExecStack::>::run_call( @@ -1549,11 +1541,11 @@ impl Pallet { &exec_config, )?; - storage_deposit = transaction_meter + transaction_meter .execute_postponed_deposits(&origin, &exec_config) .inspect_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}"); - })?; + log::debug!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}"); + })?; Ok(result) }; let result = Self::run_guarded(try_call); @@ -1562,7 +1554,8 @@ impl Pallet { result: result.map_err(|r| r.error), weight_consumed: transaction_meter.weight_consumed(), weight_required: transaction_meter.weight_required(), - storage_deposit, + storage_deposit: transaction_meter.deposit_consumed(), + max_storage_deposit: transaction_meter.deposit_required(), } } @@ -1593,17 +1586,9 @@ impl Pallet { ) -> ContractResult> { let mut transaction_meter = match TransactionMeter::new(transaction_limits) { Ok(transaction_meter) => transaction_meter, - Err(error) => - return ContractResult { - result: Err(error), - weight_consumed: Default::default(), - weight_required: Default::default(), - storage_deposit: Default::default(), - }, + Err(error) => return ContractResult { result: Err(error), ..Default::default() }, }; - let mut storage_deposit = Default::default(); - let try_instantiate = || { let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?; @@ -1640,8 +1625,7 @@ impl Pallet { salt.as_ref(), &exec_config, ); - storage_deposit = - transaction_meter.execute_postponed_deposits(&instantiate_origin, &exec_config)?; + transaction_meter.execute_postponed_deposits(&instantiate_origin, &exec_config)?; result }; let output = Self::run_guarded(try_instantiate); @@ -1651,7 +1635,8 @@ impl Pallet { .map_err(|e| e.error), weight_consumed: transaction_meter.weight_consumed(), weight_required: transaction_meter.weight_required(), - storage_deposit, + storage_deposit: transaction_meter.deposit_consumed(), + max_storage_deposit: transaction_meter.deposit_required(), } } @@ -1800,6 +1785,7 @@ impl Pallet { EthTransactInfo { weight_required: result.weight_required, storage_deposit: result.storage_deposit.charge_or_zero(), + max_storage_deposit: result.max_storage_deposit.charge_or_zero(), data, eth_gas: Default::default(), } @@ -1844,6 +1830,7 @@ impl Pallet { EthTransactInfo { weight_required: result.weight_required, storage_deposit: result.storage_deposit.charge_or_zero(), + max_storage_deposit: result.max_storage_deposit.charge_or_zero(), data: returned_data, eth_gas: Default::default(), } @@ -1881,7 +1868,7 @@ impl Pallet { // We add `1` to account for the potential rounding error of the multiplication. // Returning a larger value here just increases the the pre-dispatch weight. let eth_gas: U256 = T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(transaction_fee.saturating_add(dry_run.storage_deposit)) + .saturating_mul_int(transaction_fee.saturating_add(dry_run.max_storage_deposit)) .saturating_add(1_u32.into()) .into(); @@ -1895,11 +1882,13 @@ impl Pallet { encoded_len={} \ tx_fee={transaction_fee:?} \ storage_deposit={:?}\ + max_storage_deposit={:?}\ ", dry_run.weight_required, max_weight.saturating_sub(total_weight), call_info.encoded_len, dry_run.storage_deposit, + dry_run.max_storage_deposit, ); dry_run.eth_gas = eth_gas; diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index c65b579f37403..70c1c13557369 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -18,6 +18,9 @@ pub mod storage; pub mod weight; +#[cfg(test)] +mod tests; + use crate::{ evm::fees::InfoT, exec::CallResources, storage::ContractInfo, vm::evm::Halt, BalanceOf, Config, Error, ExecConfig, ExecOrigin as Origin, StorageDeposit, LOG_TARGET, @@ -149,6 +152,7 @@ impl ResourceMeter { if self.deposit.is_root { if self.deposit_left().is_none() { + self.deposit.reset(); return Err(>::StorageDepositLimitExhausted.into()); } } @@ -462,6 +466,14 @@ impl ResourceMeter { pub fn weight_required(&self) -> Weight { self.weight.weight_required() } + + pub fn deposit_consumed(&self) -> DepositOf { + self.deposit.consumed() + } + + pub fn deposit_required(&self) -> DepositOf { + self.deposit.max_charged() + } } impl EthTxInfo { diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index bdc462732b09b..cc5ba3363f941 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -85,12 +85,16 @@ pub struct RawMeter { /// The amount of balance that was used in this meter and all of its already absorbed children. total_deposit: DepositOf, /// The amount of storage changes that were recorded in this meter alone. + /// This has no meaning for Root meters and will always be Contribution::Checked(0) own_contribution: Contribution, /// List of charges that should be applied at the end of a contract stack execution. /// /// We only have one charge per contract hence the size of this vector is /// limited by the maximum call depth. charges: Vec>, + /// The maximal consumed deposit that occurred at any point during the execution of this + /// storage deposit meter + max_charged: BalanceOf, /// True if this is the root meter. /// /// Sometimes we cannot know at compile time. @@ -254,14 +258,20 @@ where pub fn nested(&self, mut limit: Option>) -> RawMeter { debug_assert!(matches!(self.contract_state(), ContractState::Alive)); - match (limit, self.limit) { - (Some(new_limit), Some(old_limit)) => limit = Some(new_limit.min(old_limit)), - _ => (), + if let (Some(new_limit), Some(old_limit)) = (limit, self.limit) { + limit = Some(new_limit.min(old_limit)); } RawMeter { limit, ..Default::default() } } + pub fn reset(&mut self) { + self.own_contribution = Default::default(); + self.total_deposit = Default::default(); + self.charges = Default::default(); + self.max_charged = Default::default(); + } + /// Absorb a child that was spawned to handle a sub call. /// /// This should be called whenever a sub call comes to its end and it is **not** reverted. @@ -283,6 +293,12 @@ where contract: &T::AccountId, info: Option<&mut ContractInfo>, ) { + // No need to recalculate max_charged for `absorbed` here. With `info` we can now calculate + // the correct `own_contribution` of `absorbed` but that can only be less + self.max_charged = self + .max_charged + .max(self.consumed().saturating_add(&absorbed.max_charged()).charge_or_zero()); + let own_deposit = absorbed.own_contribution.update_contract(info); self.total_deposit = self .total_deposit @@ -290,6 +306,8 @@ where .saturating_add(&own_deposit); self.charges.extend_from_slice(&absorbed.charges); + self.recalulculate_max_charged(); + // Allow recording zero deposit charge only if the contract was terminated. if !own_deposit.is_zero() || matches!(absorbed.contract_state(), ContractState::Terminated { .. }) @@ -322,6 +340,7 @@ where pub fn record_charge(&mut self, amount: &DepositOf) { let total_deposit = self.total_deposit.saturating_add(&amount); self.total_deposit = total_deposit; + self.recalulculate_max_charged(); } /// The amount of balance that this meter has consumed. @@ -332,6 +351,11 @@ where self.total_deposit.saturating_add(&self.own_contribution.update_contract(None)) } + /// Return the maximum consumed deposit at any point in the previous execution + pub fn max_charged(&self) -> DepositOf { + Deposit::Charge(self.max_charged) + } + /// Returns the state of the currently executed contract. fn contract_state(&self) -> ContractState { match &self.own_contribution { @@ -344,6 +368,11 @@ where } } + /// Recaluclate the max deposit value + fn recalulculate_max_charged(&mut self) { + self.max_charged = self.max_charged.max(self.consumed().charge_or_zero()); + } + /// The amount of balance still available from the current meter. /// /// This includes charges from the current frame but no refunds. @@ -366,7 +395,12 @@ where /// If the limit is larger than what the origin can afford we will just fail /// when collecting the deposits in `try_into_deposit`. pub fn new(limit: Option>) -> Self { - Self { limit, is_root: true, ..Default::default() } + Self { + limit, + is_root: true, + own_contribution: Contribution::Checked(Default::default()), + ..Default::default() + } } /// The total amount of deposit that should change hands as result of the execution @@ -426,7 +460,10 @@ impl> RawMeter { /// Charges `diff` from the meter. pub fn charge(&mut self, diff: &Diff) { match &mut self.own_contribution { - Contribution::Alive(own) => *own = own.saturating_add(diff), + Contribution::Alive(own) => { + *own = own.saturating_add(diff); + self.recalulculate_max_charged(); + }, _ => panic!("Charge is never called after termination; qed"), }; } @@ -459,15 +496,21 @@ impl> RawMeter { beneficiary, delete_code, }; + + // no need to recalculate max_charged here as the consumed amount cannot increase + // when replacing an `own_contribution` that is `Contribution::Alive` with a + // `Contribution::Terminated` containing a `Deposit::Refund` } /// Determine the actual final charge from the own contributions pub fn finalize_own_contributions(&mut self, info: Option<&mut ContractInfo>) { - let deposit = self.own_contribution.update_contract(info); - // We don't want to override a `Terminated` with a `Checked`. if matches!(self.contract_state(), ContractState::Alive) { + let deposit = self.own_contribution.update_contract(info); self.own_contribution = Contribution::Checked(deposit); + + // no need to recalculate max_charged here as the consumed amount cannot increase + // when taking removed bytes/items into account } } } @@ -723,6 +766,13 @@ mod tests { let mut nested0 = meter.nested(Some(BalanceOf::::zero())); nested0.charge(&Default::default()); meter.absorb(nested0, &BOB, None); + assert_eq!( + meter.execute_postponed_deposits( + &Origin::::from_account_id(ALICE), + &ExecConfig::new_substrate_tx(), + ), + Ok(Default::default()) + ); assert_eq!(TestExtTestValue::get(), TestExt { ..Default::default() }) } @@ -761,6 +811,7 @@ mod tests { clear_ext(); let mut meter = TestMeter::new(Some(100)); + assert_eq!(meter.consumed(), Default::default()); assert_eq!(meter.available(), 100); let mut nested0_info = new_info(StorageInfo { @@ -777,7 +828,11 @@ mod tests { items_added: 1, items_removed: 2, }); + assert_eq!(nested0.consumed(), Deposit::Charge(103)); + assert_eq!(nested0.available(), 0); nested0.charge(&Diff { bytes_removed: 99, ..Default::default() }); + assert_eq!(nested0.consumed(), Deposit::Charge(4)); + assert_eq!(nested0.available(), 0); let mut nested1_info = new_info(StorageInfo { bytes: 100, @@ -788,7 +843,15 @@ mod tests { }); let mut nested1 = nested0.nested(Some(BalanceOf::::zero())); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + assert_eq!(nested1.consumed(), Default::default()); + assert_eq!(nested1.available(), 0); + nested1.finalize_own_contributions(Some(&mut nested1_info)); + assert_eq!(nested1.consumed(), Deposit::Refund(10)); + assert_eq!(nested1.available(), 10); + nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info)); + assert_eq!(nested0.consumed(), Deposit::Refund(6)); + assert_eq!(nested0.available(), 6); let mut nested2_info = new_info(StorageInfo { bytes: 100, @@ -799,10 +862,23 @@ mod tests { }); let mut nested2 = nested0.nested(Some(BalanceOf::::zero())); nested2.charge(&Diff { items_removed: 7, ..Default::default() }); + assert_eq!(nested2.consumed(), Default::default()); + assert_eq!(nested2.available(), 0); + nested2.finalize_own_contributions(Some(&mut nested2_info)); + assert_eq!(nested2.consumed(), Deposit::Refund(20)); + assert_eq!(nested2.available(), 20); + nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info)); + assert_eq!(nested0.consumed(), Deposit::Refund(26)); + assert_eq!(nested0.available(), 26); nested0.finalize_own_contributions(Some(&mut nested0_info)); + assert_eq!(nested0.consumed(), Deposit::Refund(28)); + assert_eq!(nested0.available(), 28); + meter.absorb(nested0, &BOB, Some(&mut nested0_info)); + assert_eq!(meter.consumed(), Deposit::Refund(28)); + assert_eq!(meter.available(), 128); assert_eq!( meter @@ -832,7 +908,7 @@ mod tests { contract: CHARLIE, amount: Deposit::Refund(120), state: ContractState::Terminated { - beneficiary: CHARLIE, + beneficiary: DJANGO, delete_code: true, }, }, @@ -859,13 +935,18 @@ mod tests { assert_eq!(meter.available(), 1_000); let mut nested0 = meter.nested(Some(BalanceOf::::max_value())); + assert_eq!(nested0.available(), 1_000); + nested0.charge(&Diff { bytes_added: 5, bytes_removed: 1, items_added: 3, items_removed: 1, }); + assert_eq!(nested0.consumed(), Deposit::Charge(8)); + nested0.charge(&Diff { items_added: 2, ..Default::default() }); + assert_eq!(nested0.consumed(), Deposit::Charge(12)); let mut nested1_info = new_info(StorageInfo { bytes: 100, @@ -874,14 +955,23 @@ mod tests { items_deposit: 20, immutable_data_len: 0, }); + let mut nested1 = nested0.nested(Some(BalanceOf::::max_value())); + assert_eq!(nested1.consumed(), Default::default()); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + assert_eq!(nested1.consumed(), Default::default()); nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); - nested1.terminate(&nested1_info, CHARLIE, true); - nested0.finalize_own_contributions(Some(&mut nested1_info)); + assert_eq!(nested1.consumed(), Deposit::Charge(20)); + nested1.terminate(&nested1_info, DJANGO, true); + assert_eq!(nested1.consumed(), Deposit::Refund(120)); + nested1.finalize_own_contributions(Some(&mut nested1_info)); + nested0.absorb(nested1, &CHARLIE, None); + assert_eq!(nested0.consumed(), Deposit::Refund(108)); meter.absorb(nested0, &BOB, None); + assert_eq!(meter.consumed(), Deposit::Refund(108)); + assert_eq!( meter .execute_postponed_deposits(&test_case.origin, &ExecConfig::new_substrate_tx()) @@ -891,4 +981,179 @@ mod tests { assert_eq!(TestExtTestValue::get(), test_case.expected) } } + + #[test] + fn max_deposits_work_with_charges() { + clear_ext(); + let meter = TestMeter::new(None); + let mut nested = meter.nested(None); + + assert_eq!(nested.consumed(), Default::default()); + assert_eq!(nested.max_charged(), Default::default()); + + nested.record_charge(&Deposit::Charge(100)); + assert_eq!(nested.consumed(), Deposit::Charge(100)); + assert_eq!(nested.max_charged(), Deposit::Charge(100)); + + nested.record_charge(&Deposit::Refund(50)); + assert_eq!(nested.consumed(), Deposit::Charge(50)); + assert_eq!(nested.max_charged(), Deposit::Charge(100)); + + nested.record_charge(&Deposit::Charge(80)); + assert_eq!(nested.consumed(), Deposit::Charge(130)); + assert_eq!(nested.max_charged(), Deposit::Charge(130)); + + nested.record_charge(&Deposit::Refund(200)); + assert_eq!(nested.consumed(), Deposit::Refund(70)); + assert_eq!(nested.max_charged(), Deposit::Charge(130)); + + let meter = TestMeter::new(None); + let mut nested = meter.nested(None); + nested.record_charge(&Deposit::Refund(100)); + assert_eq!(nested.consumed(), Deposit::Refund(100)); + assert_eq!(nested.max_charged(), Default::default()); + + nested.record_charge(&Deposit::Charge(100)); + assert_eq!(nested.consumed(), Default::default()); + assert_eq!(nested.max_charged(), Default::default()); + + nested.record_charge(&Deposit::Charge(50)); + assert_eq!(nested.consumed(), Deposit::Charge(50)); + assert_eq!(nested.max_charged(), Deposit::Charge(50)); + + nested.record_charge(&Deposit::Refund(20)); + assert_eq!(nested.consumed(), Deposit::Charge(30)); + assert_eq!(nested.max_charged(), Deposit::Charge(50)); + } + + #[test] + fn max_deposits_work_with_diffs() { + clear_ext(); + let meter = TestMeter::new(None); + let mut nested = meter.nested(None); + + nested.charge(&Diff { bytes_added: 2, ..Default::default() }); + + assert_eq!(nested.consumed(), Deposit::Charge(2)); + assert_eq!(nested.max_charged(), Deposit::Charge(2)); + + nested.charge(&Diff { bytes_removed: 1, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(1)); + assert_eq!(nested.max_charged(), Deposit::Charge(2)); + + nested.charge(&Diff { items_added: 10, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(21)); + assert_eq!(nested.max_charged(), Deposit::Charge(21)); + + nested.charge(&Diff { items_removed: 8, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(5)); + assert_eq!(nested.max_charged(), Deposit::Charge(21)); + + nested.charge(&Diff { items_added: 10, bytes_added: 10, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(35)); + assert_eq!(nested.max_charged(), Deposit::Charge(35)); + + nested.charge(&Diff { items_removed: 5, bytes_added: 10, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(35)); + assert_eq!(nested.max_charged(), Deposit::Charge(35)); + + let meter = TestMeter::new(None); + let mut nested = meter.nested(None); + nested.charge(&Diff { bytes_removed: 10, items_added: 2, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(4)); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.charge(&Diff { bytes_added: 5, items_removed: 3, ..Default::default() }); + assert_eq!(nested.consumed(), Default::default()); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.charge(&Diff { bytes_added: 7, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(2)); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.record_charge(&Deposit::Refund(10)); + assert_eq!(nested.consumed(), Deposit::Refund(8)); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.charge(&Diff { bytes_removed: 4, items_added: 2, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Refund(8)); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.charge(&Diff { bytes_added: 20, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(10)); + assert_eq!(nested.max_charged(), Deposit::Charge(10)); + + nested.record_charge(&Deposit::Refund(20)); + assert_eq!(nested.consumed(), Deposit::Refund(10)); + assert_eq!(nested.max_charged(), Deposit::Charge(10)); + } + + #[test] + fn max_deposits_work_nested() { + clear_ext(); + let mut meter = TestMeter::new(None); + let mut nested1 = meter.nested(None); + nested1.record_charge(&Deposit::Charge(10)); + + let mut nested2a = nested1.nested(None); + nested2a.record_charge(&Deposit::Charge(20)); + nested2a.record_charge(&Deposit::Refund(10)); + assert_eq!(nested2a.consumed(), Deposit::Charge(10)); + assert_eq!(nested2a.max_charged(), Deposit::Charge(20)); + + nested2a.charge(&Diff { bytes_removed: 20, items_removed: 10, ..Default::default() }); + assert_eq!(nested2a.consumed(), Deposit::Charge(10)); + assert_eq!(nested2a.max_charged(), Deposit::Charge(20)); + + nested2a.charge(&Diff { bytes_added: 15, items_added: 16, ..Default::default() }); + assert_eq!(nested2a.consumed(), Deposit::Charge(22)); + assert_eq!(nested2a.max_charged(), Deposit::Charge(22)); + + let mut nested2a_info = new_info(StorageInfo { + bytes: 100, + items: 100, + bytes_deposit: 100, + items_deposit: 100, + immutable_data_len: 0, + }); + nested1.absorb(nested2a, &BOB, Some(&mut nested2a_info)); + assert_eq!(nested1.consumed(), Deposit::Charge(27)); + assert_eq!(nested1.max_charged(), Deposit::Charge(32)); + + nested1.charge(&Diff { bytes_added: 10, ..Default::default() }); + assert_eq!(nested1.consumed(), Deposit::Charge(37)); + assert_eq!(nested1.max_charged(), Deposit::Charge(37)); + + nested1.record_charge(&Deposit::Refund(10)); + assert_eq!(nested1.consumed(), Deposit::Charge(27)); + assert_eq!(nested1.max_charged(), Deposit::Charge(37)); + + let mut nested2b = nested1.nested(None); + nested2b.record_charge(&Deposit::Refund(10)); + assert_eq!(nested2b.consumed(), Deposit::Refund(10)); + assert_eq!(nested2b.max_charged(), Default::default()); + + nested2b.charge(&Diff { bytes_added: 10, items_added: 10, ..Default::default() }); + assert_eq!(nested2b.consumed(), Deposit::Charge(20)); + assert_eq!(nested2b.max_charged(), Deposit::Charge(20)); + + nested2b.charge(&Diff { bytes_removed: 20, items_removed: 20, ..Default::default() }); + assert_eq!(nested2b.consumed(), Deposit::Refund(10)); + assert_eq!(nested2b.max_charged(), Deposit::Charge(20)); + + let mut nested2b_info = new_info(StorageInfo { + bytes: 100, + items: 100, + bytes_deposit: 100, + items_deposit: 100, + immutable_data_len: 0, + }); + nested1.absorb(nested2b, &BOB, Some(&mut nested2b_info)); + assert_eq!(nested1.consumed(), Deposit::Refund(3)); + assert_eq!(nested1.max_charged(), Deposit::Charge(47)); + + meter.absorb(nested1, &ALICE, None); + assert_eq!(meter.consumed(), Deposit::Refund(3)); + assert_eq!(meter.max_charged(), Deposit::Charge(47)); + } } diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs new file mode 100644 index 0000000000000..4ad6ca9f2936c --- /dev/null +++ b/substrate/frame/revive/src/metering/tests.rs @@ -0,0 +1,70 @@ +// 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. + +use crate::{ + test_utils::{builder::Contract, ALICE}, + tests::{builder, ExtBuilder, Test}, + Code, Config, StorageDeposit, +}; +use alloy_core::sol_types::SolCall; +use frame_support::traits::fungible::Mutate; +use pallet_revive_fixtures::{compile_module_with_type, Deposit, FixtureType}; +use test_case::test_case; + +#[test_case(FixtureType::Solc ; "solc")] +#[test_case(FixtureType::Resolc ; "resolc")] +fn max_consumed_deposit_integration(fixture_type: FixtureType) { + let (code, _) = compile_module_with_type("Deposit", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(caller_addr).data(Deposit::dCall {}.abi_encode()).build(); + + assert_eq!(result.storage_deposit, StorageDeposit::Charge(66)); + assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); + }); +} + +#[ignore = "TODO: Does not work yet, see https://github.com/paritytech/contract-issues/issues/213"] +#[test_case(FixtureType::Solc ; "solc")] +#[test_case(FixtureType::Resolc ; "resolc")] +fn max_consumed_deposit_integration_refunds_subframes(fixture_type: FixtureType) { + let (code, _) = compile_module_with_type("Deposit", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(caller_addr).data(Deposit::cCall {}.abi_encode()).build(); + + assert_eq!(result.storage_deposit, StorageDeposit::Charge(66)); + assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); + + builder::bare_call(caller_addr).data(Deposit::clearCall {}.abi_encode()).build(); + + let result = builder::bare_call(caller_addr).data(Deposit::eCall {}.abi_encode()).build(); + + assert_eq!(result.storage_deposit, StorageDeposit::Charge(66)); + assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); + }); +} diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index eeebcf15eaee1..649bde2dbe067 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -20,7 +20,7 @@ use crate::{mock::MockHandler, storage::WriteOutcome, BalanceOf, Config, H160, U256}; use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::weights::Weight; +use frame_support::{traits::tokens::Balance, weights::Weight}; use pallet_revive_uapi::ReturnFlags; use scale_info::TypeInfo; use sp_core::Get; @@ -61,17 +61,35 @@ pub struct ContractResult { /// is `Err`. This is because on error all storage changes are rolled back including the /// payment of the deposit. pub storage_deposit: StorageDeposit, + /// The maximual storage deposit amount that occured at any time during the execution. + /// This can be higher than the final storage_deposit due to refunds + /// This is always a StorageDeposit::Charge(..) + pub max_storage_deposit: StorageDeposit, /// The execution result of the vm binary code. pub result: Result, } +impl Default for ContractResult { + fn default() -> Self { + Self { + weight_consumed: Default::default(), + weight_required: Default::default(), + storage_deposit: Default::default(), + max_storage_deposit: Default::default(), + result: Ok(Default::default()), + } + } +} + /// The result of the execution of a `eth_transact` call. #[derive(Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct EthTransactInfo { /// The amount of weight that was necessary to execute the transaction. pub weight_required: Weight, - /// Storage deposit charged. + /// Final storage deposit charged. pub storage_deposit: Balance, + /// Maximal storage deposit charged at any time during execution. + pub max_storage_deposit: Balance, /// The weight and deposit equivalent in EVM Gas. pub eth_gas: U256, /// The execution return value. @@ -192,7 +210,7 @@ impl ExecReturnValue { } /// The result of a successful contract instantiation. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Default)] pub struct InstantiateReturnValue { /// The output of the called constructor. pub result: ExecReturnValue, From a96a66f663e3f6a7d27d34068f3647019694eb5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 4 Nov 2025 05:11:11 -0300 Subject: [PATCH 07/69] Use SignedGas instead of StorageDeposit --- substrate/frame/revive/src/metering/math.rs | 374 +++++++++++++++ substrate/frame/revive/src/metering/mod.rs | 475 +++++--------------- substrate/frame/revive/src/primitives.rs | 100 +++++ 3 files changed, 574 insertions(+), 375 deletions(-) create mode 100644 substrate/frame/revive/src/metering/math.rs diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs new file mode 100644 index 0000000000000..42377d4d536ae --- /dev/null +++ b/substrate/frame/revive/src/metering/math.rs @@ -0,0 +1,374 @@ +// 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. + +use super::{ + BalanceOf, CallResources, Config, DispatchError, Error, EthTxInfo, FixedPointNumber, FixedU128, + FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, Saturating, SignedGas, + State, StorageDeposit, TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, +}; +use core::marker::PhantomData; + +pub mod substrate_execution { + use super::*; + + pub fn new_root( + weight_limit: Weight, + deposit_limit: BalanceOf, + ) -> Result, DispatchError> { + Ok(TransactionMeter { + weight: WeightMeter::new(Some(weight_limit)), + deposit: RootStorageMeter::new(Some(deposit_limit)), + // ignore max total gas for Substrate executions + max_total_gas: Default::default(), + total_consumed_weight_before: Default::default(), + total_consumed_deposit_before: Default::default(), + transaction_limits: TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit }, + _phantom: PhantomData, + }) + } + + pub fn new_nested_meter( + meter: &ResourceMeter, + limit: &CallResources, + ) -> Result, DispatchError> { + let self_consumed_weight = meter.weight.weight_consumed(); + let self_consumed_deposit = meter.deposit.consumed(); + + let total_consumed_weight = + meter.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + let (nested_weight_limit, nested_deposit_limit) = { + let weight_left = meter + .weight + .weight_limit + .expect("Weight limits all always defined for WeightAndDeposit; qed") + .checked_sub(&self_consumed_weight) + .ok_or(>::OutOfGas)?; + + let deposit_limit = meter + .deposit + .limit + .expect("Deposit limits all always defined for WeightAndDeposit; qed"); + let deposit_left = self_consumed_deposit + .available(&deposit_limit) + .ok_or(>::StorageDepositLimitExhausted)?; + + match limit { + CallResources::NoLimits => (weight_left, deposit_left), + + CallResources::Ethereum(gas) => { + let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); + let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(deposit_left); + let gas_left = weight_gas.saturating_add(deposit_gas); + let gas_limit = gas_left.min(*gas); + + if (gas_left).is_zero() { + (weight_left, deposit_left) + } else { + let ratio = FixedU128::from_rational( + gas_limit.saturated_into(), + gas_left.saturated_into(), + ); + + let weight_limit = Weight::from_parts( + ratio.saturating_mul_int(weight_left.ref_time()), + ratio.saturating_mul_int(weight_left.proof_size()), + ); + let deposit_limit = ratio.saturating_mul_int(deposit_left); + + (weight_limit, deposit_limit) + } + }, + + CallResources::Precise { weight, deposit_limit } => + (weight_left.min(*weight), deposit_left.min(*deposit_limit)), + } + }; + + Ok(FrameMeter:: { + weight: WeightMeter::new(Some(nested_weight_limit)), + deposit: meter.deposit.nested(Some(nested_deposit_limit)), + max_total_gas: Default::default(), + total_consumed_weight_before: total_consumed_weight, + total_consumed_deposit_before: total_consumed_deposit, + transaction_limits: meter.transaction_limits.clone(), + _phantom: PhantomData, + }) + } + + pub fn eth_gas_left(meter: &ResourceMeter) -> Option> { + match (meter.weight_left(), meter.deposit_left()) { + (Some(weight_left), Some(deposit_left)) => { + let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); + let deposit_gas = + T::FeeInfo::next_fee_multiplier_reciprocal().saturating_mul_int(deposit_left); + + Some(weight_gas.saturating_add(deposit_gas)) + }, + _ => None, + } + } + + pub fn weight_left(meter: &ResourceMeter) -> Option { + let weight_limit = meter + .weight + .weight_limit + .expect("Weight limits all always defined for WeightAndDeposit; qed"); + weight_limit.checked_sub(&meter.weight.weight_consumed()) + } + + pub fn deposit_left(meter: &ResourceMeter) -> Option> { + let deposit_limit = meter + .deposit + .limit + .expect("Deposit limits all always defined for WeightAndDeposit; qed"); + meter.deposit.consumed().available(&deposit_limit) + } + + pub fn total_consumed_gas(meter: &ResourceMeter) -> SignedGas { + let self_consumed_weight = meter.weight.weight_consumed(); + let self_consumed_deposit = meter.deposit.consumed(); + + let total_consumed_weight = + meter.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + let consumed_weight_gas = T::FeeInfo::weight_to_fee_average(&total_consumed_weight); + + let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); + let consumed_deposit_gas = match total_consumed_deposit { + StorageDeposit::Charge(amount) => + SignedGas::Positive(multiplier.saturating_mul_int(amount)), + StorageDeposit::Refund(amount) => + SignedGas::Negative(multiplier.saturating_mul_int(amount)), + }; + + consumed_deposit_gas.saturating_add(&SignedGas::Positive(consumed_weight_gas)) + } +} + +pub mod ethereum_execution { + use super::*; + + pub fn new_root( + eth_gas_limit: BalanceOf, + eth_tx_info: EthTxInfo, + ) -> Result, DispatchError> { + let meter = TransactionMeter { + weight: WeightMeter::new(None), + deposit: RootStorageMeter::new(None), + max_total_gas: SignedGas::Positive(eth_gas_limit), + total_consumed_weight_before: Default::default(), + total_consumed_deposit_before: Default::default(), + transaction_limits: TransactionLimits::EthereumGas { eth_gas_limit, eth_tx_info }, + _phantom: PhantomData, + }; + + if meter.eth_gas_left().is_some() { + Ok(meter) + } else { + return Err(>::OutOfGas.into()); + } + } + + pub fn new_nested_meter( + meter: &ResourceMeter, + limit: &CallResources, + eth_tx_info: &EthTxInfo, + ) -> Result, DispatchError> { + let self_consumed_weight = meter.weight.weight_consumed(); + let self_consumed_deposit = meter.deposit.consumed(); + + let total_consumed_weight = + meter.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + let total_gas_consumption = + eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit); + + let Some(gas_left) = + meter.max_total_gas.saturating_sub(&total_gas_consumption).as_positive() + else { + return Err(>::OutOfGas.into()); + }; + + let (nested_gas_limit, nested_weight_limit, nested_deposit_limit) = { + let is_simple = meter.weight.weight_limit.is_none() && + matches!(limit, CallResources::NoLimits | CallResources::Ethereum(..)); + + if is_simple { + let nested_gas_limit = if let CallResources::Ethereum(gas) = limit { + gas_left.min(*gas) + } else { + gas_left + }; + (nested_gas_limit, None, None) + } else { + let weight_left = { + let unbounded_weight_left = eth_tx_info + .weight_remaining( + &meter.max_total_gas, + &total_consumed_weight, + &total_consumed_deposit, + ) + .ok_or(>::OutOfGas)?; + + match meter.weight.weight_limit { + Some(weight_limit) => unbounded_weight_left.min( + weight_limit + .checked_sub(&self_consumed_weight) + .ok_or(>::OutOfGas)?, + ), + None => unbounded_weight_left, + } + }; + + let deposit_left = { + let unbounded_deposit_left: BalanceOf = + T::FeeInfo::next_fee_multiplier().saturating_mul_int(gas_left); + match meter.deposit.limit { + Some(deposit_limit) => unbounded_deposit_left.min( + self_consumed_deposit + .available(&deposit_limit) + .ok_or(>::StorageDepositLimitExhausted)?, + ), + None => unbounded_deposit_left, + } + }; + + match limit { + CallResources::NoLimits => (gas_left, Some(weight_left), Some(deposit_left)), + CallResources::Ethereum(gas) => + (gas_left.min(*gas), Some(weight_left), Some(deposit_left)), + CallResources::Precise { weight, deposit_limit } => { + let nested_weight_limit = weight_left.min(*weight); + let nested_deposit_limit = deposit_left.min(*deposit_limit); + + let new_max_total_gas = eth_tx_info.gas_consumption( + &total_consumed_weight.saturating_add(nested_weight_limit), + &total_consumed_deposit + .saturating_add(&StorageDeposit::Charge(nested_deposit_limit)), + ); + + let Some(gas_limit) = + new_max_total_gas.saturating_sub(&total_gas_consumption).as_positive() + else { + return Err(>::OutOfGas.into()); + }; + + ( + gas_left.min(gas_limit), + Some(nested_weight_limit), + Some(nested_deposit_limit), + ) + }, + } + } + }; + + let nested_max_total_gas = + total_gas_consumption.saturating_add(&SignedGas::Positive(nested_gas_limit)); + + Ok(FrameMeter:: { + weight: WeightMeter::new(nested_weight_limit), + deposit: meter.deposit.nested(nested_deposit_limit), + max_total_gas: nested_max_total_gas, + total_consumed_weight_before: total_consumed_weight, + total_consumed_deposit_before: total_consumed_deposit, + transaction_limits: meter.transaction_limits.clone(), + _phantom: PhantomData, + }) + } + + pub fn eth_gas_left( + meter: &ResourceMeter, + eth_tx_info: &EthTxInfo, + ) -> Option> { + let self_consumed_weight = meter.weight.weight_consumed(); + let self_consumed_deposit = meter.deposit.consumed(); + + let total_consumed_weight = + meter.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + let total_gas_consumption = + eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit); + + meter.max_total_gas.saturating_sub(&total_gas_consumption).as_positive() + } + + pub fn weight_left( + meter: &ResourceMeter, + eth_tx_info: &EthTxInfo, + ) -> Option { + let self_consumed_weight = meter.weight.weight_consumed(); + let self_consumed_deposit = meter.deposit.consumed(); + + let total_consumed_weight = + meter.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + let weight_left = eth_tx_info.weight_remaining( + &meter.max_total_gas, + &total_consumed_weight, + &total_consumed_deposit, + )?; + + Some(match meter.weight.weight_limit { + Some(weight_limit) => weight_left.min(weight_limit.checked_sub(&self_consumed_weight)?), + None => weight_left, + }) + } + + pub fn deposit_left( + meter: &ResourceMeter, + eth_tx_info: &EthTxInfo, + ) -> Option> { + let eth_gas_left = eth_gas_left(meter, eth_tx_info)?; + let deposit_left = T::FeeInfo::next_fee_multiplier().saturating_mul_int(eth_gas_left); + + Some(match meter.deposit.limit { + Some(deposit_limit) => { + let deposit_available = meter.deposit.consumed().available(&deposit_limit)?; + deposit_left.min(deposit_available) + }, + None => deposit_left, + }) + } + + pub fn total_consumed_gas( + meter: &ResourceMeter, + eth_tx_info: &EthTxInfo, + ) -> SignedGas { + let self_consumed_weight = meter.weight.weight_consumed(); + let self_consumed_deposit = meter.deposit.consumed(); + + let total_consumed_weight = + meter.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit) + } +} diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 70c1c13557369..cefd00cdad49a 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod math; pub mod storage; pub mod weight; @@ -23,7 +24,7 @@ mod tests; use crate::{ evm::fees::InfoT, exec::CallResources, storage::ContractInfo, vm::evm::Halt, BalanceOf, Config, - Error, ExecConfig, ExecOrigin as Origin, StorageDeposit, LOG_TARGET, + Error, ExecConfig, ExecOrigin as Origin, SignedGas, StorageDeposit, }; use frame_support::{DebugNoBound, DefaultNoBound}; use num_traits::Zero; @@ -61,13 +62,15 @@ mod private { pub type TransactionMeter = ResourceMeter; pub type FrameMeter = ResourceMeter; +/// invariant: either the limits in both meters are both None or both Some(..) +/// they will always be defined if `transaction_limits` is `TransactionLimits::WeightAndDeposit` #[derive(DefaultNoBound)] pub struct ResourceMeter { weight: WeightMeter, deposit: GenericStorageMeter, - eth_gas_limit: BalanceOf, - max_total_gas: DepositOf, + // this is always zero for Substrate executions + max_total_gas: SignedGas, total_consumed_weight_before: Weight, total_consumed_deposit_before: DepositOf, @@ -76,13 +79,6 @@ pub struct ResourceMeter { _phantom: PhantomData, } -#[derive(DebugNoBound, Clone)] -pub struct EthTxInfo { - pub encoded_len: u32, - pub extra_weight: Weight, - _phantom: PhantomData, -} - #[derive(DebugNoBound, Clone)] pub enum TransactionLimits { EthereumGas { eth_gas_limit: BalanceOf, eth_tx_info: EthTxInfo }, @@ -160,182 +156,6 @@ impl ResourceMeter { Ok(()) } - pub fn new_nested(&self, limit: &CallResources) -> Result, DispatchError> { - let self_consumed_weight = self.weight.weight_consumed(); - let self_consumed_deposit = self.deposit.consumed(); - - let total_consumed_weight = - self.total_consumed_weight_before.saturating_add(self_consumed_weight); - let total_consumed_deposit = - self.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); - - let (nested_gas_limit, nested_weight_limit, nested_deposit_limit, max_total_gas) = - match &self.transaction_limits { - TransactionLimits::EthereumGas { eth_tx_info, .. } => { - let max_total_gas = eth_tx_info.max_total_gas( - self.eth_gas_limit, - self.total_consumed_weight_before, - &self.total_consumed_deposit_before, - ); - - let total_gas_consumption = - eth_tx_info.gas_consumption(total_consumed_weight, &total_consumed_deposit); - - let StorageDeposit::Refund(gas_left) = - max_total_gas.saturating_sub(&total_gas_consumption) - else { - return Err(>::OutOfGas.into()); - }; - - if self.weight.weight_limit.is_none() && - matches!(limit, CallResources::NoLimits | CallResources::Ethereum(..)) - { - let nested_gas_limit = if let CallResources::Ethereum(gas) = limit { - gas_left.min(*gas) - } else { - gas_left - }; - (nested_gas_limit, None, None, max_total_gas) - } else { - let weight_left = eth_tx_info - .weight_remaining( - &max_total_gas, - total_consumed_weight, - &total_consumed_deposit, - ) - .ok_or(>::OutOfGas)?; - - let weight_left = match self.weight.weight_limit { - Some(weight_limit) => weight_left.min( - weight_limit - .checked_sub(&self_consumed_weight) - .ok_or(>::OutOfGas)?, - ), - None => weight_left, - }; - - let deposit_left: BalanceOf = - EthTxInfo::::deposit_remaining(gas_left); - let deposit_left = match self.deposit.limit { - Some(deposit_limit) => deposit_left.min( - self_consumed_deposit - .available(&deposit_limit) - .ok_or(>::StorageDepositLimitExhausted)?, - ), - None => deposit_left, - }; - - match limit { - CallResources::NoLimits => - (gas_left, Some(weight_left), Some(deposit_left), max_total_gas), - CallResources::Ethereum(gas) => ( - gas_left.min(*gas), - Some(weight_left), - Some(deposit_left), - max_total_gas, - ), - CallResources::Precise { weight, deposit_limit } => { - let nested_weight_limit = weight_left.min(*weight); - let nested_deposit_limit = deposit_left.min(*deposit_limit); - - let new_max_total_gas = eth_tx_info.gas_consumption( - total_consumed_weight.saturating_add(nested_weight_limit), - &total_consumed_deposit.saturating_add( - &StorageDeposit::Charge(nested_deposit_limit), - ), - ); - - let gas_limit = - new_max_total_gas.saturating_sub(&total_gas_consumption); - let DepositOf::::Refund(gas_limit) = gas_limit else { - return Err(>::OutOfGas.into()); - }; - - ( - gas_left.min(gas_limit), - Some(nested_weight_limit), - Some(nested_deposit_limit), - max_total_gas, - ) - }, - } - } - }, - - TransactionLimits::WeightAndDeposit { .. } => { - let weight_left = self - .weight - .weight_limit - .expect("Weight limits all always defined for WeightAndDeposit; qed") - .checked_sub(&self_consumed_weight) - .ok_or(>::OutOfGas)?; - - let deposit_limit = self - .deposit - .limit - .expect("Deposit limits all always defined for WeightAndDeposit; qed"); - let deposit_left = self_consumed_deposit - .available(&deposit_limit) - .ok_or(>::StorageDepositLimitExhausted)?; - - match limit { - CallResources::NoLimits => ( - Default::default(), - Some(weight_left), - Some(deposit_left), - Default::default(), - ), - - CallResources::Ethereum(gas) => { - let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); - let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(deposit_left); - let gas_left = weight_gas.saturating_add(deposit_gas); - if (gas_left).is_zero() { - Err(>::OutOfGas)?; - } - - let ratio = FixedU128::from_rational( - gas_left.min(*gas).saturated_into(), - gas_left.saturated_into(), - ); - - let weight_limit = Weight::from_parts( - ratio.saturating_mul_int(weight_left.ref_time()), - ratio.saturating_mul_int(weight_left.proof_size()), - ); - let deposit_limit = ratio.saturating_mul_int(deposit_left); - - ( - Default::default(), - Some(weight_limit), - Some(deposit_limit), - Default::default(), - ) - }, - - CallResources::Precise { weight, deposit_limit } => ( - Default::default(), - Some(weight_left.min(*weight)), - Some(deposit_left.min(*deposit_limit)), - Default::default(), - ), - } - }, - }; - - Ok(FrameMeter:: { - weight: WeightMeter::new(nested_weight_limit), - deposit: self.deposit.nested(nested_deposit_limit), - eth_gas_limit: nested_gas_limit, - max_total_gas, - total_consumed_weight_before: total_consumed_weight, - total_consumed_deposit_before: total_consumed_deposit, - transaction_limits: self.transaction_limits.clone(), - _phantom: PhantomData, - }) - } - pub fn absorb_weight_meter_only(&mut self, other: FrameMeter) { self.weight.absorb_nested(other.weight); } @@ -350,115 +170,53 @@ impl ResourceMeter { self.deposit.absorb(other.deposit, contract, info); } + pub fn new_nested(&self, limit: &CallResources) -> Result, DispatchError> { + match &self.transaction_limits { + TransactionLimits::EthereumGas { eth_tx_info, .. } => + math::ethereum_execution::new_nested_meter(self, limit, eth_tx_info), + TransactionLimits::WeightAndDeposit { .. } => + math::substrate_execution::new_nested_meter(self, limit), + } + } + pub fn eth_gas_left(&self) -> Option> { match &self.transaction_limits { - TransactionLimits::EthereumGas { eth_tx_info, .. } => { - let self_consumed_weight = self.weight.weight_consumed(); - let self_consumed_deposit = self.deposit.consumed(); - - let total_consumed_weight = - self.total_consumed_weight_before.saturating_add(self_consumed_weight); - let total_consumed_deposit = - self.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); - - let total_gas_consumption = - eth_tx_info.gas_consumption(total_consumed_weight, &total_consumed_deposit); - - match self.max_total_gas.saturating_sub(&total_gas_consumption) { - StorageDeposit::Refund(gas_left) => Some(gas_left), - StorageDeposit::Charge(_) => { - log::debug!( target: LOG_TARGET, "Eth gas limit exhausted: {:?} > {:?}", total_gas_consumption, self.max_total_gas); - None - }, - } - }, - - TransactionLimits::WeightAndDeposit { .. } => { - match (self.weight_left(), self.deposit_left()) { - (Some(weight_left), Some(deposit_left)) => { - let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); - let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(deposit_left); - - Some(weight_gas.saturating_add(deposit_gas)) - }, - _ => None, - } - }, + TransactionLimits::EthereumGas { eth_tx_info, .. } => + math::ethereum_execution::eth_gas_left(self, eth_tx_info), + TransactionLimits::WeightAndDeposit { .. } => + math::substrate_execution::eth_gas_left(self), } } pub fn weight_left(&self) -> Option { match &self.transaction_limits { - TransactionLimits::EthereumGas { eth_tx_info, .. } => { - let self_consumed_weight = self.weight.weight_consumed(); - let self_consumed_deposit = self.deposit.consumed(); - - let total_consumed_weight = - self.total_consumed_weight_before.saturating_add(self_consumed_weight); - let total_consumed_deposit = - self.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); - - let weight_left = eth_tx_info.weight_remaining( - &self.max_total_gas, - total_consumed_weight, - &total_consumed_deposit, - )?; - - Some(match self.weight.weight_limit { - Some(weight_limit) => - weight_left.min(weight_limit.checked_sub(&self_consumed_weight)?), - None => weight_left, - }) - }, - - TransactionLimits::WeightAndDeposit { .. } => { - let weight_limit = self - .weight - .weight_limit - .expect("Weight limits all always defined for WeightAndDeposit; qed"); - weight_limit.checked_sub(&self.weight.weight_consumed()) - }, + TransactionLimits::EthereumGas { eth_tx_info, .. } => + math::ethereum_execution::weight_left(self, eth_tx_info), + TransactionLimits::WeightAndDeposit { .. } => + math::substrate_execution::weight_left(self), } } pub fn deposit_left(&self) -> Option> { match &self.transaction_limits { - TransactionLimits::EthereumGas { .. } => { - let eth_gas_left = self.eth_gas_left()?; - let deposit_left = EthTxInfo::::deposit_remaining(eth_gas_left); - - match self.deposit.limit { - Some(deposit_limit) => { - let deposit_available = self.deposit.consumed().available(&deposit_limit); - let Some(deposit_available) = deposit_available else { - log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", self.deposit.consumed(), deposit_limit); - return None; - }; - - Some(deposit_left.min(deposit_available)) - }, - None => Some(deposit_left), - } - }, - - TransactionLimits::WeightAndDeposit { .. } => { - let deposit_limit = self - .deposit - .limit - .expect("Deposit limits all always defined for WeightAndDeposit; qed"); - let deposit_available = self.deposit.consumed().available(&deposit_limit); - - if deposit_available.is_none() { - log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", self.deposit.consumed(), deposit_limit); - return None; - } - - deposit_available - }, + TransactionLimits::EthereumGas { eth_tx_info, .. } => + math::ethereum_execution::deposit_left(self, eth_tx_info), + TransactionLimits::WeightAndDeposit { .. } => + math::substrate_execution::deposit_left(self), } } + pub fn total_consumed_gas(&self) -> BalanceOf { + let signed_gas = match &self.transaction_limits { + TransactionLimits::EthereumGas { eth_tx_info, .. } => + math::ethereum_execution::total_consumed_gas(self, eth_tx_info), + TransactionLimits::WeightAndDeposit { .. } => + math::substrate_execution::total_consumed_gas(self), + }; + + signed_gas.as_positive().unwrap_or_default() + } + pub fn weight_consumed(&self) -> Weight { self.weight.weight_consumed() } @@ -476,103 +234,13 @@ impl ResourceMeter { } } -impl EthTxInfo { - pub fn new(encoded_len: u32, extra_weight: Weight) -> Self { - Self { encoded_len, extra_weight, _phantom: PhantomData } - } - - pub fn gas_consumption( - &self, - consumed_weight: Weight, - consumed_deposit: &DepositOf, - ) -> DepositOf { - let fee_a = StorageDeposit::Refund(T::FeeInfo::fixed_fee(self.encoded_len)) - .saturating_sub(consumed_deposit) - .scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()); - - let fee_b = T::FeeInfo::weight_to_fee(&consumed_weight.saturating_add(self.extra_weight)); - - fee_a.saturating_add(&StorageDeposit::Refund(fee_b)) - } - - pub fn gas_remaining( - max_total_gas: &DepositOf, - total_gas_consumption: &DepositOf, - ) -> Option> { - match max_total_gas.saturating_sub(total_gas_consumption) { - StorageDeposit::Refund(amount) => Some(amount), - StorageDeposit::Charge(_) => None, - } - } - - pub fn max_total_gas( - &self, - eth_gas_limit: BalanceOf, - total_consumed_weight_before: Weight, - total_consumed_deposit_before: &DepositOf, - ) -> DepositOf { - self.gas_consumption(total_consumed_weight_before, total_consumed_deposit_before) - .saturating_add(&StorageDeposit::Refund(eth_gas_limit)) - } - - pub fn weight_remaining( - &self, - max_total_gas: &DepositOf, - total_weight_consumption: Weight, - total_deposit_consumption: &DepositOf, - ) -> Option { - let consumable_fee = max_total_gas.saturating_add( - &total_deposit_consumption - .saturating_add(&StorageDeposit::Charge(T::FeeInfo::fixed_fee(self.encoded_len))) - .scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()), - ); - - let StorageDeposit::Refund(consumable_fee) = consumable_fee else { - return None; - }; - - T::FeeInfo::fee_to_weight(consumable_fee) - .checked_sub(&total_weight_consumption.saturating_add(self.extra_weight)) - } - - pub fn deposit_remaining(gas_remaining: BalanceOf) -> BalanceOf { - T::FeeInfo::next_fee_multiplier().saturating_mul_int(gas_remaining) - } -} - impl TransactionMeter { pub fn new(transaction_limits: TransactionLimits) -> Result { - match &transaction_limits { - TransactionLimits::EthereumGas { eth_gas_limit, eth_tx_info } => { - let base_gas = - eth_tx_info.gas_consumption(Weight::default(), &DepositOf::::default()); - - let Some(gas_limit) = base_gas.available(ð_gas_limit) else { - return Err(>::StorageDepositNotEnoughFunds.into()); - }; - - Ok(Self { - weight: WeightMeter::new(None), - deposit: RootStorageMeter::new(None), - eth_gas_limit: gas_limit, - max_total_gas: StorageDeposit::Refund(*eth_gas_limit), - total_consumed_weight_before: Weight::default(), - total_consumed_deposit_before: DepositOf::::default(), - transaction_limits, - _phantom: PhantomData, - }) - }, - - TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } => Ok(Self { - weight: WeightMeter::new(Some(*weight_limit)), - deposit: RootStorageMeter::new(Some(*deposit_limit)), - eth_gas_limit: Default::default(), // ignore eth gas limit for Substrate executions - max_total_gas: Default::default(), // ignore eth gas limit for Substrate executions - total_consumed_weight_before: Weight::default(), - total_consumed_deposit_before: DepositOf::::default(), - transaction_limits, - _phantom: PhantomData, - }), + match transaction_limits { + TransactionLimits::EthereumGas { eth_gas_limit, eth_tx_info } => + math::ethereum_execution::new_root(eth_gas_limit, eth_tx_info), + TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } => + math::substrate_execution::new_root(weight_limit, deposit_limit), } } @@ -634,3 +302,60 @@ impl FrameMeter { Ok(()) } } + +#[derive(DebugNoBound, Clone)] +pub struct EthTxInfo { + pub encoded_len: u32, + pub extra_weight: Weight, + _phantom: PhantomData, +} + +impl EthTxInfo { + pub fn new(encoded_len: u32, extra_weight: Weight) -> Self { + Self { encoded_len, extra_weight, _phantom: PhantomData } + } + + pub fn gas_consumption( + &self, + consumed_weight: &Weight, + consumed_deposit: &DepositOf, + ) -> SignedGas { + let deposit_gas = SignedGas::from_deposit_charge(consumed_deposit); + let fixed_fee_gas = SignedGas::Positive(T::FeeInfo::fixed_fee(self.encoded_len)); + let scaled_gas = (deposit_gas.saturating_add(&fixed_fee_gas)) + .scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()); + + let weight_fee = SignedGas::Positive(T::FeeInfo::weight_to_fee( + &consumed_weight.saturating_add(self.extra_weight), + )); + + scaled_gas.saturating_add(&weight_fee) + } + + pub fn gas_remaining( + max_total_gas: &SignedGas, + total_gas_consumption: &SignedGas, + ) -> Option> { + max_total_gas.saturating_sub(total_gas_consumption).as_positive() + } + + pub fn weight_remaining( + &self, + max_total_gas: &SignedGas, + total_weight_consumption: &Weight, + total_deposit_consumption: &DepositOf, + ) -> Option { + let numerator = SignedGas::from_deposit_charge(total_deposit_consumption) + .saturating_add(&SignedGas::Positive(T::FeeInfo::fixed_fee(self.encoded_len))); + let consumable_fee = max_total_gas.saturating_sub( + &numerator.scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()), + ); + + let SignedGas::Positive(consumable_fee) = consumable_fee else { + return None; + }; + + T::FeeInfo::fee_to_weight(consumable_fee) + .checked_sub(&total_weight_consumption.saturating_add(self.extra_weight)) + } +} diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 649bde2dbe067..5f1b259c6816f 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -353,6 +353,106 @@ where } } +/// The type for Ethereum gas. We need to deal with negative and positive values and the structure +/// of this type resembles `StorageDeposit` but the enum variants have a more obvious name to avoid +/// confusion and errors +#[derive( + Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo, +)] +pub enum SignedGas { + /// Positive gas amount + Positive(BalanceOf), + /// Negative gas amount + Negative(BalanceOf), +} + +impl Default for SignedGas { + fn default() -> Self { + Self::Positive(Default::default()) + } +} + +impl SignedGas { + /// This is essentially a saturating signed add. + pub fn saturating_add(&self, rhs: &Self) -> Self { + use SignedGas::*; + match (self, rhs) { + (Positive(lhs), Positive(rhs)) => Positive(lhs.saturating_add(*rhs)), + (Negative(lhs), Negative(rhs)) => Negative(lhs.saturating_add(*rhs)), + (Positive(lhs), Negative(rhs)) => + if lhs >= rhs { + Positive(lhs.saturating_sub(*rhs)) + } else { + Negative(rhs.saturating_sub(*lhs)) + }, + (Negative(lhs), Positive(rhs)) => + if lhs > rhs { + Negative(lhs.saturating_sub(*rhs)) + } else { + Positive(rhs.saturating_sub(*lhs)) + }, + } + } + + /// This is essentially a saturating signed sub. + pub fn saturating_sub(&self, rhs: &Self) -> Self { + use SignedGas::*; + match (self, rhs) { + (Positive(lhs), Negative(rhs)) => Positive(lhs.saturating_add(*rhs)), + (Negative(lhs), Positive(rhs)) => Negative(lhs.saturating_add(*rhs)), + (Positive(lhs), Positive(rhs)) => + if lhs >= rhs { + Positive(lhs.saturating_sub(*rhs)) + } else { + Negative(rhs.saturating_sub(*lhs)) + }, + (Negative(lhs), Negative(rhs)) => + if lhs > rhs { + Negative(lhs.saturating_sub(*rhs)) + } else { + Positive(rhs.saturating_sub(*lhs)) + }, + } + } + + /// transform a storage deposit into a gas value and treat a charge as a positive number + pub fn from_deposit_charge(deposit: &StorageDeposit>) -> Self { + use SignedGas::*; + match deposit { + StorageDeposit::Charge(amount) => Positive(*amount), + StorageDeposit::Refund(amount) if *amount == Default::default() => Positive(*amount), + StorageDeposit::Refund(amount) => Negative(*amount), + } + } + + /// transform a storage deposit into a gas value and treat a refund as a positive number + pub fn from_deposit_refund(deposit: &StorageDeposit>) -> Self { + use SignedGas::*; + match deposit { + StorageDeposit::Refund(amount) => Positive(*amount), + StorageDeposit::Charge(amount) if *amount == Default::default() => Positive(*amount), + StorageDeposit::Charge(amount) => Negative(*amount), + } + } + + /// Scale this scaled gas value by a `FixedU128` factor + pub fn scale_by_factor(&self, rhs: &FixedU128) -> Self { + use SignedGas::*; + match self { + Positive(amount) => Positive(rhs.saturating_mul_int(*amount)), + Negative(amount) => Negative(rhs.saturating_mul_int(*amount)), + } + } + + pub fn as_positive(&self) -> Option> { + use SignedGas::*; + match self { + Positive(amount) => Some(*amount), + Negative(_amount) => None, + } + } +} + /// `Stack` wide configuration options. pub struct ExecConfig { /// Indicates whether the account nonce should be incremented after instantiating a new From 11b57ced8952db76fe5b74e2d6239297e3f20e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 4 Nov 2025 06:33:25 -0300 Subject: [PATCH 08/69] Add doc comments --- substrate/frame/revive/src/exec.rs | 13 +- substrate/frame/revive/src/metering/math.rs | 68 +++++++++- substrate/frame/revive/src/metering/mod.rs | 130 +++++++++++++++++--- substrate/frame/revive/src/primitives.rs | 19 +-- 4 files changed, 190 insertions(+), 40 deletions(-) diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index a0284481a71bb..8b870ef948602 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -201,16 +201,21 @@ pub enum CallResources { /// Resources are not limited NoLimits, /// Resources encoded using their actual values. - Precise { weight: Weight, deposit_limit: BalanceOf }, + WeightDeposit { weight: Weight, deposit_limit: BalanceOf }, /// Resources encoded as unified ethereum gas. Ethereum(BalanceOf), } impl CallResources { + /// Creates a new `CallResources` with weight and deposit limits. pub fn from_weight_and_deposit(weight: Weight, deposit_limit: U256) -> Self { - Self::Precise { weight, deposit_limit: deposit_limit.saturated_into::>() } + Self::WeightDeposit { + weight, + deposit_limit: deposit_limit.saturated_into::>(), + } } + /// Creates a new `CallResources` from Ethereum gas limits. pub fn from_ethereum_gas(gas: U256) -> Self { Self::Ethereum(gas.saturated_into::>()) } @@ -218,7 +223,7 @@ impl CallResources { impl Default for CallResources { fn default() -> Self { - Self::Precise { weight: Default::default(), deposit_limit: Default::default() } + Self::WeightDeposit { weight: Default::default(), deposit_limit: Default::default() } } } @@ -295,6 +300,8 @@ pub trait PrecompileExt: sealing::Sealed { self.gas_meter_mut().charge_weight_token(RuntimeCosts::Precompile(weight)) } + /// Reconcile an earlier gas charge with the actual weight consumed. + /// This updates the current weight meter to reflect the real cost of the token. fn adjust_gas(&mut self, charged: ChargedAmount, actual_weight: Weight) { self.gas_meter_mut() .adjust_weight(charged, RuntimeCosts::Precompile(actual_weight)); diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index 42377d4d536ae..8541305ac8a33 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -25,6 +25,13 @@ use core::marker::PhantomData; pub mod substrate_execution { use super::*; + /// Create a transaction-level (root) meter for Substrate-style execution. + /// + /// This constructs the root resource meter that enforces explicit weight and + /// storage-deposit limits for the whole transaction. The returned `TransactionMeter`: + /// - charges weight via `WeightMeter` with the provided `weight_limit`, + /// - accounts storage deposit via `RootStorageMeter` with the provided `deposit_limit`, + /// - records that the transaction's limit mode is `WeightAndDeposit`. pub fn new_root( weight_limit: Weight, deposit_limit: BalanceOf, @@ -41,6 +48,18 @@ pub mod substrate_execution { }) } + /// Create a nested (frame) meter derived from a parent `ResourceMeter`. + /// + /// This produces a frame-local meter that enforces the resource limits for + /// a nested call. It computes how much of the parent's remaining resources are available + /// to the nested frame by: + /// - collecting the parent's own consumed amounts (`self_consumed_*`), + /// - deriving the total consumed amounts up to this point, + /// - applying the requested `CallResources` (no limits, ethereum gas conversion, or explicit + /// weight+deposit) to derive per-frame limits. + /// + /// Returns `Err(Error::OutOfGas)` when weight is exhausted, or + /// `Err(Error::StorageDepositLimitExhausted)` when deposit bookkeeping forbids further storage. pub fn new_nested_meter( meter: &ResourceMeter, limit: &CallResources, @@ -73,6 +92,10 @@ pub mod substrate_execution { CallResources::NoLimits => (weight_left, deposit_left), CallResources::Ethereum(gas) => { + // Convert leftover weight and deposit to an ethereum-gas equivalent, + // then cap that gas by the requested `gas`. Distribute the capped gas + // back into weight and deposit portions using the same ratio so that + // the nested frame receives proportional limits. let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() .saturating_mul_int(deposit_left); @@ -97,7 +120,9 @@ pub mod substrate_execution { } }, - CallResources::Precise { weight, deposit_limit } => + CallResources::WeightDeposit { weight, deposit_limit } => + // when explicit weight+deposit requested, take the minimum of parent's left + // and the requested per-call limits. (weight_left.min(*weight), deposit_left.min(*deposit_limit)), } }; @@ -113,6 +138,10 @@ pub mod substrate_execution { }) } + /// Compute the remaining ethereum-gas-equivalent for a Substrate-style transaction. + /// + /// Converts the remaining weight and deposit into their gas-equivalents (via `FeeInfo`) and + /// returns the sum. Returns `None` if either component there is not enough as left pub fn eth_gas_left(meter: &ResourceMeter) -> Option> { match (meter.weight_left(), meter.deposit_left()) { (Some(weight_left), Some(deposit_left)) => { @@ -126,6 +155,9 @@ pub mod substrate_execution { } } + /// Return remaining weight available in the given meter. + /// + /// Subtracts the weight already consumed in the current frame from the configured limit. pub fn weight_left(meter: &ResourceMeter) -> Option { let weight_limit = meter .weight @@ -134,6 +166,10 @@ pub mod substrate_execution { weight_limit.checked_sub(&meter.weight.weight_consumed()) } + /// Return remaining deposit available to the given meter. + /// + /// Subtracts the storage deposit already consumed in the current frame from the configured + /// limit. pub fn deposit_left(meter: &ResourceMeter) -> Option> { let deposit_limit = meter .deposit @@ -142,6 +178,10 @@ pub mod substrate_execution { meter.deposit.consumed().available(&deposit_limit) } + /// Compute the total consumed gas (signed) for Substrate-style execution. + /// + /// This returns a `SignedGas` as the consumed gas can be negative (when there are major storage + /// deposit refunds) pub fn total_consumed_gas(meter: &ResourceMeter) -> SignedGas { let self_consumed_weight = meter.weight.weight_consumed(); let self_consumed_deposit = meter.deposit.consumed(); @@ -168,6 +208,12 @@ pub mod substrate_execution { pub mod ethereum_execution { use super::*; + /// Create a transaction-level (root) meter for Ethereum-style execution. + /// + /// This constructs a root `TransactionMeter` where the global limit is an + /// ethereum-gas budget (`max_total_gas`). Weight and deposit meters are left unbounded + /// (None). The function validates that there is positive gas left after initialization, + /// otherwise it returns an error. pub fn new_root( eth_gas_limit: BalanceOf, eth_tx_info: EthTxInfo, @@ -189,6 +235,17 @@ pub mod ethereum_execution { } } + /// Create a nested (frame) meter for an Ethereum-style execution. + /// + /// - computes the gas already consumed by the transaction and determines how much gas is left, + /// - if the parent is in a simple gas-only mode, returns a child meter that is limited only by + /// gas (no per-frame weight/deposit limits), + /// - otherwise computes concrete nested weight/deposit limits derived from the remaining + /// ethereum gas + /// + /// The function ensures the nested frame's derived gas+resources remain within the parent's + /// remaining budget and returns `Err(Error::OutOfGas)` when the derived limits would exhaust + /// available resources. pub fn new_nested_meter( meter: &ResourceMeter, limit: &CallResources, @@ -212,6 +269,8 @@ pub mod ethereum_execution { }; let (nested_gas_limit, nested_weight_limit, nested_deposit_limit) = { + // In the simple case the parent has no explicit weight and storage deposit limits and + // the requested CallResources are gas-only; then nested frames only need a gas cap. let is_simple = meter.weight.weight_limit.is_none() && matches!(limit, CallResources::NoLimits | CallResources::Ethereum(..)); @@ -223,6 +282,7 @@ pub mod ethereum_execution { }; (nested_gas_limit, None, None) } else { + // More complex path: derive a concrete weight_left and deposit_left. let weight_left = { let unbounded_weight_left = eth_tx_info .weight_remaining( @@ -259,7 +319,7 @@ pub mod ethereum_execution { CallResources::NoLimits => (gas_left, Some(weight_left), Some(deposit_left)), CallResources::Ethereum(gas) => (gas_left.min(*gas), Some(weight_left), Some(deposit_left)), - CallResources::Precise { weight, deposit_limit } => { + CallResources::WeightDeposit { weight, deposit_limit } => { let nested_weight_limit = weight_left.min(*weight); let nested_deposit_limit = deposit_left.min(*deposit_limit); @@ -299,6 +359,7 @@ pub mod ethereum_execution { }) } + /// Compute remaining ethereum gas for an Ethereum-style execution. pub fn eth_gas_left( meter: &ResourceMeter, eth_tx_info: &EthTxInfo, @@ -317,6 +378,7 @@ pub mod ethereum_execution { meter.max_total_gas.saturating_sub(&total_gas_consumption).as_positive() } + /// Return the remaining weight available to a nested frame under Ethereum-style execution. pub fn weight_left( meter: &ResourceMeter, eth_tx_info: &EthTxInfo, @@ -341,6 +403,7 @@ pub mod ethereum_execution { }) } + /// Return remaining deposit available to a nested frame under Ethereum-style execution. pub fn deposit_left( meter: &ResourceMeter, eth_tx_info: &EthTxInfo, @@ -357,6 +420,7 @@ pub mod ethereum_execution { }) } + /// Compute the total consumed gas (signed) for Ethereum-style execution. pub fn total_consumed_gas( meter: &ResourceMeter, eth_tx_info: &EthTxInfo, diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index cefd00cdad49a..24108ed0f04c7 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -36,17 +36,20 @@ use weight::{ChargedAmount, Token, WeightMeter}; use sp_runtime::{DispatchError, DispatchResult, FixedU128, SaturatedConversion}; -/// Used to implement a type state pattern for the meter. +/// A type-state pattern ensuring that meters can only be used in valid states (root vs nested). /// /// It is sealed and cannot be implemented outside of this module. pub trait State: private::Sealed + Default + Debug {} -/// State parameter that constitutes a meter that is in its root state. +/// Root state for transaction-level resource metering. +/// +/// Represents the top-level accounting of a transaction's resource usage. #[derive(Default, Debug)] pub struct Root; -/// State parameter that constitutes a meter that is in its nested state. -/// Its value indicates whether the nested meter has its own limit. +/// Nested state for frame-level resource metering. +/// +/// Represents resource accounting for a single call frame. #[derive(Default, Debug)] pub struct Nested; @@ -62,8 +65,17 @@ mod private { pub type TransactionMeter = ResourceMeter; pub type FrameMeter = ResourceMeter; -/// invariant: either the limits in both meters are both None or both Some(..) -/// they will always be defined if `transaction_limits` is `TransactionLimits::WeightAndDeposit` +/// Resource meter tracking weight and storage deposit consumption. +/// +/// This type maintains the core invariant that either: +/// - Both weight and deposit limits are None, or +/// - Both limits are Some(value) +/// +/// A resource meter tracks: +/// - Current frame's weight consumption via WeightMeter +/// - Current frame's storage deposit changes via GenericStorageMeter +/// - Total resources consumed before this frame started +/// - Transaction-wide resource limits and execution mode #[derive(DefaultNoBound)] pub struct ResourceMeter { weight: WeightMeter, @@ -79,6 +91,11 @@ pub struct ResourceMeter { _phantom: PhantomData, } +/// Transaction-wide resource limit configuration. +/// +/// Represents the two supported resource accounting modes: +/// - EthereumGas: Single gas limit +/// - WeightAndDeposit: Explicit limits for both computational weight and storage deposit #[derive(DebugNoBound, Clone)] pub enum TransactionLimits { EthereumGas { eth_gas_limit: BalanceOf, eth_tx_info: EthTxInfo }, @@ -95,6 +112,9 @@ impl Default for TransactionLimits { } impl ResourceMeter { + /// Charge a weight token against this meter's remaining weight limit. + /// + /// Returns `Err(Error::OutOfGas)` if the weight limit would be exceeded. pub fn charge_weight_token>( &mut self, token: Tok, @@ -105,6 +125,7 @@ impl ResourceMeter { self.weight.charge(token, weight_left) } + /// Try to charge a weight token or halt if not enough weight is left. pub fn charge_or_halt>( &mut self, token: Tok, @@ -115,10 +136,16 @@ impl ResourceMeter { self.weight.charge_or_halt(token, weight_left) } + /// Adjust an earlier weight charge with the actual weight consumed. pub fn adjust_weight>(&mut self, charged_amount: ChargedAmount, token: Tok) { self.weight.adjust_weight(charged_amount, token); } + /// Synchronize meter state with PolkaVM executor's fuel consumption. + /// + /// Maps the VM's internal fuel accounting to weight consumption: + /// - Converts engine fuel units to weight units + /// - Updates meter state to match actual VM resource usage pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> { // TODO: optimize let weight_left = self.weight_left().ok_or(>::OutOfGas)?; @@ -128,21 +155,28 @@ impl ResourceMeter { .sync_from_executor(engine_fuel, weight_left.saturating_add(weight_consumed)) } - pub fn consume_all_weight(&mut self) { + /// Convert meter state to PolkaVM executor fuel units. + /// + /// Prepares for VM execution by: + /// - Computing remaining available weight + /// - Converting weight units to VM fuel units and return + pub fn sync_to_executor(&mut self) -> polkavm::Gas { // TODO: optimize let weight_left = self.weight_left().unwrap_or_default(); - let weight_consumed = self.weight.weight_consumed(); - self.weight.consume_all(weight_left.saturating_add(weight_consumed)); + self.weight.sync_to_executor(weight_left) } - pub fn sync_to_executor(&mut self) -> polkavm::Gas { + /// Consume all remaining weight in the meter. + pub fn consume_all_weight(&mut self) { // TODO: optimize let weight_left = self.weight_left().unwrap_or_default(); + let weight_consumed = self.weight.weight_consumed(); - self.weight.sync_to_executor(weight_left) + self.weight.consume_all(weight_left.saturating_add(weight_consumed)); } + /// Record a storage deposit charge against this meter. pub fn charge_deposit(&mut self, deposit: &DepositOf) -> DispatchResult { self.deposit.record_charge(deposit); @@ -156,10 +190,12 @@ impl ResourceMeter { Ok(()) } + /// Absorb only the weight consumption from a nested frame meter. pub fn absorb_weight_meter_only(&mut self, other: FrameMeter) { self.weight.absorb_nested(other.weight); } + /// Absorb all resource consumption from a nested frame meter. pub fn absorb_all_meters( &mut self, other: FrameMeter, @@ -170,6 +206,7 @@ impl ResourceMeter { self.deposit.absorb(other.deposit, contract, info); } + /// Create a new nested meter with derived resource limits. pub fn new_nested(&self, limit: &CallResources) -> Result, DispatchError> { match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => @@ -179,6 +216,12 @@ impl ResourceMeter { } } + /// Get remaining ethereum gas equivalent. + /// + /// Converts remaining resources to ethereum gas units: + /// - For ethereum mode: computes directly from gas accounting + /// - For substrate mode: converts weight+deposit to gas equivalent + /// Returns None if resources are exhausted or conversion fails. pub fn eth_gas_left(&self) -> Option> { match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => @@ -188,6 +231,12 @@ impl ResourceMeter { } } + /// Get remaining weight available. + /// + /// Computes remaining computational capacity: + /// - For ethereum mode: converts from gas to weight units + /// - For substrate mode: subtracts consumed from weight limit + /// Returns None if resources are exhausted. pub fn weight_left(&self) -> Option { match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => @@ -197,6 +246,12 @@ impl ResourceMeter { } } + /// Get remaining deposit available. + /// + /// Computes remaining storage deposit allowance: + /// - For ethereum mode: converts from gas to deposit units + /// - For substrate mode: subtracts consumed from deposit limit + /// Returns None if resources are exhausted. pub fn deposit_left(&self) -> Option> { match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => @@ -206,6 +261,12 @@ impl ResourceMeter { } } + /// Calculate total gas consumed so far. + /// + /// Computes the ethereum-gas equivalent of all resource usage: + /// - Converts weight and deposit consumption to gas units + /// - For ethereum mode: uses direct gas accounting + /// - For substrate mode: synthesizes from weight+deposit usage pub fn total_consumed_gas(&self) -> BalanceOf { let signed_gas = match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => @@ -217,24 +278,41 @@ impl ResourceMeter { signed_gas.as_positive().unwrap_or_default() } + /// Get total weight consumed pub fn weight_consumed(&self) -> Weight { self.weight.weight_consumed() } + /// Get total weight required + /// This is the maxmimum amount of weight consumption that occurred during execution so far + /// This is relevant because consumed weight can decrease in case it is asjusted a posteriori + /// for some operations pub fn weight_required(&self) -> Weight { self.weight.weight_required() } + /// Get total storage deposit consumed in the current frame. + /// + /// Returns the net storage deposit change from this frame, pub fn deposit_consumed(&self) -> DepositOf { self.deposit.consumed() } + /// Get maximum storage deposit required at any point. + /// + /// Returns the highest deposit amount needed during execution, + /// accounting for temporary storage spikes before later refunds. pub fn deposit_required(&self) -> DepositOf { self.deposit.max_charged() } } impl TransactionMeter { + /// Create a new transaction-level meter with the specified resource limits. + /// + /// Initializes either: + /// - An ethereum-style gas-based meter or + /// - A substrate-style meter with explicit weight and deposit limits pub fn new(transaction_limits: TransactionLimits) -> Result { match transaction_limits { TransactionLimits::EthereumGas { eth_gas_limit, eth_tx_info } => @@ -244,6 +322,7 @@ impl TransactionMeter { } } + /// Convenience constructor for substrate-style weight+deposit limits. pub fn new_from_limits( weight_limit: Weight, deposit_limit: BalanceOf, @@ -251,6 +330,9 @@ impl TransactionMeter { Self::new(TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit }) } + /// Execute all postponed storage deposit operations. + /// + /// Returns `Err(Error::StorageDepositNotEnoughFunds)` if deposit limit would be exceeded. pub fn execute_postponed_deposits( &mut self, origin: &Origin, @@ -264,6 +346,7 @@ impl TransactionMeter { self.deposit.execute_postponed_deposits(origin, exec_config) } + /// Absorb resources from a terminated contract. pub fn terminate_absorb( &mut self, contract_account: T::AccountId, @@ -277,6 +360,10 @@ impl TransactionMeter { } impl FrameMeter { + /// Record a contract's storage deposit and schedule the transfer. + /// + /// Updates the frame's deposit accounting and schedules the actual token transfer + /// for later execution – at the end of the transaction execution. pub fn charge_contract_deposit_and_transfer( &mut self, contract: T::AccountId, @@ -285,6 +372,7 @@ impl FrameMeter { self.deposit.charge_deposit(contract, amount) } + /// Record storage changes of a contract. pub fn record_contract_storage_changes(&mut self, diff: &Diff) { self.deposit.charge(diff); } @@ -303,18 +391,27 @@ impl FrameMeter { } } +/// Ethereum transaction context for gas conversions. +/// +/// Contains the parameters needed to convert between ethereum gas and substrate resources +/// (weight/deposit) #[derive(DebugNoBound, Clone)] pub struct EthTxInfo { + /// The encoding length of the extrinsic pub encoded_len: u32, + /// The extra weight of the transaction. The total weight of the extrinsic is `extra_weight` + + /// the weight consumed during smart contract execution. pub extra_weight: Weight, _phantom: PhantomData, } impl EthTxInfo { + /// Create a new ethereum transaction context with the given parameters. pub fn new(encoded_len: u32, extra_weight: Weight) -> Self { Self { encoded_len, extra_weight, _phantom: PhantomData } } + /// Calculate total gas consumed by weight and storage operations. pub fn gas_consumption( &self, consumed_weight: &Weight, @@ -332,13 +429,10 @@ impl EthTxInfo { scaled_gas.saturating_add(&weight_fee) } - pub fn gas_remaining( - max_total_gas: &SignedGas, - total_gas_consumption: &SignedGas, - ) -> Option> { - max_total_gas.saturating_sub(total_gas_consumption).as_positive() - } - + /// Calculate maximal possible remaining weight that can be consumed given a particular gas + /// limit. + /// + /// Returns None if remaining gas would not allow any more weight consumption. pub fn weight_remaining( &self, max_total_gas: &SignedGas, diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 5f1b259c6816f..259062d49d321 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -61,7 +61,7 @@ pub struct ContractResult { /// is `Err`. This is because on error all storage changes are rolled back including the /// payment of the deposit. pub storage_deposit: StorageDeposit, - /// The maximual storage deposit amount that occured at any time during the execution. + /// The maximal storage deposit amount that occured at any time during the execution. /// This can be higher than the final storage_deposit due to refunds /// This is always a StorageDeposit::Charge(..) pub max_storage_deposit: StorageDeposit, @@ -335,22 +335,6 @@ where Refund(amount) => Some(limit.saturating_add(*amount)), } } - - pub fn negate(&self) -> Self { - use StorageDeposit::*; - match self { - Charge(amount) => Refund(*amount), - Refund(amount) => Charge(*amount), - } - } - - pub fn scale_by_factor(&self, rhs: &FixedU128) -> Self { - use StorageDeposit::*; - match self { - Charge(amount) => Charge(rhs.saturating_mul_int(*amount)), - Refund(amount) => Refund(rhs.saturating_mul_int(*amount)), - } - } } /// The type for Ethereum gas. We need to deal with negative and positive values and the structure @@ -444,6 +428,7 @@ impl SignedGas { } } + /// Return the balance of the `SignedGas` if it is `Positive`, otherwise return `None` pub fn as_positive(&self) -> Option> { use SignedGas::*; match self { From 548a143a99e533c07509c25523789462b54e0b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 5 Nov 2025 02:46:20 -0300 Subject: [PATCH 09/69] Use correct gas handling in tracer --- substrate/frame/revive/src/evm/tracing.rs | 4 +- .../revive/src/evm/tracing/call_tracing.rs | 32 +++++------- .../src/evm/tracing/prestate_tracing.rs | 8 +-- substrate/frame/revive/src/exec.rs | 33 +++++++----- substrate/frame/revive/src/exec/tests.rs | 2 +- substrate/frame/revive/src/lib.rs | 26 ++++++---- substrate/frame/revive/src/metering/math.rs | 51 +++++++++++++++++++ substrate/frame/revive/src/metering/mod.rs | 10 ++++ .../revive/src/precompiles/builtin/bn128.rs | 3 +- .../src/precompiles/builtin/identity.rs | 3 +- .../revive/src/precompiles/builtin/sha256.rs | 3 +- substrate/frame/revive/src/primitives.rs | 4 +- substrate/frame/revive/src/tests/pvm.rs | 33 ++++++++++-- substrate/frame/revive/src/tests/sol.rs | 8 ++- substrate/frame/revive/src/tracing.rs | 8 +-- 15 files changed, 165 insertions(+), 63 deletions(-) diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index 9a28c0c6b293d..17c8382c57548 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -17,7 +17,7 @@ use crate::{ evm::{CallTrace, Trace}, tracing::Tracing, - Config, Weight, + Config, }; use sp_core::U256; @@ -31,7 +31,7 @@ pub use prestate_tracing::*; #[derive(derive_more::From, Debug)] pub enum Tracer { /// A tracer that traces calls. - CallTracer(CallTracer U256>), + CallTracer(CallTracer), /// A tracer that traces the prestate. PrestateTracer(PrestateTracer), } diff --git a/substrate/frame/revive/src/evm/tracing/call_tracing.rs b/substrate/frame/revive/src/evm/tracing/call_tracing.rs index 270810800c13f..b8e913a78a67e 100644 --- a/substrate/frame/revive/src/evm/tracing/call_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/call_tracing.rs @@ -18,16 +18,14 @@ use crate::{ evm::{decode_revert_reason, CallLog, CallTrace, CallTracerConfig, CallType}, primitives::ExecReturnValue, tracing::Tracing, - Code, DispatchError, Weight, + Code, DispatchError, }; use alloc::{format, string::ToString, vec::Vec}; use sp_core::{H160, H256, U256}; /// A Tracer that reports logs and nested call traces transactions. #[derive(Default, Debug, Clone, PartialEq)] -pub struct CallTracer { - /// Map Weight to Gas equivalent. - gas_mapper: GasMapper, +pub struct CallTracer { /// Store all in-progress CallTrace instances. traces: Vec>, /// Stack of indices to the current active traces. @@ -38,16 +36,10 @@ pub struct CallTracer { config: CallTracerConfig, } -impl CallTracer { +impl CallTracer { /// Create a new [`CallTracer`] instance. - pub fn new(config: CallTracerConfig, gas_mapper: GasMapper) -> Self { - Self { - gas_mapper, - traces: Vec::new(), - code_with_salt: None, - current_stack: Vec::new(), - config, - } + pub fn new(config: CallTracerConfig) -> Self { + Self { traces: Vec::new(), code_with_salt: None, current_stack: Vec::new(), config } } /// Collect the traces and return them. @@ -56,7 +48,7 @@ impl CallTracer { } } -impl Gas> Tracing for CallTracer { +impl Tracing for CallTracer { fn instantiate_code(&mut self, code: &Code, salt: Option<&[u8; 32]>) { self.code_with_salt = Some((code.clone(), salt.is_some())); } @@ -69,7 +61,7 @@ impl Gas> Tracing for CallTracer Gas> Tracing for CallTracer Gas> Tracing for CallTracer Gas> Tracing for CallTracer Gas> Tracing for CallTracer diff --git a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs index 1693996575745..929f1f8002731 100644 --- a/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/prestate_tracing.rs @@ -17,7 +17,7 @@ use crate::{ evm::{Bytes, PrestateTrace, PrestateTraceInfo, PrestateTracerConfig}, tracing::Tracing, - AccountInfo, Code, Config, ExecReturnValue, Key, Pallet, PristineCode, Weight, + AccountInfo, Code, Config, ExecReturnValue, Key, Pallet, PristineCode, }; use alloc::{collections::BTreeMap, vec::Vec}; use sp_core::{H160, U256}; @@ -183,7 +183,7 @@ where _is_read_only: bool, _value: U256, _input: &[u8], - _weight: Weight, + _gas_limit: U256, ) { let include_code = !self.config.disable_code; self.trace.0.entry(from).or_insert_with_key(|addr| { @@ -209,11 +209,11 @@ where } } - fn exit_child_span_with_error(&mut self, _error: crate::DispatchError, _weight_used: Weight) { + fn exit_child_span_with_error(&mut self, _error: crate::DispatchError, _gas_used: U256) { self.is_create = None; } - fn exit_child_span(&mut self, output: &ExecReturnValue, _weight_used: Weight) { + fn exit_child_span(&mut self, output: &ExecReturnValue, _gas_used: U256) { let create_code = self.is_create.take(); if output.did_revert() { return diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 8b870ef948602..6358fa6452045 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -876,8 +876,8 @@ where }; if_tracing(|t| match result { - Ok(ref output) => t.exit_child_span(&output, Weight::zero()), - Err(e) => t.exit_child_span_with_error(e.error.into(), Weight::zero()), + Ok(ref output) => t.exit_child_span(&output, Default::default()), + Err(e) => t.exit_child_span_with_error(e.error.into(), Default::default()), }); log::trace!(target: LOG_TARGET, "call finished with: {result:?}"); @@ -1194,7 +1194,7 @@ where frame.read_only, frame.value_transferred, &input_data, - frame.frame_meter.weight_left().unwrap_or_default(), + frame.frame_meter.eth_gas_left().unwrap_or_default().into(), ); }); let mock_answer = self.exec_config.mock_handler.as_ref().and_then(|handler| { @@ -1407,11 +1407,15 @@ where // `with_transactional` executed successfully, and we have the expected output. Ok((success, output)) => { if_tracing(|tracer| { - let weight_consumed = top_frame!(self).frame_meter.weight_consumed(); + let gas_consumed = top_frame!(self) + .frame_meter + .eth_gas_consumed() + .as_positive() + .unwrap_or_default() + .into(); match &output { - Ok(output) => tracer.exit_child_span(&output, weight_consumed), - Err(e) => - tracer.exit_child_span_with_error(e.error.into(), weight_consumed), + Ok(output) => tracer.exit_child_span(&output, gas_consumed), + Err(e) => tracer.exit_child_span_with_error(e.error.into(), gas_consumed), } }); @@ -1421,8 +1425,13 @@ where // has changed. Err(error) => { if_tracing(|tracer| { - let weight_consumed = top_frame!(self).frame_meter.weight_consumed(); - tracer.exit_child_span_with_error(error.into(), weight_consumed); + let gas_consumed = top_frame!(self) + .frame_meter + .eth_gas_consumed() + .as_positive() + .unwrap_or_default() + .into(); + tracer.exit_child_span_with_error(error.into(), gas_consumed); }); (false, Err(error.into())) @@ -2028,7 +2037,7 @@ where is_read_only, value, &input_data, - Weight::zero(), + Default::default(), ); }); let result = if let Some(mock_answer) = @@ -2055,8 +2064,8 @@ where }; if_tracing(|t| match result { - Ok(ref output) => t.exit_child_span(&output, Weight::zero()), - Err(e) => t.exit_child_span_with_error(e.error.into(), Weight::zero()), + Ok(ref output) => t.exit_child_span(&output, Default::default()), + Err(e) => t.exit_child_span_with_error(e.error.into(), Default::default()), }); result.map(|_| ()) diff --git a/substrate/frame/revive/src/exec/tests.rs b/substrate/frame/revive/src/exec/tests.rs index 3aae157f10611..9bf793788e0ca 100644 --- a/substrate/frame/revive/src/exec/tests.rs +++ b/substrate/frame/revive/src/exec/tests.rs @@ -2710,7 +2710,7 @@ fn block_hash_returns_proper_values() { assert_eq!( ctx.ext.block_hash(U256::from(0)), Some(H256::from(hex_literal::hex!( - "b9be84fb00044994dff6b62393b4c8aa8191717c5ea046f270bd2695524e9f85" + "92690939a7c8ccc70096c9c8d07e0314b3bc52005c01cc075cca22cdc355375a" ))) ); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 7c84ba4d0e6eb..d47ea40812f02 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1978,11 +1978,23 @@ impl Pallet { .max_total .unwrap_or_else(|| T::BlockWeights::get().max_block); - let length_fee = T::FeeInfo::next_fee_multiplier_reciprocal().saturating_mul_int( - T::FeeInfo::length_to_fee(*T::BlockLength::get().max.get(DispatchClass::Normal)), - ); + let max_storage_deposit = Self::max_storage_deposit(); + let max_length_fee = + T::FeeInfo::length_to_fee(*T::BlockLength::get().max.get(DispatchClass::Normal)); + + let variable_fee = T::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(max_storage_deposit.saturating_add(max_length_fee)); - Self::evm_gas_from_weight(max_block_weight).saturating_add(length_fee.into()) + Self::evm_gas_from_weight(max_block_weight).saturating_add(variable_fee.into()) + } + + /// This is not an enforced limit but an estimate of the maximum storage deposit occurring in + /// practice. It is used to determine the block gas limit + pub fn max_storage_deposit() -> BalanceOf { + // Assume that transactions in that block deploy new contracts and upload contract code up + // to the POV limits of the block + T::DepositPerItem::get() + .saturating_mul(T::BlockWeights::get().max_block.proof_size().saturated_into()) } /// The maximum weight an `eth_transact` is allowed to consume. @@ -2010,11 +2022,7 @@ impl Pallet { T::Nonce: Into, { match tracer_type { - TracerType::CallTracer(config) => CallTracer::new( - config.unwrap_or_default(), - Self::evm_gas_from_weight as fn(Weight) -> U256, - ) - .into(), + TracerType::CallTracer(config) => CallTracer::new(config.unwrap_or_default()).into(), TracerType::PrestateTracer(config) => PrestateTracer::new(config.unwrap_or_default()).into(), } diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index 8541305ac8a33..70de3af1b0000 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -203,6 +203,34 @@ pub mod substrate_execution { consumed_deposit_gas.saturating_add(&SignedGas::Positive(consumed_weight_gas)) } + + /// Compute the gas (signed) during the lifetime of this meter for Substrate-style execution. + pub fn eth_gas_consumed(meter: &ResourceMeter) -> SignedGas { + let self_consumed_weight = meter.weight.weight_consumed(); + let self_consumed_deposit = meter.deposit.consumed(); + + let total_consumed_weight = + meter.total_consumed_weight_before.saturating_add(self_consumed_weight); + + let consumed_weight_gas_before = SignedGas::Positive(T::FeeInfo::weight_to_fee_average( + &meter.total_consumed_weight_before, + )); + let consumed_weight_gas = + SignedGas::Positive(T::FeeInfo::weight_to_fee_average(&total_consumed_weight)); + + let self_consumed_weight_gas = + consumed_weight_gas.saturating_sub(&consumed_weight_gas_before); + + let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); + let self_consumed_deposit_gas = match self_consumed_deposit { + StorageDeposit::Charge(amount) => + SignedGas::Positive(multiplier.saturating_mul_int(amount)), + StorageDeposit::Refund(amount) => + SignedGas::Negative(multiplier.saturating_mul_int(amount)), + }; + + self_consumed_deposit_gas.saturating_add(&self_consumed_weight_gas) + } } pub mod ethereum_execution { @@ -435,4 +463,27 @@ pub mod ethereum_execution { eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit) } + + /// Compute the gas (signed) during the lifetime of this meter for Ethereum-style execution. + pub fn eth_gas_consumed( + meter: &ResourceMeter, + eth_tx_info: &EthTxInfo, + ) -> SignedGas { + let self_consumed_weight = meter.weight.weight_consumed(); + let self_consumed_deposit = meter.deposit.consumed(); + + let total_consumed_weight = + meter.total_consumed_weight_before.saturating_add(self_consumed_weight); + let total_consumed_deposit = + meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); + + let total_gas_consumed = + eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit); + let total_gas_consumed_before = eth_tx_info.gas_consumption( + &meter.total_consumed_weight_before, + &meter.total_consumed_deposit_before, + ); + + total_gas_consumed.saturating_sub(&total_gas_consumed_before) + } } diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 24108ed0f04c7..9d6617fd307a6 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -305,6 +305,16 @@ impl ResourceMeter { pub fn deposit_required(&self) -> DepositOf { self.deposit.max_charged() } + + /// Get the Ethereum gas that has been consumed during the lifetime of this meter + pub fn eth_gas_consumed(&self) -> SignedGas { + match &self.transaction_limits { + TransactionLimits::EthereumGas { eth_tx_info, .. } => + math::ethereum_execution::eth_gas_consumed(self, eth_tx_info), + TransactionLimits::WeightAndDeposit { .. } => + math::substrate_execution::eth_gas_consumed(self), + } + } } impl TransactionMeter { diff --git a/substrate/frame/revive/src/precompiles/builtin/bn128.rs b/substrate/frame/revive/src/precompiles/builtin/bn128.rs index 413a2ae62ac18..e477e0eb6daad 100644 --- a/substrate/frame/revive/src/precompiles/builtin/bn128.rs +++ b/substrate/frame/revive/src/precompiles/builtin/bn128.rs @@ -104,7 +104,8 @@ impl PrimitivePrecompile for Bn128Pairing { } else { // (a, b_a, b_b - each 64-byte affine coordinates) let elements = input.len() / 192; - env.gas_meter_mut().charge_weight_token(RuntimeCosts::Bn128Pairing(elements as u32))?; + env.gas_meter_mut() + .charge_weight_token(RuntimeCosts::Bn128Pairing(elements as u32))?; let mut vals = Vec::new(); for i in 0..elements { diff --git a/substrate/frame/revive/src/precompiles/builtin/identity.rs b/substrate/frame/revive/src/precompiles/builtin/identity.rs index 250742fe8c063..983779d4a3e30 100644 --- a/substrate/frame/revive/src/precompiles/builtin/identity.rs +++ b/substrate/frame/revive/src/precompiles/builtin/identity.rs @@ -35,7 +35,8 @@ impl PrimitivePrecompile for Identity { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::Identity(input.len() as _))?; + env.gas_meter_mut() + .charge_weight_token(RuntimeCosts::Identity(input.len() as _))?; Ok(input) } } diff --git a/substrate/frame/revive/src/precompiles/builtin/sha256.rs b/substrate/frame/revive/src/precompiles/builtin/sha256.rs index 52b802bf8a5fb..8425201480df3 100644 --- a/substrate/frame/revive/src/precompiles/builtin/sha256.rs +++ b/substrate/frame/revive/src/precompiles/builtin/sha256.rs @@ -35,7 +35,8 @@ impl PrimitivePrecompile for Sha256 { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::HashSha256(input.len() as _))?; + env.gas_meter_mut() + .charge_weight_token(RuntimeCosts::HashSha256(input.len() as _))?; let data = sp_io::hashing::sha2_256(&input).to_vec(); Ok(data) } diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 259062d49d321..74fe2a4a6dbb3 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -20,7 +20,7 @@ use crate::{mock::MockHandler, storage::WriteOutcome, BalanceOf, Config, H160, U256}; use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{traits::tokens::Balance, weights::Weight}; +use frame_support::{traits::tokens::Balance, weights::Weight, DebugNoBound}; use pallet_revive_uapi::ReturnFlags; use scale_info::TypeInfo; use sp_core::Get; @@ -341,7 +341,7 @@ where /// of this type resembles `StorageDeposit` but the enum variants have a more obvious name to avoid /// confusion and errors #[derive( - Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo, + Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, DebugNoBound, TypeInfo, )] pub enum SignedGas { /// Positive gas amount diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index d5cef0020cd13..d67735f9732f4 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -4138,7 +4138,7 @@ fn unstable_interface_rejected() { fn tracing_works_for_transfers() { ExtBuilder::default().build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 100_000_000); - let mut tracer = CallTracer::new(Default::default(), |_| U256::zero()); + let mut tracer = CallTracer::new(Default::default()); trace(&mut tracer, || { builder::bare_call(BOB_ADDR).evm_value(10.into()).build_and_unwrap_result(); }); @@ -4192,6 +4192,8 @@ fn call_tracing_works() { */ // Discarding gas usage, check that traces reported are correct + let mut ed_required: u64 = 200; + for config in tracer_configs { let logs = if config.with_logs { vec![ @@ -4227,6 +4229,8 @@ fn call_tracing_works() { error: Some("execution reverted".to_string()), call_type: Call, value: Some(U256::from(0)), + gas: 1248904700460u64.into(), + gas_used: 11967774.into(), ..Default::default() }, CallTrace { @@ -4236,6 +4240,8 @@ fn call_tracing_works() { call_type: Call, logs: logs.clone(), value: Some(U256::from(0)), + gas: 1247950604625u64.into(), + gas_used: (4332765740u64 + ed_required).into(), calls: vec![ CallTrace { from: addr, @@ -4245,6 +4251,8 @@ fn call_tracing_works() { error: Some("ContractTrapped".to_string()), call_type: Call, value: Some(U256::from(0)), + gas: 1247105006008u64.into(), + gas_used: 1100894.into(), ..Default::default() }, CallTrace { @@ -4254,6 +4262,8 @@ fn call_tracing_works() { call_type: Call, logs: logs.clone(), value: Some(U256::from(0)), + gas: 1246161777053u64.into(), + gas_used: (2543578433u64 + ed_required).into(), calls: vec![ CallTrace { from: addr, @@ -4262,6 +4272,8 @@ fn call_tracing_works() { output: 0u32.to_le_bytes().to_vec().into(), call_type: Call, value: Some(U256::from(0)), + gas: 1245316178436u64.into(), + gas_used: 1705726.into(), ..Default::default() }, CallTrace { @@ -4270,6 +4282,8 @@ fn call_tracing_works() { input: (0u32, addr_callee).encode().into(), call_type: Call, value: Some(U256::from(0)), + gas: 1244372344649u64.into(), + gas_used: (753786293 + ed_required).into(), calls: vec![ CallTrace { from: addr, @@ -4293,7 +4307,7 @@ fn call_tracing_works() { ] }; - let mut tracer = CallTracer::new(config, |_| U256::zero()); + let mut tracer = CallTracer::new(config); trace(&mut tracer, || { builder::bare_call(addr).data((3u32, addr_callee).encode()).build() }); @@ -4308,6 +4322,8 @@ fn call_tracing_works() { value: Some(U256::from(0)), calls: calls, child_call_count: 2, + gas: 1249750299077u64.into(), + gas_used: (6132819927u64 + ed_required).into(), // 6132819927u64 or 6132820127u64 ..Default::default() }; @@ -4315,6 +4331,9 @@ fn call_tracing_works() { trace, expected_trace.into(), ); + + // the innermost call transfers a value of 100 to BOB, this requires an extra 200 storage deposit for the ed on the first iteration + ed_required = 0; } }); } @@ -4326,7 +4345,7 @@ fn create_call_tracing_works() { ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 100_000_000); - let mut tracer = CallTracer::new(Default::default(), |_| U256::zero()); + let mut tracer = CallTracer::new(Default::default()); let Contract { addr, .. } = trace(&mut tracer, || { builder::bare_instantiate(Code::Upload(code.clone())) @@ -4344,11 +4363,13 @@ fn create_call_tracing_works() { value: Some(100.into()), input: Bytes(code.clone()), call_type: CallType::Create, + gas: 1250009998433u64.into(), + gas_used: 36747.into(), ..Default::default() } ); - let mut tracer = CallTracer::new(Default::default(), |_| U256::zero()); + let mut tracer = CallTracer::new(Default::default()); let data = b"garbage"; let input = (code_hash, data).encode(); trace(&mut tracer, || { @@ -4365,12 +4386,16 @@ fn create_call_tracing_works() { to: addr, value: Some(0.into()), input: input.clone().into(), + gas: 1249861529896u64.into(), + gas_used: 983784141.into(), calls: vec![CallTrace { from: addr, input: input.clone().into(), to: child_addr, value: Some(0.into()), call_type: CallType::Create2, + gas: 1248878034317u64.into(), + gas_used: 36748.into(), ..Default::default() },], child_call_count: 1, diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 73e4ef6c9c9f2..555652924a97f 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -110,7 +110,7 @@ fn basic_evm_flow_tracing_works() { let (code, _) = compile_module_with_type("Fibonacci", FixtureType::Solc).unwrap(); ExtBuilder::default().build().execute_with(|| { - let mut tracer = CallTracer::new(Default::default(), |_| crate::U256::zero()); + let mut tracer = CallTracer::new(Default::default()); let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); let Contract { addr, .. } = trace(&mut tracer, || { @@ -129,11 +129,13 @@ fn basic_evm_flow_tracing_works() { input: code.into(), output: runtime_code.into(), value: Some(crate::U256::zero()), + gas: 1250010000000u64.into(), + gas_used: 668075.into(), ..Default::default() } ); - let mut call_tracer = CallTracer::new(Default::default(), |_| crate::U256::zero()); + let mut call_tracer = CallTracer::new(Default::default()); let result = trace(&mut call_tracer, || { builder::bare_call(addr) .data(Fibonacci::FibonacciCalls::fib(Fibonacci::fibCall { n: 10u64 }).abi_encode()) @@ -154,6 +156,8 @@ fn basic_evm_flow_tracing_works() { .into(), output: result.data.into(), value: Some(crate::U256::zero()), + gas: 1250009987465u64.into(), + gas_used: 483318632.into(), ..Default::default() }, ); diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs index 2536ff47bf7b5..020307f44c11f 100644 --- a/substrate/frame/revive/src/tracing.rs +++ b/substrate/frame/revive/src/tracing.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{primitives::ExecReturnValue, Code, DispatchError, Key, Weight}; +use crate::{primitives::ExecReturnValue, Code, DispatchError, Key}; use alloc::vec::Vec; use environmental::environmental; use sp_core::{H160, H256, U256}; @@ -54,7 +54,7 @@ pub trait Tracing { _is_read_only: bool, _value: U256, _input: &[u8], - _weight: Weight, + _gas_limit: U256, ) { } @@ -80,8 +80,8 @@ pub trait Tracing { fn log_event(&mut self, _event: H160, _topics: &[H256], _data: &[u8]) {} /// Called after a contract call is executed - fn exit_child_span(&mut self, _output: &ExecReturnValue, _weight_left: Weight) {} + fn exit_child_span(&mut self, _output: &ExecReturnValue, _gas_used: U256) {} /// Called when a contract call terminates with an error - fn exit_child_span_with_error(&mut self, _error: DispatchError, _weight_left: Weight) {} + fn exit_child_span_with_error(&mut self, _error: DispatchError, _gas_used: U256) {} } From 947a492c685b9d5cf506d87cf70be1fabad744dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 5 Nov 2025 08:25:50 -0300 Subject: [PATCH 10/69] Add tweaks for dry running --- substrate/frame/revive/src/evm/call.rs | 36 +++++++++++++-------- substrate/frame/revive/src/lib.rs | 15 +++++++-- substrate/frame/revive/src/metering/math.rs | 9 ++++-- substrate/frame/revive/src/metering/mod.rs | 17 +++++++--- 4 files changed, 54 insertions(+), 23 deletions(-) diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs index 3f59e5be7f615..73ab27166a205 100644 --- a/substrate/frame/revive/src/evm/call.rs +++ b/substrate/frame/revive/src/evm/call.rs @@ -63,6 +63,9 @@ where return Err(InvalidTransaction::Call); }; + // Currently, effective_gas_price will always be the same as base_fee + // Because all callers of `create_call` will prepare `tx` that way. Some of the subsequent + // logic will not work correctly anymore if we change that assumption. let Some(effective_gas_price) = tx.gas_price else { log::debug!(target: LOG_TARGET, "No gas_price provided."); return Err(InvalidTransaction::Payment); @@ -165,28 +168,33 @@ where let remaining_fee = { let adjusted = eth_fee.checked_sub(fixed_fee.into()).ok_or_else(|| { - log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover base and len fee. eth_fee={eth_fee:?} fixed_fee={fixed_fee:?}"); - InvalidTransaction::Payment - })?; + log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover base and len fee. eth_fee={eth_fee:?} fixed_fee={fixed_fee:?}"); + InvalidTransaction::Payment + })?; let unadjusted = ::FeeInfo::next_fee_multiplier_reciprocal() .saturating_mul_int(>::saturated_from(adjusted)); + unadjusted }; let remaining_fee_weight = ::FeeInfo::fee_to_weight(remaining_fee); let weight_limit = remaining_fee_weight - .checked_sub(&info.total_weight()).ok_or_else(|| { - log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover the weight ({:?}) of the extrinsic. remaining_fee_weight: {remaining_fee_weight:?}", info.total_weight(),); - InvalidTransaction::Payment - })?; + .checked_sub(&info.total_weight()).ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover the weight ({:?}) of the extrinsic. remaining_fee_weight: {remaining_fee_weight:?}", info.total_weight(),); + InvalidTransaction::Payment + })?; call.set_weight_limit(weight_limit); - let info = ::FeeInfo::dispatch_info(&call); - let max_weight = - if apply_weight_cap { >::evm_max_extrinsic_weight() } else { Weight::MAX }; - 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 + + if apply_weight_cap { + let max_weight = >::evm_max_extrinsic_weight(); + let info = ::FeeInfo::dispatch_info(&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 + } else { + weight_limit + } }; // the overall fee of the extrinsic including the gas limit diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 3e1912be8097c..6f267603ae6c9 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1271,6 +1271,7 @@ pub mod pallet { value, TransactionLimits::EthereumGas { eth_gas_limit: eth_gas_limit.saturated_into(), + maybe_weight_limit: Some(weight_limit), eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), }, Code::Upload(code), @@ -1350,6 +1351,7 @@ pub mod pallet { value, TransactionLimits::EthereumGas { eth_gas_limit: eth_gas_limit.saturated_into(), + maybe_weight_limit: Some(weight_limit), eth_tx_info: EthTxInfo::new(encoded_len, extra_weight), }, data, @@ -1671,9 +1673,10 @@ impl Pallet { if tx.chain_id.is_none() { tx.chain_id = Some(T::ChainId::get().into()); } - if tx.gas_price.is_none() { - tx.gas_price = Some(effective_gas_price); - } + + // create_call expects tx.gas_price to be the effective gas price + tx.gas_price = Some(effective_gas_price); + if tx.max_priority_fee_per_gas.is_none() { tx.max_priority_fee_per_gas = Some(effective_gas_price); } @@ -1762,6 +1765,9 @@ impl Pallet { value, TransactionLimits::EthereumGas { eth_gas_limit: call_info.eth_gas_limit.saturated_into(), + // no need to limit weight here, we will check later whether it exceeds + // evm_max_extrinsic_weight + maybe_weight_limit: None, eth_tx_info: EthTxInfo::new(call_info.encoded_len, base_weight), }, input.clone(), @@ -1805,6 +1811,9 @@ impl Pallet { value, TransactionLimits::EthereumGas { eth_gas_limit: call_info.eth_gas_limit.saturated_into(), + // no need to limit weight here, we will check later whether it exceeds + // evm_max_extrinsic_weight + maybe_weight_limit: None, eth_tx_info: EthTxInfo::new(call_info.encoded_len, base_weight), }, Code::Upload(code.clone()), diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index 70de3af1b0000..0b26969a39db3 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -244,15 +244,20 @@ pub mod ethereum_execution { /// otherwise it returns an error. pub fn new_root( eth_gas_limit: BalanceOf, + maybe_weight_limit: Option, eth_tx_info: EthTxInfo, ) -> Result, DispatchError> { let meter = TransactionMeter { - weight: WeightMeter::new(None), + weight: WeightMeter::new(maybe_weight_limit), deposit: RootStorageMeter::new(None), max_total_gas: SignedGas::Positive(eth_gas_limit), total_consumed_weight_before: Default::default(), total_consumed_deposit_before: Default::default(), - transaction_limits: TransactionLimits::EthereumGas { eth_gas_limit, eth_tx_info }, + transaction_limits: TransactionLimits::EthereumGas { + eth_gas_limit, + maybe_weight_limit, + eth_tx_info, + }, _phantom: PhantomData, }; diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 9d6617fd307a6..02bf11d34559d 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -98,8 +98,17 @@ pub struct ResourceMeter { /// - WeightAndDeposit: Explicit limits for both computational weight and storage deposit #[derive(DebugNoBound, Clone)] pub enum TransactionLimits { - EthereumGas { eth_gas_limit: BalanceOf, eth_tx_info: EthTxInfo }, - WeightAndDeposit { weight_limit: Weight, deposit_limit: BalanceOf }, + EthereumGas { + eth_gas_limit: BalanceOf, + // if this is provided, we will additionally ensure that execution will not exhaust this + // weight limit + maybe_weight_limit: Option, + eth_tx_info: EthTxInfo, + }, + WeightAndDeposit { + weight_limit: Weight, + deposit_limit: BalanceOf, + }, } impl Default for TransactionLimits { @@ -325,8 +334,8 @@ impl TransactionMeter { /// - A substrate-style meter with explicit weight and deposit limits pub fn new(transaction_limits: TransactionLimits) -> Result { match transaction_limits { - TransactionLimits::EthereumGas { eth_gas_limit, eth_tx_info } => - math::ethereum_execution::new_root(eth_gas_limit, eth_tx_info), + TransactionLimits::EthereumGas { eth_gas_limit, maybe_weight_limit, eth_tx_info } => + math::ethereum_execution::new_root(eth_gas_limit, maybe_weight_limit, eth_tx_info), TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } => math::substrate_execution::new_root(weight_limit, deposit_limit), } From 66cfae5220724e115f9f5fa6f1b33b1f86e5cd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 13 Nov 2025 03:34:50 -0300 Subject: [PATCH 11/69] Add call stipends for *CALL instructions --- .../revive/fixtures/contracts/Stipends.sol | 95 ++++++ substrate/frame/revive/src/exec.rs | 35 +- substrate/frame/revive/src/exec/mock_ext.rs | 4 +- substrate/frame/revive/src/exec/tests.rs | 194 +++++++++-- substrate/frame/revive/src/lib.rs | 2 +- substrate/frame/revive/src/limits.rs | 3 + substrate/frame/revive/src/metering/math.rs | 238 ++++++++------ substrate/frame/revive/src/metering/weight.rs | 24 +- substrate/frame/revive/src/tests.rs | 1 + .../frame/revive/src/tests/precompiles.rs | 4 +- substrate/frame/revive/src/tests/stipends.rs | 302 ++++++++++++++++++ substrate/frame/revive/src/vm/evm.rs | 2 +- .../src/vm/evm/instructions/contract.rs | 20 +- substrate/frame/revive/src/vm/pvm.rs | 11 +- 14 files changed, 773 insertions(+), 162 deletions(-) create mode 100644 substrate/frame/revive/fixtures/contracts/Stipends.sol create mode 100644 substrate/frame/revive/src/tests/stipends.rs diff --git a/substrate/frame/revive/fixtures/contracts/Stipends.sol b/substrate/frame/revive/fixtures/contracts/Stipends.sol new file mode 100644 index 0000000000000..553268e8ea4a5 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/Stipends.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +/** + * @title Sender + * @dev Sender contract: provides three transfer methods + */ +contract StipendSender { + event TransferSuccess(string method, address to, uint256 amount, uint256 gasUsed); + event TransferFailed(string method, address to, uint256 amount, string reason); + + // Method 1: transfer (2300 gas stipend) + function sendViaTransfer(address payable to) external payable { + uint256 gasBefore = gasleft(); + to.transfer(msg.value); + uint256 gasUsed = gasBefore - gasleft(); + emit TransferSuccess("transfer", to, msg.value, gasUsed); + } + + // Method 2: send (2300 gas stipend, returns bool) + function sendViaSend(address payable to) external payable returns (bool) { + uint256 gasBefore = gasleft(); + bool success = to.send(msg.value); + uint256 gasUsed = gasBefore - gasleft(); + + if (success) { + emit TransferSuccess("send", to, msg.value, gasUsed); + } else { + emit TransferFailed("send", to, msg.value, "send returned false"); + } + return success; + } + + // Method 3: call (forwards all gas) + function sendViaCall(address payable to) external payable returns (bool) { + uint256 gasBefore = gasleft(); + (bool success, ) = to.call{value: msg.value}(""); + uint256 gasUsed = gasBefore - gasleft(); + + if (success) { + emit TransferSuccess("call", to, msg.value, gasUsed); + } else { + emit TransferFailed("call", to, msg.value, "call returned false"); + } + return success; + } + + receive() external payable {} +} + +/** + * @title DoNothingReceiver + * @dev Receiver contract 1: empty receive(), does nothing + */ +contract DoNothingReceiver { + receive() external payable {} + + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +/** + * @title SimpleReceiver + * @dev Receiver contract 2: only emits events + */ +contract SimpleReceiver { + event Received(address from, uint256 amount); + + receive() external payable { + emit Received(msg.sender, msg.value); + } + + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} + +/** + * @title ComplexReceiver + * @dev Receiver contract 3: performs complex operations (SSTORE) + */ +contract ComplexReceiver { + uint256 public counter; + event Received(address from, uint256 amount, uint256 newCounter); + + receive() external payable { + counter += 1; + emit Received(msg.sender, msg.value, counter); + } + + function getBalance() external view returns (uint256) { + return address(this).balance; + } +} \ No newline at end of file diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index e250d6e700a9c..8bf71e93bb6e7 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -119,6 +119,22 @@ impl Key { } } +/// Level of reentrancy protection. +/// This needs to be specifed when a contract makes a message call. This way the calling contract +/// can specify the level of re-entrancy protection while the callee (and it's recursive callees) is +/// executing. +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ReentrancyProtection { + /// Don't activate reentrancy protection + AllowReentry, + // Activate strict reentrancy protection. The direct callee and none of its own recursive + // callees must be the calling contract. + Strict, + // Activate reentrancy protection where the direct callee can be the same contract as the + // caller but none of the recursive callees of the callee must be the caller + AllowNext, +} + /// Origin of the error. /// /// Call or instantiate both called into other contracts and pass through errors happening @@ -202,7 +218,7 @@ pub enum CallResources { /// Resources encoded using their actual values. WeightDeposit { weight: Weight, deposit_limit: BalanceOf }, /// Resources encoded as unified ethereum gas. - Ethereum(BalanceOf), + Ethereum { gas: BalanceOf, add_stipend: bool }, } impl CallResources { @@ -215,8 +231,8 @@ impl CallResources { } /// Creates a new `CallResources` from Ethereum gas limits. - pub fn from_ethereum_gas(gas: U256) -> Self { - Self::Ethereum(gas.saturated_into::>()) + pub fn from_ethereum_gas(gas: U256, add_stipend: bool) -> Self { + Self::Ethereum { gas: gas.saturated_into::>(), add_stipend } } } @@ -322,7 +338,7 @@ pub trait PrecompileExt: sealing::Sealed { to: &H160, value: U256, input_data: Vec, - allows_reentry: bool, + reentrancy: ReentrancyProtection, read_only: bool, ) -> Result<(), ExecError>; @@ -1918,13 +1934,16 @@ where dest_addr: &H160, value: U256, input_data: Vec, - allows_reentry: bool, + allows_reentry: ReentrancyProtection, read_only: bool, ) -> Result<(), ExecError> { // Before pushing the new frame: Protect the caller contract against reentrancy attacks. // It is important to do this before calling `allows_reentry` so that a direct recursion // is caught by it. - self.top_frame_mut().allows_reentry = allows_reentry; + + if allows_reentry == ReentrancyProtection::Strict { + self.top_frame_mut().allows_reentry = false; + } // We reset the return data now, so it is cleared out even if no new frame was executed. // This is for example the case for balance transfers or when creating the frame fails. @@ -1945,6 +1964,10 @@ where return Err(>::ReentranceDenied.into()); } + if allows_reentry == ReentrancyProtection::AllowNext { + self.top_frame_mut().allows_reentry = false; + } + // We ignore instantiate frames in our search for a cached contract. // Otherwise it would be possible to recursively call a contract from its own // constructor: We disallow calling not fully constructed contracts. diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index d93180f4bd6bf..97f3538d02574 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -25,7 +25,7 @@ use crate::{ precompiles::Diff, storage::{ContractInfo, WriteOutcome}, transient_storage::TransientStorage, - BalanceOf, Code, CodeRemoved, Config, ExecReturnValue, ImmutableData, + BalanceOf, Code, CodeRemoved, Config, ExecReturnValue, ImmutableData, ReentrancyProtection, }; use alloc::vec::Vec; use core::marker::PhantomData; @@ -61,7 +61,7 @@ impl PrecompileExt for MockExt { _to: &H160, _value: U256, _input_data: Vec, - _allows_reentry: bool, + _reentrancy: ReentrancyProtection, _read_only: bool, ) -> Result<(), ExecError> { panic!("MockExt::call") diff --git a/substrate/frame/revive/src/exec/tests.rs b/substrate/frame/revive/src/exec/tests.rs index e4901874e19cd..e9032774c58c8 100644 --- a/substrate/frame/revive/src/exec/tests.rs +++ b/substrate/frame/revive/src/exec/tests.rs @@ -30,7 +30,7 @@ use crate::{ test_utils::{get_balance, place_contract, set_balance}, ExtBuilder, RuntimeEvent as MetaEvent, Test, }, - AddressMapper, Error, Pallet, + AddressMapper, Error, Pallet, ReentrancyProtection, }; use assert_matches::assert_matches; use frame_support::{assert_err, assert_ok, parameter_types}; @@ -631,7 +631,14 @@ fn max_depth() { let value = 0; let recurse_ch = MockLoader::insert(Call, |ctx, _| { // Try to call into yourself. - let r = ctx.ext.call(&Default::default(), &BOB_ADDR, U256::zero(), vec![], true, false); + let r = ctx.ext.call( + &Default::default(), + &BOB_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false, + ); ReachedBottom::mutate(|reached_bottom| { if !*reached_bottom { @@ -685,8 +692,14 @@ fn caller_returns_proper_values() { // Call into CHARLIE contract. assert_matches!( - ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false), + ctx.ext.call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false + ), Ok(_) ); exec_success() @@ -741,8 +754,14 @@ fn origin_returns_proper_values() { // Call into CHARLIE contract. assert_matches!( - ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false), + ctx.ext.call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false + ), Ok(_) ); exec_success() @@ -886,7 +905,14 @@ fn caller_is_origin_returns_proper_values() { assert!(ctx.ext.caller_is_origin(false)); // BOB calls CHARLIE ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) + .call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false, + ) .map(|_| ctx.ext.last_frame_output().clone()) }); @@ -971,7 +997,14 @@ fn root_caller_succeeds_with_consecutive_calls() { assert!(ctx.ext.caller_is_root(false)); // BOB calls CHARLIE. ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) + .call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false, + ) .map(|_| ctx.ext.last_frame_output().clone()) }); @@ -1001,8 +1034,14 @@ fn address_returns_proper_values() { // Call into charlie contract. assert_matches!( - ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false), + ctx.ext.call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false + ), Ok(_) ); exec_success() @@ -1319,7 +1358,14 @@ fn in_memory_changes_not_discarded() { info.storage_byte_deposit = 42; assert_eq!( ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) + .call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false + ) .map(|_| ctx.ext.last_frame_output().clone()), exec_trapped() ); @@ -1330,7 +1376,14 @@ fn in_memory_changes_not_discarded() { let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext - .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .call( + &Default::default(), + &BOB_ADDR, + U256::zero(), + vec![99], + ReentrancyProtection::AllowReentry, + false + ) .is_ok()); exec_trapped() }); @@ -1369,7 +1422,7 @@ fn recursive_call_during_constructor_is_balance_transfer() { &addr, (balance - 1).into(), vec![], - true, + ReentrancyProtection::AllowReentry, false )); @@ -1380,7 +1433,7 @@ fn recursive_call_during_constructor_is_balance_transfer() { &addr, 1u32.into(), vec![1, 2, 3, 4], - true, + ReentrancyProtection::AllowReentry, false )); exec_success() @@ -1420,8 +1473,14 @@ fn cannot_send_more_balance_than_available_to_self() { let balance = ctx.ext.balance(); assert_err!( - ctx.ext - .call(&Default::default(), &addr, (balance + 1).into(), vec![], true, false), + ctx.ext.call( + &Default::default(), + &addr, + (balance + 1).into(), + vec![], + ReentrancyProtection::AllowReentry, + false + ), >::TransferFailed, ); exec_success() @@ -1454,7 +1513,14 @@ fn call_reentry_direct_recursion() { let code_bob = MockLoader::insert(Call, |ctx, _| { let dest = H160::from_slice(ctx.input_data.as_ref()); ctx.ext - .call(&Default::default(), &dest, U256::zero(), vec![], false, false) + .call( + &Default::default(), + &dest, + U256::zero(), + vec![], + ReentrancyProtection::Strict, + false, + ) .map(|_| ctx.ext.last_frame_output().clone()) }); @@ -1497,7 +1563,14 @@ fn call_deny_reentry() { let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], false, false) + .call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::Strict, + false, + ) .map(|_| ctx.ext.last_frame_output().clone()) } else { exec_success() @@ -1507,7 +1580,14 @@ fn call_deny_reentry() { // call BOB with input set to '1' let code_charlie = MockLoader::insert(Call, |ctx, _| { ctx.ext - .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![1], true, false) + .call( + &Default::default(), + &BOB_ADDR, + U256::zero(), + vec![1], + ReentrancyProtection::AllowReentry, + false, + ) .map(|_| ctx.ext.last_frame_output().clone()) }); @@ -1608,7 +1688,14 @@ fn nonce() { // a plain call should not influence the account counter ctx.ext - .call(&Default::default(), &addr, U256::zero(), vec![], false, false) + .call( + &Default::default(), + &addr, + U256::zero(), + vec![], + ReentrancyProtection::Strict, + false, + ) .unwrap(); assert_eq!(System::account_nonce(ALICE), alice_nonce); @@ -2114,7 +2201,14 @@ fn get_transient_storage_works() { ); assert_eq!( ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false,) + .call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false, + ) .map(|_| ctx.ext.last_frame_output().clone()), exec_success() ); @@ -2136,7 +2230,14 @@ fn get_transient_storage_works() { let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext - .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .call( + &Default::default(), + &BOB_ADDR, + U256::zero(), + vec![99], + ReentrancyProtection::AllowReentry, + false + ) .is_ok()); // CHARLIE can not read BOB`s storage. assert_eq!(ctx.ext.get_transient_storage(storage_key_1), None); @@ -2210,7 +2311,14 @@ fn rollback_transient_storage_works() { ); assert_eq!( ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) + .call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false + ) .map(|_| ctx.ext.last_frame_output().clone()), exec_trapped() ); @@ -2228,7 +2336,14 @@ fn rollback_transient_storage_works() { let code_charlie = MockLoader::insert(Call, |ctx, _| { assert!(ctx .ext - .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .call( + &Default::default(), + &BOB_ADDR, + U256::zero(), + vec![99], + ReentrancyProtection::AllowReentry, + false + ) .is_ok()); exec_trapped() }); @@ -2313,7 +2428,7 @@ fn last_frame_output_works_on_instantiate() { &address, Pallet::::convert_native_to_evm(1), vec![], - true, + ReentrancyProtection::AllowReentry, false, ) .unwrap(); @@ -2376,7 +2491,14 @@ fn last_frame_output_works_on_nested_call() { ); ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false) + .call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false, + ) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -2395,7 +2517,14 @@ fn last_frame_output_works_on_nested_call() { assert!(ctx .ext - .call(&Default::default(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .call( + &Default::default(), + &BOB_ADDR, + U256::zero(), + vec![99], + ReentrancyProtection::AllowReentry, + false + ) .is_ok()); assert_eq!( ctx.ext.last_frame_output(), @@ -2437,7 +2566,7 @@ fn last_frame_output_is_always_reset() { &H160::zero(), U256::max_value(), vec![], - true, + ReentrancyProtection::AllowReentry, false, ), Err(Error::::BalanceConversionFailed.into()) @@ -2553,7 +2682,14 @@ fn correct_immutable_data_in_delegate_call() { // In a regular call, we should witness the callee immutable data assert_eq!( ctx.ext - .call(&Default::default(), &CHARLIE_ADDR, U256::zero(), vec![], true, false,) + .call( + &Default::default(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + ReentrancyProtection::AllowReentry, + false, + ) .map(|_| ctx.ext.last_frame_output().data.clone()), Ok(vec![2]), ); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 804cf561e0ea0..d162bcbfc4ec5 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -52,7 +52,7 @@ use crate::{ runtime::SetWeightLimit, CallTracer, GenericTransaction, PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, }, - exec::{AccountIdOf, ExecError, Stack as ExecStack}, + exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack}, storage::{AccountType, DeletionQueueManager}, tracing::if_tracing, vm::{pvm::extract_code_and_data, CodeInfo, RuntimeCosts}, diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index b99ea119be321..75c8358f8d8e7 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -96,6 +96,9 @@ pub const EVM_MEMORY_BYTES: u32 = 1024 * 1024; /// EVM interpreter stack limit. pub const EVM_STACK_LIMIT: u32 = 1024; +/// The call stipend gas amount defined in the EVM +pub const SOLIDITY_CALL_STIPEND: u32 = 2300; + /// Limits that are only enforced on code upload. /// /// # Note diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index 0b26969a39db3..f81a69ba5a2e5 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -20,9 +20,17 @@ use super::{ FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, Saturating, SignedGas, State, StorageDeposit, TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, }; +use crate::{limits::SOLIDITY_CALL_STIPEND, metering::weight::Token, vm::evm::EVMGas}; use core::marker::PhantomData; +fn determine_call_stipend() -> Weight { + let gas = EVMGas(SOLIDITY_CALL_STIPEND.into()); + >::weight(&gas) +} + pub mod substrate_execution { + use num_traits::One; + use super::*; /// Create a transaction-level (root) meter for Substrate-style execution. @@ -37,7 +45,7 @@ pub mod substrate_execution { deposit_limit: BalanceOf, ) -> Result, DispatchError> { Ok(TransactionMeter { - weight: WeightMeter::new(Some(weight_limit)), + weight: WeightMeter::new(Some(weight_limit), None), deposit: RootStorageMeter::new(Some(deposit_limit)), // ignore max total gas for Substrate executions max_total_gas: Default::default(), @@ -72,26 +80,26 @@ pub mod substrate_execution { let total_consumed_deposit = meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); - let (nested_weight_limit, nested_deposit_limit) = { - let weight_left = meter - .weight - .weight_limit - .expect("Weight limits all always defined for WeightAndDeposit; qed") - .checked_sub(&self_consumed_weight) - .ok_or(>::OutOfGas)?; + let weight_left = meter + .weight + .weight_limit + .expect("Weight limits all always defined for WeightAndDeposit; qed") + .checked_sub(&self_consumed_weight) + .ok_or(>::OutOfGas)?; - let deposit_limit = meter - .deposit - .limit - .expect("Deposit limits all always defined for WeightAndDeposit; qed"); - let deposit_left = self_consumed_deposit - .available(&deposit_limit) - .ok_or(>::StorageDepositLimitExhausted)?; + let deposit_limit = meter + .deposit + .limit + .expect("Deposit limits all always defined for WeightAndDeposit; qed"); + let deposit_left = self_consumed_deposit + .available(&deposit_limit) + .ok_or(>::StorageDepositLimitExhausted)?; + let (nested_weight_limit, nested_deposit_limit, stipend) = { match limit { - CallResources::NoLimits => (weight_left, deposit_left), + CallResources::NoLimits => (weight_left, deposit_left, None), - CallResources::Ethereum(gas) => { + CallResources::Ethereum { gas, add_stipend } => { // Convert leftover weight and deposit to an ethereum-gas equivalent, // then cap that gas by the requested `gas`. Distribute the capped gas // back into weight and deposit portions using the same ratio so that @@ -102,33 +110,46 @@ pub mod substrate_execution { let gas_left = weight_gas.saturating_add(deposit_gas); let gas_limit = gas_left.min(*gas); - if (gas_left).is_zero() { - (weight_left, deposit_left) + let ratio = if gas_left.is_zero() { + FixedU128::one() } else { - let ratio = FixedU128::from_rational( + FixedU128::from_rational( gas_limit.saturated_into(), gas_left.saturated_into(), - ); + ) + }; + + let mut weight_limit = Weight::from_parts( + ratio.saturating_mul_int(weight_left.ref_time()), + ratio.saturating_mul_int(weight_left.proof_size()), + ); + let deposit_limit = ratio.saturating_mul_int(deposit_left); - let weight_limit = Weight::from_parts( - ratio.saturating_mul_int(weight_left.ref_time()), - ratio.saturating_mul_int(weight_left.proof_size()), - ); - let deposit_limit = ratio.saturating_mul_int(deposit_left); + let stipend = if *add_stipend { + let weight_stipend = determine_call_stipend::(); + if weight_left.any_lt(weight_stipend) { + Err(>::OutOfGas)? + } + + weight_limit.saturating_accrue(weight_stipend); + + Some(weight_stipend) + } else { + None + }; - (weight_limit, deposit_limit) - } + (weight_left.min(weight_limit), deposit_left.min(deposit_limit), stipend) }, CallResources::WeightDeposit { weight, deposit_limit } => // when explicit weight+deposit requested, take the minimum of parent's left // and the requested per-call limits. - (weight_left.min(*weight), deposit_left.min(*deposit_limit)), + (weight_left.min(*weight), deposit_left.min(*deposit_limit), None), } }; Ok(FrameMeter:: { - weight: WeightMeter::new(Some(nested_weight_limit)), + weight: WeightMeter::new(Some(nested_weight_limit), stipend), deposit: meter.deposit.nested(Some(nested_deposit_limit)), max_total_gas: Default::default(), total_consumed_weight_before: total_consumed_weight, @@ -248,7 +269,7 @@ pub mod ethereum_execution { eth_tx_info: EthTxInfo, ) -> Result, DispatchError> { let meter = TransactionMeter { - weight: WeightMeter::new(maybe_weight_limit), + weight: WeightMeter::new(maybe_weight_limit, None), deposit: RootStorageMeter::new(None), max_total_gas: SignedGas::Positive(eth_gas_limit), total_consumed_weight_before: Default::default(), @@ -301,80 +322,91 @@ pub mod ethereum_execution { return Err(>::OutOfGas.into()); }; - let (nested_gas_limit, nested_weight_limit, nested_deposit_limit) = { - // In the simple case the parent has no explicit weight and storage deposit limits and - // the requested CallResources are gas-only; then nested frames only need a gas cap. - let is_simple = meter.weight.weight_limit.is_none() && - matches!(limit, CallResources::NoLimits | CallResources::Ethereum(..)); - - if is_simple { - let nested_gas_limit = if let CallResources::Ethereum(gas) = limit { - gas_left.min(*gas) - } else { - gas_left - }; - (nested_gas_limit, None, None) - } else { - // More complex path: derive a concrete weight_left and deposit_left. - let weight_left = { - let unbounded_weight_left = eth_tx_info - .weight_remaining( - &meter.max_total_gas, - &total_consumed_weight, - &total_consumed_deposit, - ) - .ok_or(>::OutOfGas)?; - - match meter.weight.weight_limit { - Some(weight_limit) => unbounded_weight_left.min( - weight_limit - .checked_sub(&self_consumed_weight) - .ok_or(>::OutOfGas)?, - ), - None => unbounded_weight_left, - } - }; - - let deposit_left = { - let unbounded_deposit_left: BalanceOf = - T::FeeInfo::next_fee_multiplier().saturating_mul_int(gas_left); - match meter.deposit.limit { - Some(deposit_limit) => unbounded_deposit_left.min( - self_consumed_deposit - .available(&deposit_limit) - .ok_or(>::StorageDepositLimitExhausted)?, - ), - None => unbounded_deposit_left, - } - }; - - match limit { - CallResources::NoLimits => (gas_left, Some(weight_left), Some(deposit_left)), - CallResources::Ethereum(gas) => - (gas_left.min(*gas), Some(weight_left), Some(deposit_left)), - CallResources::WeightDeposit { weight, deposit_limit } => { - let nested_weight_limit = weight_left.min(*weight); - let nested_deposit_limit = deposit_left.min(*deposit_limit); - - let new_max_total_gas = eth_tx_info.gas_consumption( - &total_consumed_weight.saturating_add(nested_weight_limit), - &total_consumed_deposit - .saturating_add(&StorageDeposit::Charge(nested_deposit_limit)), - ); - - let Some(gas_limit) = - new_max_total_gas.saturating_sub(&total_gas_consumption).as_positive() - else { - return Err(>::OutOfGas.into()); - }; + let weight_left = { + let unbounded_weight_left = eth_tx_info + .weight_remaining( + &meter.max_total_gas, + &total_consumed_weight, + &total_consumed_deposit, + ) + .ok_or(>::OutOfGas)?; + + match meter.weight.weight_limit { + Some(weight_limit) => unbounded_weight_left.min( + weight_limit.checked_sub(&self_consumed_weight).ok_or(>::OutOfGas)?, + ), + None => unbounded_weight_left, + } + }; + + let deposit_left = { + let unbounded_deposit_left: BalanceOf = + T::FeeInfo::next_fee_multiplier().saturating_mul_int(gas_left); + match meter.deposit.limit { + Some(deposit_limit) => unbounded_deposit_left.min( + self_consumed_deposit + .available(&deposit_limit) + .ok_or(>::StorageDepositLimitExhausted)?, + ), + None => unbounded_deposit_left, + } + }; + + let (nested_gas_limit, nested_weight_limit, nested_deposit_limit, stipend) = { + match limit { + CallResources::NoLimits => ( + gas_left, + if meter.weight.weight_limit.is_none() { None } else { Some(weight_left) }, + if meter.deposit.limit.is_none() { None } else { Some(deposit_left) }, + None, + ), + + CallResources::Ethereum { gas, add_stipend } => { + let (gas_limit, stipend) = if *add_stipend { + let weight_stipend = determine_call_stipend::(); + if weight_left.any_lt(weight_stipend) { + Err(>::OutOfGas)? + } ( - gas_left.min(gas_limit), - Some(nested_weight_limit), - Some(nested_deposit_limit), + gas.saturating_add(T::FeeInfo::weight_to_fee(&weight_stipend)), + Some(weight_stipend), ) - }, - } + } else { + (*gas, None) + }; + + ( + gas_left.min(gas_limit), + if meter.weight.weight_limit.is_none() { None } else { Some(weight_left) }, + if meter.deposit.limit.is_none() { None } else { Some(deposit_left) }, + stipend, + ) + }, + + CallResources::WeightDeposit { weight, deposit_limit } => { + let nested_weight_limit = weight_left.min(*weight); + let nested_deposit_limit = deposit_left.min(*deposit_limit); + + let new_max_total_gas = eth_tx_info.gas_consumption( + &total_consumed_weight.saturating_add(nested_weight_limit), + &total_consumed_deposit + .saturating_add(&StorageDeposit::Charge(nested_deposit_limit)), + ); + + let Some(gas_limit) = + new_max_total_gas.saturating_sub(&total_gas_consumption).as_positive() + else { + return Err(>::OutOfGas.into()); + }; + + ( + gas_left.min(gas_limit), + Some(nested_weight_limit), + Some(nested_deposit_limit), + None, + ) + }, } }; @@ -382,7 +414,7 @@ pub mod ethereum_execution { total_gas_consumption.saturating_add(&SignedGas::Positive(nested_gas_limit)); Ok(FrameMeter:: { - weight: WeightMeter::new(nested_weight_limit), + weight: WeightMeter::new(nested_weight_limit, stipend), deposit: meter.deposit.nested(nested_deposit_limit), max_total_gas: nested_max_total_gas, total_consumed_weight_before: total_consumed_weight, diff --git a/substrate/frame/revive/src/metering/weight.rs b/substrate/frame/revive/src/metering/weight.rs index a3e7464859224..9ee7bd57a0060 100644 --- a/substrate/frame/revive/src/metering/weight.rs +++ b/substrate/frame/revive/src/metering/weight.rs @@ -140,11 +140,11 @@ pub struct WeightMeter { } impl WeightMeter { - pub fn new(weight_limit: Option) -> Self { + pub fn new(weight_limit: Option, stipend: Option) -> Self { WeightMeter { weight_limit, weight_consumed: Default::default(), - weight_consumed_highest: Default::default(), + weight_consumed_highest: stipend.unwrap_or_default(), engine_meter: EngineMeter::new(), _phantom: PhantomData, #[cfg(test)] @@ -281,7 +281,7 @@ impl WeightMeter { #[cfg(test)] pub fn nested(&mut self, amount: Weight) -> Self { - Self::new(Some(self.weight_left().min(amount))) + Self::new(Some(self.weight_left().min(amount)), None) } } @@ -338,13 +338,13 @@ mod tests { #[test] fn it_works() { - let weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0))); + let weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0)), None); assert_eq!(weight_meter.weight_left(), Weight::from_parts(50000, 0)); } #[test] fn tracing() { - let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0))); + let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0)), None); assert!(!weight_meter.charge(SimpleToken(1), weight_meter.weight_left()).is_err()); let mut tokens = weight_meter.tokens().iter(); @@ -354,7 +354,7 @@ mod tests { // This test makes sure that nothing can be executed if there is no weight. #[test] fn refuse_to_execute_anything_if_zero() { - let mut weight_meter = WeightMeter::::new(Some(Weight::zero())); + let mut weight_meter = WeightMeter::::new(Some(Weight::zero()), None); assert!(weight_meter.charge(SimpleToken(1), weight_meter.weight_left()).is_err()); } @@ -365,7 +365,7 @@ mod tests { #[test] fn nested_zero_weight_requested() { let test_weight = 50000.into(); - let mut weight_meter = WeightMeter::::new(Some(test_weight)); + let mut weight_meter = WeightMeter::::new(Some(test_weight), None); let weight_for_nested_call = weight_meter.nested(0.into()); assert_eq!(weight_meter.weight_left(), 50000.into()); @@ -375,7 +375,7 @@ mod tests { #[test] fn nested_some_weight_requested() { let test_weight = 50000.into(); - let mut weight_meter = WeightMeter::::new(Some(test_weight)); + let mut weight_meter = WeightMeter::::new(Some(test_weight), None); let weight_for_nested_call = weight_meter.nested(10000.into()); assert_eq!(weight_meter.weight_consumed(), 0.into()); @@ -385,7 +385,7 @@ mod tests { #[test] fn nested_all_weight_requested() { let test_weight = Weight::from_parts(50000, 50000); - let mut weight_meter = WeightMeter::::new(Some(test_weight)); + let mut weight_meter = WeightMeter::::new(Some(test_weight), None); let weight_for_nested_call = weight_meter.nested(test_weight); assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); @@ -395,7 +395,7 @@ mod tests { #[test] fn nested_excess_weight_requested() { let test_weight = Weight::from_parts(50000, 50000); - let mut weight_meter = WeightMeter::::new(Some(test_weight)); + let mut weight_meter = WeightMeter::::new(Some(test_weight), None); let weight_for_nested_call = weight_meter.nested(test_weight + 10000.into()); assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); @@ -405,7 +405,7 @@ mod tests { // Make sure that the weight meter does not charge in case of overcharge #[test] fn overcharge_does_not_charge() { - let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(200, 0))); + let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(200, 0)), None); // The first charge is should lead to OOG. assert!(weight_meter.charge(SimpleToken(300), weight_meter.weight_left()).is_err()); @@ -418,7 +418,7 @@ mod tests { // possible. #[test] fn charge_exact_amount() { - let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(25, 0))); + let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(25, 0)), None); assert!(!weight_meter.charge(SimpleToken(25), weight_meter.weight_left()).is_err()); } } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index fbcc34e88470a..0afd4ffcedf0d 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -20,6 +20,7 @@ mod pallet_dummy; mod precompiles; mod pvm; mod sol; +mod stipends; use std::collections::HashMap; diff --git a/substrate/frame/revive/src/tests/precompiles.rs b/substrate/frame/revive/src/tests/precompiles.rs index 4394973ea547d..a6039c1a9987e 100644 --- a/substrate/frame/revive/src/tests/precompiles.rs +++ b/substrate/frame/revive/src/tests/precompiles.rs @@ -20,7 +20,7 @@ use crate::{ exec::{CallResources, ErrorOrigin, ExecError}, precompiles::{AddressMatcher, Error, Ext, ExtWithInfo, Precompile, Token}, - Config, DispatchError, ExecOrigin as Origin, Weight, U256, + Config, DispatchError, ExecOrigin as Origin, ReentrancyProtection, Weight, U256, }; use alloc::vec::Vec; use alloy_core::{ @@ -113,7 +113,7 @@ impl Precompile for NoInfo { &env.address(), 0.into(), vec![42; *inputLen as usize], - true, + ReentrancyProtection::AllowReentry, false, )?; Ok(Vec::new()) diff --git a/substrate/frame/revive/src/tests/stipends.rs b/substrate/frame/revive/src/tests/stipends.rs new file mode 100644 index 0000000000000..d4ba5a188405f --- /dev/null +++ b/substrate/frame/revive/src/tests/stipends.rs @@ -0,0 +1,302 @@ +// 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. + +use crate::{ + test_utils::{builder::Contract, ALICE, BOB_ADDR}, + tests::{builder, ExtBuilder, RuntimeEvent, Test}, + BalanceWithDust, Code, Config, Pallet, System, +}; +use alloy_core::sol_types::{SolCall, SolEvent}; +use frame_support::traits::fungible::Mutate; +use pallet_revive_fixtures::{ + compile_module_with_type, ComplexReceiver, FixtureType, SimpleReceiver, StipendSender, +}; +use test_case::test_case; + +pub use sp_core::H160; + +enum Receiver { + Contract(&'static str), + EOA(H160), +} + +#[derive(Clone, Copy)] +enum TestCase { + Bob, + DoNothingReceiver, + SimpleReceiver, + ComplexReceiver, +} + +impl TestCase { + fn receiver(&self) -> Receiver { + match self { + &TestCase::Bob => Receiver::EOA(BOB_ADDR), + &TestCase::DoNothingReceiver => Receiver::Contract("DoNothingReceiver"), + &TestCase::SimpleReceiver => Receiver::Contract("SimpleReceiver"), + &TestCase::ComplexReceiver => Receiver::Contract("ComplexReceiver"), + } + } +} + +fn get_contract_events() -> Vec<(H160, Vec, Vec<[u8; 32]>)> { + let events = System::::events(); + events + .into_iter() + .filter_map(|e| match e.event { + RuntimeEvent::Contracts(crate::Event::ContractEmitted { contract, data, topics }) => + Some(( + contract, + data, + topics.into_iter().map(|t| t.to_fixed_bytes()).collect::>(), + )), + _ => None, + }) + .collect() +} + +#[test_case(TestCase::Bob; "EOA")] +#[test_case(TestCase::DoNothingReceiver; "DoNothingReceiver")] +#[test_case(TestCase::SimpleReceiver; "SimpleReceiver")] +#[test_case(TestCase::ComplexReceiver; "ComplexReceiver")] +fn evm_call_stipends_work_for_transfers(test_case: TestCase) { + let (expect_receive_event, expect_success) = match test_case { + TestCase::Bob => (false, true), + TestCase::DoNothingReceiver => (false, true), + TestCase::SimpleReceiver => (true, true), + TestCase::ComplexReceiver => (false, false), + }; + + let (code, _) = compile_module_with_type("StipendSender", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 10_000_000_000_000); + + let Contract { addr: stipend_sender_address, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let receiver_addr = match test_case.receiver() { + Receiver::Contract(name) => { + let (receiver_code, _) = compile_module_with_type(name, FixtureType::Solc).unwrap(); + let Contract { addr: receiver_addr, .. } = + builder::bare_instantiate(Code::Upload(receiver_code)) + .build_and_unwrap_contract(); + + receiver_addr + }, + + Receiver::EOA(address) => address, + }; + + let balance_before = Pallet::::evm_balance(&receiver_addr); + + let amount = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::( + 1_000_000_000_000, + 0, + )); + + let result = builder::bare_call(stipend_sender_address) + .data(StipendSender::sendViaTransferCall { to: receiver_addr.0.into() }.abi_encode()) + .evm_value(amount) + .build_and_unwrap_result(); + + let balance_after = Pallet::::evm_balance(&receiver_addr); + + let mut contract_events = get_contract_events().into_iter(); + + if expect_receive_event { + let (_contract, data, topics) = contract_events.next().unwrap(); + let decoded_event = + SimpleReceiver::Received::decode_raw_log(topics, data.as_slice()).unwrap(); + assert_eq!(decoded_event.from, stipend_sender_address.0); + assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); + } + + if expect_success { + assert!(!result.did_revert()); + assert_eq!(amount, balance_after.saturating_sub(balance_before)); + + let (_contract, data, topics) = contract_events.next().unwrap(); + let decoded_event = + StipendSender::TransferSuccess::decode_raw_log(topics, data.as_slice()).unwrap(); + assert_eq!(decoded_event.method, "transfer"); + assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); + assert_eq!(decoded_event.to, receiver_addr.0); + } else { + assert!(result.did_revert()); + assert_eq!(balance_after, balance_before); + } + }); +} + +#[test_case(TestCase::Bob; "EOA")] +#[test_case(TestCase::DoNothingReceiver; "DoNothingReceiver")] +#[test_case(TestCase::SimpleReceiver; "SimpleReceiver")] +#[test_case(TestCase::ComplexReceiver; "ComplexReceiver")] +fn evm_call_stipends_work_for_sends(test_case: TestCase) { + let (expect_receive_event, expect_success) = match test_case { + TestCase::Bob => (false, true), + TestCase::DoNothingReceiver => (false, true), + TestCase::SimpleReceiver => (true, true), + TestCase::ComplexReceiver => (false, false), + }; + + let (code, _) = compile_module_with_type("StipendSender", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 10_000_000_000_000); + + let Contract { addr: stipend_sender_address, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let receiver_addr = match test_case.receiver() { + Receiver::Contract(name) => { + let (receiver_code, _) = compile_module_with_type(name, FixtureType::Solc).unwrap(); + let Contract { addr: receiver_addr, .. } = + builder::bare_instantiate(Code::Upload(receiver_code)) + .build_and_unwrap_contract(); + + receiver_addr + }, + + Receiver::EOA(address) => address, + }; + + let balance_before = Pallet::::evm_balance(&receiver_addr); + + let amount = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::( + 1_000_000_000_000, + 0, + )); + + let result = builder::bare_call(stipend_sender_address) + .data(StipendSender::sendViaSendCall { to: receiver_addr.0.into() }.abi_encode()) + .evm_value(amount) + .build_and_unwrap_result(); + + let balance_after = Pallet::::evm_balance(&receiver_addr); + + let mut contract_events = get_contract_events().into_iter(); + + if expect_receive_event { + let (_contract, data, topics) = contract_events.next().unwrap(); + let decoded_event = + SimpleReceiver::Received::decode_raw_log(topics, data.as_slice()).unwrap(); + assert_eq!(decoded_event.from, stipend_sender_address.0); + assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); + } + + if expect_success { + assert!(!result.did_revert()); + assert_eq!(amount, balance_after.saturating_sub(balance_before)); + + let (_contract, data, topics) = contract_events.next().unwrap(); + let decoded_event = + StipendSender::TransferSuccess::decode_raw_log(topics, data.as_slice()).unwrap(); + assert_eq!(decoded_event.method, "send"); + assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); + assert_eq!(decoded_event.to, receiver_addr.0); + } else { + assert!(!result.did_revert()); + assert_eq!(balance_after, balance_before); + + let (_contract, data, topics) = contract_events.next().unwrap(); + let decoded_event = + StipendSender::TransferFailed::decode_raw_log(topics, data.as_slice()).unwrap(); + assert_eq!(decoded_event.method, "send"); + assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); + assert_eq!(decoded_event.to, receiver_addr.0); + } + }); +} + +#[test_case(TestCase::Bob; "EOA")] +#[test_case(TestCase::DoNothingReceiver; "DoNothingReceiver")] +#[test_case(TestCase::SimpleReceiver; "SimpleReceiver")] +#[test_case(TestCase::ComplexReceiver; "ComplexReceiver")] +fn evm_call_stipends_work_for_calls(test_case: TestCase) { + let (expect_receive_event, expect_simple_receive_event) = match test_case { + TestCase::Bob => (false, true), + TestCase::DoNothingReceiver => (false, true), + TestCase::SimpleReceiver => (true, true), + TestCase::ComplexReceiver => (true, false), + }; + + let (code, _) = compile_module_with_type("StipendSender", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 10_000_000_000_000); + + let Contract { addr: stipend_sender_address, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let receiver_addr = match test_case.receiver() { + Receiver::Contract(name) => { + let (receiver_code, _) = compile_module_with_type(name, FixtureType::Solc).unwrap(); + let Contract { addr: receiver_addr, .. } = + builder::bare_instantiate(Code::Upload(receiver_code)) + .build_and_unwrap_contract(); + + receiver_addr + }, + + Receiver::EOA(address) => address, + }; + + let balance_before = Pallet::::evm_balance(&receiver_addr); + + let amount = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::( + 1_000_000_000_000, + 0, + )); + + let result = builder::bare_call(stipend_sender_address) + .data(StipendSender::sendViaCallCall { to: receiver_addr.0.into() }.abi_encode()) + .evm_value(amount) + .build_and_unwrap_result(); + + let balance_after = Pallet::::evm_balance(&receiver_addr); + + let mut contract_events = get_contract_events().into_iter(); + + if expect_receive_event { + let (_contract, data, topics) = contract_events.next().unwrap(); + if expect_simple_receive_event { + let decoded_event = + SimpleReceiver::Received::decode_raw_log(topics, data.as_slice()).unwrap(); + assert_eq!(decoded_event.from, stipend_sender_address.0); + assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); + } else { + let decoded_event = + ComplexReceiver::Received::decode_raw_log(topics, data.as_slice()).unwrap(); + assert_eq!(decoded_event.from, stipend_sender_address.0); + assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); + assert_eq!(decoded_event.newCounter.try_into(), Ok(1)); + } + } + + assert!(!result.did_revert()); + assert_eq!(amount, balance_after.saturating_sub(balance_before)); + + let (_contract, data, topics) = contract_events.next().unwrap(); + let decoded_event = + StipendSender::TransferSuccess::decode_raw_log(topics, data.as_slice()).unwrap(); + assert_eq!(decoded_event.method, "call"); + assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); + assert_eq!(decoded_event.to, receiver_addr.0); + }); +} diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 91da9c18e5460..8d60d14d23b45 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -50,7 +50,7 @@ pub(crate) const DIFFICULTY: u64 = 2500000000000000_u64; /// Cost for a single unit of EVM gas. #[derive(Eq, PartialEq, Debug, Clone, Copy)] -pub struct EVMGas(u64); +pub struct EVMGas(pub u64); impl Token for EVMGas { fn weight(&self) -> Weight { diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index 9525e0887b4df..27ac08185201e 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -24,7 +24,7 @@ use crate::{ evm::{interpreter::Halt, util::as_usize_or_halt, Interpreter}, Ext, RuntimeCosts, }, - Code, Error, Pallet, H160, LOG_TARGET, U256, + Code, Error, Pallet, ReentrancyProtection, H160, LOG_TARGET, U256, }; use alloc::{vec, vec::Vec}; pub use call_helpers::{charge_call_gas, get_memory_in_and_out_ranges}; @@ -188,17 +188,29 @@ fn run_call<'a, E: Ext>( value: U256, return_memory_range: Range, ) -> ControlFlow { + // We use SOLIDITY_CALL_STIPEND to detect the typical gas limit solc defines as a call stipend + // This is just a heuristic + let add_stipend = !value.is_zero() || + gas_limit + .try_into() + .is_ok_and(|limit: u32| limit == crate::limits::SOLIDITY_CALL_STIPEND); + let call_result = match scheme { CallScheme::Call | CallScheme::StaticCall => interpreter.ext.call( - &CallResources::from_ethereum_gas(gas_limit), + &CallResources::from_ethereum_gas(gas_limit, add_stipend), &callee, value, input, - true, + // protect against re-entrancy when we grant the stipend + if add_stipend { + ReentrancyProtection::AllowNext + } else { + ReentrancyProtection::AllowReentry + }, scheme.is_static_call(), ), CallScheme::DelegateCall => interpreter.ext.delegate_call( - &CallResources::from_ethereum_gas(gas_limit), + &CallResources::from_ethereum_gas(gas_limit, add_stipend), callee, input, ), diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index f5bfe332ab02b..df61f946b2d1d 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -28,7 +28,7 @@ use crate::{ metering::weight::ChargedAmount, precompiles::{All as AllPrecompiles, Precompiles}, primitives::ExecReturnValue, - Code, Config, Error, Pallet, RuntimeCosts, LOG_TARGET, SENTINEL, + Code, Config, Error, Pallet, ReentrancyProtection, RuntimeCosts, LOG_TARGET, SENTINEL, }; use alloc::{vec, vec::Vec}; use codec::Encode; @@ -686,12 +686,19 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { dust_transfer: Pallet::::has_dust(value), })?; } + + let reentrancy = if flags.contains(CallFlags::ALLOW_REENTRY) { + ReentrancyProtection::AllowReentry + } else { + ReentrancyProtection::Strict + }; + self.ext.call( &CallResources::from_weight_and_deposit(weight, deposit_limit), &callee, value, input_data, - flags.contains(CallFlags::ALLOW_REENTRY), + reentrancy, read_only, ) }, From 3115020558b3bb1f9ef23a47ef814143e2ceb7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 13 Nov 2025 06:22:25 -0300 Subject: [PATCH 12/69] Fix errors in other crates --- .../xcm/pallet-xcm/precompiles/src/tests.rs | 110 ++++++++++++------ substrate/frame/assets/precompiles/src/lib.rs | 3 +- .../frame/assets/precompiles/src/tests.rs | 36 ++++-- substrate/frame/revive/src/precompiles.rs | 2 +- 4 files changed, 100 insertions(+), 51 deletions(-) diff --git a/polkadot/xcm/pallet-xcm/precompiles/src/tests.rs b/polkadot/xcm/pallet-xcm/precompiles/src/tests.rs index 3eed0aee7ca86..5d762930a340a 100644 --- a/polkadot/xcm/pallet-xcm/precompiles/src/tests.rs +++ b/polkadot/xcm/pallet-xcm/precompiles/src/tests.rs @@ -29,7 +29,7 @@ use pallet_revive::{ }, H160, }, - ExecConfig, U256, + ExecConfig, TransactionLimits, U256, }; use polkadot_parachain_primitives::primitives::Id as ParaId; use sp_runtime::traits::AccountIdConversion; @@ -75,8 +75,10 @@ fn test_xcm_send_precompile_works() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -124,8 +126,10 @@ fn test_xcm_send_precompile_to_parachain() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -173,8 +177,10 @@ fn test_xcm_send_precompile_fails() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -223,8 +229,10 @@ fn send_fails_on_old_location_version() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -250,8 +258,10 @@ fn send_fails_on_old_location_version() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -300,8 +310,10 @@ fn send_fails_on_old_xcm_version() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -328,8 +340,10 @@ fn send_fails_on_old_xcm_version() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -371,8 +385,10 @@ fn test_xcm_execute_precompile_works() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_weight_call, ExecConfig::new_substrate_tx(), ); @@ -393,8 +409,10 @@ fn test_xcm_execute_precompile_works() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -432,8 +450,10 @@ fn test_xcm_execute_precompile_different_beneficiary() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_weight_call, ExecConfig::new_substrate_tx(), ); @@ -454,8 +474,10 @@ fn test_xcm_execute_precompile_different_beneficiary() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -501,8 +523,10 @@ fn test_xcm_execute_precompile_fails() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_weight_call, ExecConfig::new_substrate_tx(), ); @@ -523,8 +547,10 @@ fn test_xcm_execute_precompile_fails() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -569,8 +595,10 @@ fn execute_fails_on_old_version() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_weight_call, ExecConfig::new_substrate_tx(), ); @@ -598,8 +626,10 @@ fn execute_fails_on_old_version() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -625,8 +655,10 @@ fn execute_fails_on_old_version() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_call, ExecConfig::new_substrate_tx(), ); @@ -674,8 +706,10 @@ fn weight_fails_on_old_version() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_weight_call, ExecConfig::new_substrate_tx(), ); @@ -698,8 +732,10 @@ fn weight_fails_on_old_version() { RuntimeOrigin::signed(ALICE), xcm_precompile_addr, U256::zero(), - Weight::MAX, - u128::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u128::MAX, + }, encoded_weight_call, ExecConfig::new_substrate_tx(), ); diff --git a/substrate/frame/assets/precompiles/src/lib.rs b/substrate/frame/assets/precompiles/src/lib.rs index c39990013a2ec..3e593c219a5ac 100644 --- a/substrate/frame/assets/precompiles/src/lib.rs +++ b/substrate/frame/assets/precompiles/src/lib.rs @@ -33,7 +33,8 @@ use pallet_revive::precompiles::{ primitives::IntoLogData, sol_types::{Revert, SolCall}, }, - AddressMapper, AddressMatcher, Error, Ext, Precompile, RuntimeCosts, H160, H256, + AddressMapper, AddressMatcher, Error, Ext, Precompile, RuntimeCosts, TransactionLimits, H160, + H256, }; #[cfg(test)] diff --git a/substrate/frame/assets/precompiles/src/tests.rs b/substrate/frame/assets/precompiles/src/tests.rs index 3ff71b3b2104a..3dd55a75dd393 100644 --- a/substrate/frame/assets/precompiles/src/tests.rs +++ b/substrate/frame/assets/precompiles/src/tests.rs @@ -76,8 +76,10 @@ fn precompile_transfer_works() { RuntimeOrigin::signed(from), H160::from(asset_addr), 0u32.into(), - Weight::MAX, - u64::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u64::MAX, + }, data, ExecConfig::new_substrate_tx(), ); @@ -115,8 +117,10 @@ fn total_supply_works() { RuntimeOrigin::signed(owner), H160::from(asset_addr), 0u32.into(), - Weight::MAX, - u64::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u64::MAX, + }, data, ExecConfig::new_substrate_tx(), ) @@ -148,8 +152,10 @@ fn balance_of_works() { RuntimeOrigin::signed(owner), H160::from(asset_addr), 0u32.into(), - Weight::MAX, - u64::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u64::MAX, + }, data, ExecConfig::new_substrate_tx(), ) @@ -194,8 +200,10 @@ fn approval_works() { RuntimeOrigin::signed(owner), H160::from(asset_addr), 0u32.into(), - Weight::MAX, - u64::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u64::MAX, + }, data, ExecConfig::new_substrate_tx(), ); @@ -217,8 +225,10 @@ fn approval_works() { RuntimeOrigin::signed(owner), H160::from(asset_addr), 0u32.into(), - Weight::MAX, - u64::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u64::MAX, + }, data, ExecConfig::new_substrate_tx(), ) @@ -240,8 +250,10 @@ fn approval_works() { RuntimeOrigin::signed(spender), H160::from(asset_addr), 0u32.into(), - Weight::MAX, - u64::MAX, + TransactionLimits::WeightAndDeposit { + weight_limit: Weight::MAX, + deposit_limit: u64::MAX, + }, data, ExecConfig::new_substrate_tx(), ); diff --git a/substrate/frame/revive/src/precompiles.rs b/substrate/frame/revive/src/precompiles.rs index 64c715aee65ad..451f55c45cc08 100644 --- a/substrate/frame/revive/src/precompiles.rs +++ b/substrate/frame/revive/src/precompiles.rs @@ -33,7 +33,7 @@ pub use crate::{ exec::{ExecError, PrecompileExt as Ext, PrecompileWithInfoExt as ExtWithInfo}, metering::{storage::Diff, weight::Token}, vm::RuntimeCosts, - AddressMapper, + AddressMapper, TransactionLimits, }; pub use alloy_core as alloy; pub use sp_core::{H160, H256, U256}; From 695357c1c2e5e98eb3ca268272b28941858cefe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 13 Nov 2025 06:46:02 -0300 Subject: [PATCH 13/69] Fix errors in other crates (2) --- substrate/frame/assets/precompiles/src/lib.rs | 3 +-- substrate/frame/assets/precompiles/src/tests.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/substrate/frame/assets/precompiles/src/lib.rs b/substrate/frame/assets/precompiles/src/lib.rs index 3e593c219a5ac..c39990013a2ec 100644 --- a/substrate/frame/assets/precompiles/src/lib.rs +++ b/substrate/frame/assets/precompiles/src/lib.rs @@ -33,8 +33,7 @@ use pallet_revive::precompiles::{ primitives::IntoLogData, sol_types::{Revert, SolCall}, }, - AddressMapper, AddressMatcher, Error, Ext, Precompile, RuntimeCosts, TransactionLimits, H160, - H256, + AddressMapper, AddressMatcher, Error, Ext, Precompile, RuntimeCosts, H160, H256, }; #[cfg(test)] diff --git a/substrate/frame/assets/precompiles/src/tests.rs b/substrate/frame/assets/precompiles/src/tests.rs index 3dd55a75dd393..33e98017b6bc2 100644 --- a/substrate/frame/assets/precompiles/src/tests.rs +++ b/substrate/frame/assets/precompiles/src/tests.rs @@ -22,7 +22,7 @@ use crate::{ }; use alloy::primitives::U256; use frame_support::{assert_ok, traits::Currency}; -use pallet_revive::ExecConfig; +use pallet_revive::{precompiles::TransactionLimits, ExecConfig}; use sp_core::H160; use sp_runtime::Weight; From 3ee4ed52b9f13f58fb6fd11f7d06baa0303cab17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 13 Nov 2025 07:41:06 -0300 Subject: [PATCH 14/69] Fix errors in other crates (3) --- .../assets/asset-hub-westend/tests/tests.rs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 8351ce49fd772..22adeb0d0a84d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -58,7 +58,7 @@ use frame_support::{ use hex_literal::hex; use pallet_revive::{ test_utils::builder::{BareInstantiateBuilder, Contract}, - Code, + Code,TransactionLimits }; use pallet_revive_fixtures::compile_module; use pallet_uniques::{asset_ops::Item, asset_strategies::Attribute}; @@ -1706,8 +1706,10 @@ fn withdraw_and_deposit_erc20s() { let initial_amount_u256 = U256::from(1_000_000_000_000u128); let constructor_data = sol_data::Uint::<256>::abi_encode(&initial_amount_u256); let Contract { addr: erc20_address, .. } = bare_instantiate(&sender, code) - .gas_limit(Weight::from_parts(500_000_000_000, 10 * 1024 * 1024)) - .storage_deposit_limit(Balance::MAX) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: Weight::from_parts(500_000_000_000, 10 * 1024 * 1024), + deposit_limit: Balance::MAX, + }) .data(constructor_data) .build_and_unwrap_contract(); @@ -1819,8 +1821,10 @@ fn smart_contract_not_erc20_will_error() { let (code, _) = compile_module("dummy").unwrap(); let Contract { addr: non_erc20_address, .. } = bare_instantiate(&sender, code) - .gas_limit(Weight::from_parts(500_000_000_000, 10 * 1024 * 1024)) - .storage_deposit_limit(Balance::MAX) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: Weight::from_parts(500_000_000_000, 10 * 1024 * 1024), + deposit_limit: Balance::MAX, + }) .build_and_unwrap_contract(); let wnd_amount_for_fees = 1_000_000_000_000u128; @@ -1877,8 +1881,10 @@ fn smart_contract_does_not_return_bool_fails() { let constructor_data = sol_data::Uint::<256>::abi_encode(&initial_amount_u256); let Contract { addr: non_erc20_address, .. } = bare_instantiate(&sender, code) - .gas_limit(Weight::from_parts(500_000_000_000, 10 * 1024 * 1024)) - .storage_deposit_limit(Balance::MAX) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: Weight::from_parts(500_000_000_000, 10 * 1024 * 1024), + deposit_limit: Balance::MAX, + }) .data(constructor_data) .build_and_unwrap_contract(); @@ -1933,8 +1939,10 @@ fn expensive_erc20_runs_out_of_gas() { let initial_amount_u256 = U256::from(1_000_000_000_000u128); let constructor_data = sol_data::Uint::<256>::abi_encode(&initial_amount_u256); let Contract { addr: non_erc20_address, .. } = bare_instantiate(&sender, code) - .gas_limit(Weight::from_parts(500_000_000_000, 10 * 1024 * 1024)) - .storage_deposit_limit(Balance::MAX) + .transaction_limits(TransactionLimits::WeightAndDeposit { + weight_limit: Weight::from_parts(500_000_000_000, 10 * 1024 * 1024), + deposit_limit: Balance::MAX, + }) .data(constructor_data) .build_and_unwrap_contract(); From 9117c8c3f1c5343df85bc67ee26d7752a989f85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 13 Nov 2025 07:56:40 -0300 Subject: [PATCH 15/69] Fix errors in other crates (4) --- .../parachains/runtimes/assets/asset-hub-westend/tests/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 22adeb0d0a84d..300dc145273dc 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -58,7 +58,7 @@ use frame_support::{ use hex_literal::hex; use pallet_revive::{ test_utils::builder::{BareInstantiateBuilder, Contract}, - Code,TransactionLimits + Code, TransactionLimits, }; use pallet_revive_fixtures::compile_module; use pallet_uniques::{asset_ops::Item, asset_strategies::Attribute}; From cd7cbf284efd5a37791d23f880ba9f1500c32a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Fri, 14 Nov 2025 08:58:08 -0300 Subject: [PATCH 16/69] Fix calculation of max storage deposit --- substrate/frame/revive/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 49c75ea7e2c45..fae0fb87af3f3 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -2057,9 +2057,9 @@ impl Pallet { /// practice. It is used to determine the block gas limit pub fn max_storage_deposit() -> BalanceOf { // Assume that transactions in that block deploy new contracts and upload contract code up - // to the POV limits of the block + // to the maximum extrinsic length of the block. T::DepositPerItem::get() - .saturating_mul(T::BlockWeights::get().max_block.proof_size().saturated_into()) + .saturating_mul((*T::BlockLength::get().max.get(DispatchClass::Normal)).into()) } /// The maximum weight an `eth_transact` is allowed to consume. From e5a3668fc534bc837fd8e9ce5d6746415882d8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:02:11 -0300 Subject: [PATCH 17/69] Add tests --- substrate/frame/revive/src/lib.rs | 2 +- substrate/frame/revive/src/metering/mod.rs | 52 +- .../frame/revive/src/metering/storage.rs | 2 +- substrate/frame/revive/src/metering/tests.rs | 555 +++++++++++++++++- substrate/frame/revive/src/tests.rs | 16 +- substrate/frame/revive/src/tests/pvm.rs | 2 +- 6 files changed, 597 insertions(+), 32 deletions(-) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index fae0fb87af3f3..1ee195dd04966 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -99,7 +99,7 @@ pub use crate::{ block_hash::ReceiptGasInfo, Address as EthAddress, Block as EthBlock, DryRunConfig, ReceiptInfo, }, - exec::{DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin}, + exec::{CallResources, DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin}, metering::{ weight::Token as WeightToken, EthTxInfo, FrameMeter, ResourceMeter, TransactionLimits, TransactionMeter, diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 02bf11d34559d..250553cd4ecc4 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -121,6 +121,32 @@ impl Default for TransactionLimits { } impl ResourceMeter { + /// Create a new nested meter with derived resource limits. + pub fn new_nested(&self, limit: &CallResources) -> Result, DispatchError> { + match &self.transaction_limits { + TransactionLimits::EthereumGas { eth_tx_info, .. } => + math::ethereum_execution::new_nested_meter(self, limit, eth_tx_info), + TransactionLimits::WeightAndDeposit { .. } => + math::substrate_execution::new_nested_meter(self, limit), + } + } + + /// Absorb only the weight consumption from a nested frame meter. + pub fn absorb_weight_meter_only(&mut self, other: FrameMeter) { + self.weight.absorb_nested(other.weight); + } + + /// Absorb all resource consumption from a nested frame meter. + pub fn absorb_all_meters( + &mut self, + other: FrameMeter, + contract: &T::AccountId, + info: Option<&mut ContractInfo>, + ) { + self.weight.absorb_nested(other.weight); + self.deposit.absorb(other.deposit, contract, info); + } + /// Charge a weight token against this meter's remaining weight limit. /// /// Returns `Err(Error::OutOfGas)` if the weight limit would be exceeded. @@ -199,32 +225,6 @@ impl ResourceMeter { Ok(()) } - /// Absorb only the weight consumption from a nested frame meter. - pub fn absorb_weight_meter_only(&mut self, other: FrameMeter) { - self.weight.absorb_nested(other.weight); - } - - /// Absorb all resource consumption from a nested frame meter. - pub fn absorb_all_meters( - &mut self, - other: FrameMeter, - contract: &T::AccountId, - info: Option<&mut ContractInfo>, - ) { - self.weight.absorb_nested(other.weight); - self.deposit.absorb(other.deposit, contract, info); - } - - /// Create a new nested meter with derived resource limits. - pub fn new_nested(&self, limit: &CallResources) -> Result, DispatchError> { - match &self.transaction_limits { - TransactionLimits::EthereumGas { eth_tx_info, .. } => - math::ethereum_execution::new_nested_meter(self, limit, eth_tx_info), - TransactionLimits::WeightAndDeposit { .. } => - math::substrate_execution::new_nested_meter(self, limit), - } - } - /// Get remaining ethereum gas equivalent. /// /// Converts remaining resources to ethereum gas units: diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index cc5ba3363f941..334362db4f3d9 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -338,7 +338,7 @@ where /// This will not perform a charge. It just records it to reflect it in the /// total amount of storage required for a transaction. pub fn record_charge(&mut self, amount: &DepositOf) { - let total_deposit = self.total_deposit.saturating_add(&amount); + let total_deposit = self.total_deposit.saturating_add(amount); self.total_deposit = total_deposit; self.recalulculate_max_charged(); } diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs index 4ad6ca9f2936c..7cd3c84b7b1f0 100644 --- a/substrate/frame/revive/src/metering/tests.rs +++ b/substrate/frame/revive/src/metering/tests.rs @@ -18,13 +18,29 @@ use crate::{ test_utils::{builder::Contract, ALICE}, tests::{builder, ExtBuilder, Test}, - Code, Config, StorageDeposit, + CallResources, Code, Config, EthTxInfo, StorageDeposit, TransactionLimits, TransactionMeter, + WeightToken, }; use alloy_core::sol_types::SolCall; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, Deposit, FixtureType}; +use sp_runtime::{FixedU128, Weight}; use test_case::test_case; +/// A trivial token that charges the specified number of weight units. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct TestToken(u64, u64); +impl WeightToken for TestToken { + fn weight(&self) -> Weight { + Weight::from_parts(self.0, self.1) + } +} + +enum Charge { + W(u64, u64), + D(i64), +} + #[test_case(FixtureType::Solc ; "solc")] #[test_case(FixtureType::Resolc ; "resolc")] fn max_consumed_deposit_integration(fixture_type: FixtureType) { @@ -68,3 +84,540 @@ fn max_consumed_deposit_integration_refunds_subframes(fixture_type: FixtureType) assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); }); } + +#[test] +fn substrate_metering_initialization_works() { + let tests = vec![ + (5_000_000_000, 1_000_000_000, 2_000, Some((2999999500, 1499999750, 11107, 599999900))), + (6_000_000_000, 1_000_000_000, 2_000, Some((3999999500, 1999999750, 13728, 799999900))), + (6_000_000_000, 1_000_000_000, 10_000, Some((2185302235, 1999999750, 5728, 437060447))), + (2_000_000_000, 1_000_000_000, 2_000, None), + (4_000_000_000, 100_000_000, 2_000, Some((3237060047, 1899999750, 8485, 647412009))), + (5_000_000_000, 1_000_000_000, 8_000, Some((1948241688, 1499999750, 5107, 389648337))), + (10_000_000_000, 1_000_000_000, 8_000, Some((6948241688, 3999999750, 18214, 1389648337))), + (3_052_000_000, 1_000_000_000, 8_000, Some((241688, 525999750, 0, 48337))), + (3_051_000_000, 1_000_000_000, 8_000, None), + ]; + + for (eth_gas_limit, extra_ref_time, extra_proof, remaining) in tests { + ExtBuilder::default() + .with_next_fee_multiplier(FixedU128::from_rational(1, 5)) + .build() + .execute_with(|| { + let eth_tx_info = + EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); + let transaction_meter = + TransactionMeter::::new(TransactionLimits::EthereumGas { + eth_gas_limit, + maybe_weight_limit: None, + eth_tx_info, + }); + + if let Some((gas_left, ref_time_left, proof_size_left, deposit_left)) = remaining { + let transaction_meter = transaction_meter.unwrap(); + assert_eq!(gas_left, transaction_meter.eth_gas_left().unwrap()); + assert_eq!( + Weight::from_parts(ref_time_left, proof_size_left), + transaction_meter.weight_left().unwrap() + ); + assert_eq!(deposit_left, transaction_meter.deposit_left().unwrap()); + } else { + assert!(transaction_meter.is_err()); + } + }); + } + + let tests = vec![ + ((1_000_000_000, 2_000), (1_000_000_000, 2_000)), + ((2_000_000_000, 2_000), (1_499_999_750, 2_000)), + ((2_000_000_000, 20_000), (1_499_999_750, 11_107)), + ((1_000_000_000, 20_000), (1_000_000_000, 11_107)), + ]; + + for ((ref_time_limit, proof_size_limit), (ref_time_left, proof_size_left)) in tests { + ExtBuilder::default() + .with_next_fee_multiplier(FixedU128::from_rational(1, 5)) + .build() + .execute_with(|| { + let eth_tx_info = + EthTxInfo::::new(100, Weight::from_parts(1_000_000_000, 2_000)); + let transaction_meter = + TransactionMeter::::new(TransactionLimits::EthereumGas { + eth_gas_limit: 5_000_000_000, + maybe_weight_limit: Some(Weight::from_parts( + ref_time_limit, + proof_size_limit, + )), + eth_tx_info, + }) + .unwrap(); + + assert_eq!( + Weight::from_parts(ref_time_left, proof_size_left), + transaction_meter.weight_left().unwrap() + ); + }); + } +} + +#[test] +fn substrate_metering_charges_works() { + use Charge::{D, W}; + + let tests = vec![ + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![(W(1000, 100), Some((2999997500, 1499998750, 11007, 599999500, 2000002500u64)))], + ), + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![(W(1000, 300), Some((2999997500, 1499998750, 10807, 599999500, 2000002500)))], + ), + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![(W(1300000000, 10000), Some((399999500, 199999750, 1107, 79999900, 4600000500)))], + ), + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![(W(1400000000, 10000), Some((199999500, 99999750, 1107, 39999900, 4800000500)))], + ), + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![(W(1400000000, 11000), Some((40893055, 99999750, 107, 8178611, 4959106945)))], + ), + ((5_000_000_000, 1_000_000_000, 2_000), vec![(W(1400000000, 12000), None)]), + ((5_000_000_000, 1_000_000_000, 2_000), vec![(W(1500000000, 11000), None)]), + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![(D(1000), Some((2999994500, 1499997250, 11107, 599998900, 2000005500)))], + ), + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![(D(500000000), Some((499999500, 249999750, 4553, 99999900, 4500000500)))], + ), + ((5_000_000_000, 1_000_000_000, 2_000), vec![(D(600000000), None)]), + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![ + (D(-100000), Some((3000499500, 1500249750, 11108, 600099900, 1999500500))), + (D(-1000000000), Some((8000499500, 4000249750, 24215, 1600099900, 0))), + ], + ), + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![ + (D(-200000), Some((3000999500, 1500499750, 11109, 600199900, 1999000500))), + (D(50000), Some((3000749500, 1500374750, 11109, 600149900, 1999250500))), + (D(100000), Some((3000249500, 1500124750, 11107, 600049900, 1999750500))), + ], + ), + ( + (5_000_000_000, 1_000_000_000, 2_000), + vec![ + (W(1000, 300), Some((2999997500, 1499998750, 10807, 599999500, 2000002500))), + (D(1000), Some((2999992500, 1499996250, 10807, 599998500, 2000007500))), + (W(100000, 300), Some((2999792500, 1499896250, 10507, 599958500, 2000207500))), + (D(-10000), Some((2999842500, 1499921250, 10507, 599968500, 2000157500))), + (W(500000, 900), Some((2998842500, 1499421250, 9607, 599768500, 2001157500))), + (W(0, 10000), None), + ], + ), + ]; + + for (input, charges) in tests { + let (eth_gas_limit, extra_ref_time, extra_proof) = input; + ExtBuilder::default() + .with_next_fee_multiplier(FixedU128::from_rational(1, 5)) + .build() + .execute_with(|| { + let eth_tx_info = + EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); + let mut transaction_meter = + TransactionMeter::::new(TransactionLimits::EthereumGas { + eth_gas_limit, + maybe_weight_limit: None, + eth_tx_info, + }) + .unwrap(); + + for (charge, remaining) in charges { + let is_ok = match charge { + W(ref_time_charge, proof_size_charge) => transaction_meter + .charge_weight_token(TestToken(ref_time_charge, proof_size_charge)) + .is_ok(), + D(deposit_charge) => transaction_meter + .charge_deposit( + &(if deposit_charge >= 0 { + StorageDeposit::Charge(deposit_charge as u64) + } else { + StorageDeposit::Refund(-deposit_charge as u64) + }), + ) + .is_ok(), + }; + + if let Some(( + gas_left, + ref_time_left, + proof_size_left, + deposit_left, + gas_consumed, + )) = remaining + { + assert!(is_ok); + assert_eq!(gas_left, transaction_meter.eth_gas_left().unwrap()); + assert_eq!( + Weight::from_parts(ref_time_left, proof_size_left), + transaction_meter.weight_left().unwrap() + ); + assert_eq!(deposit_left, transaction_meter.deposit_left().unwrap()); + assert_eq!(gas_consumed, transaction_meter.total_consumed_gas()); + } else { + assert!(!is_ok); + } + } + }); + } +} + +#[test] +fn substrate_nesting_works() { + use CallResources::{Ethereum, NoLimits, WeightDeposit}; + + let tests = vec![ + ( + ((5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000i64), NoLimits), + Some((2999992500, 1499996250, 10107, 599998500, 2000007500)), + ), + ( + ((5_000_000_000, 1_000_000_000, 2_000, 1000000000, 10000, 50000), NoLimits), + Some((422112782, 499874750, 1106, 84422556, 4577887218)), + ), + ( + ((5_000_000_000, 1_000_000_000, 3000, 2000, 100000, -7000000000), NoLimits), + Some((708617665, 18999997750, 1857, 141723533, 4291382335)), + ), + ( + ((5_000_000_000, 1_000_000_000, 3000, 2000, 100000, -70000000000), NoLimits), + Some((315708617665, 176499997750, 827611, 63141723533, 0)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000), + WeightDeposit { + weight: Weight::from_parts(10000000000, 100000), + deposit_limit: 1000000000, + }, + ), + Some((2999992500, 1499996250, 10107, 599998500, 2000007500)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000), + WeightDeposit { + weight: Weight::from_parts(1000000000, 100000), + deposit_limit: 1000000000, + }, + ), + Some((2999992500, 1000000000, 10107, 599998500, 2000007500)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000), + WeightDeposit { + weight: Weight::from_parts(10000000000, 10000), + deposit_limit: 1000000000, + }, + ), + Some((2999992500, 1499996250, 10000, 599998500, 2000007500)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000), + WeightDeposit { + weight: Weight::from_parts(10000000000, 100000), + deposit_limit: 100000000, + }, + ), + Some((2999992500, 1499996250, 10107, 100000000, 2000007500)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000), + WeightDeposit { weight: Weight::from_parts(40000, 200), deposit_limit: 300000 }, + ), + Some((1580000, 40000, 200, 300000, 2000007500)), + ), + ( + ( + (4_000_000_000, 100_000_000, 3_000, 1000, 1000, 100), + WeightDeposit { weight: Weight::from_parts(40000, 200), deposit_limit: 300000 }, + ), + Some((77793945, 40000, 200, 300000, 1525879906)), + ), + ( + ( + (4_000_000_000, 100_000_000, 3_000, 1800000000, 1000, 100), + WeightDeposit { weight: Weight::from_parts(40000, 200), deposit_limit: 300000 }, + ), + Some((1580000, 40000, 200, 300000, 3800001000)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000), + Ethereum { gas: 2999992501, add_stipend: false }, + ), + Some((2999992500, 1499996250, 10107, 599998500, 2000007500)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000), + Ethereum { gas: 2999992499, add_stipend: false }, + ), + Some((2999992499, 1499996249, 10107, 599998499, 2000007500)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 2_000, 1000000000, 10000, 50000), + Ethereum { gas: 10000, add_stipend: false }, + ), + Some((10000, 288823359, 0, 2000, 4577887218)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 3000, 2000, 100000, -7000000000), + Ethereum { gas: 708617664, add_stipend: false }, + ), + Some((708617664, 18999997749, 1857, 141723532, 4291382335)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 3000, 2000, 100000, -7000000000), + Ethereum { gas: 708617666, add_stipend: false }, + ), + Some((708617665, 18999997750, 1857, 141723533, 4291382335)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 3000, 2000, 100000, -7000000000), + Ethereum { gas: 3157000000, add_stipend: false }, + ), + Some((708617665, 18999997750, 1857, 141723533, 4291382335)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 3000, 2000, 10106, 91452), + Ethereum { gas: 5, add_stipend: false }, + ), + Some((4, 1499769120, 0, 0, 4999999996)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 3000, 2000, 10106, 91452), + Ethereum { gas: 3, add_stipend: false }, + ), + Some((3, 1499769119, 0, 0, 4999999996)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 3000, 2000, 1010, 91452), + Ethereum { gas: 3, add_stipend: false }, + ), + Some((3, 1, 1232, 0, 2000461760)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 3000, 2000, 2242, 91452), + Ethereum { gas: 6, add_stipend: false }, + ), + Some((6, 3, 0, 1, 2000461760)), + ), + ( + ( + (5_000_000_000, 1_000_000_000, 3000, 2000, 2243, 91452), + Ethereum { gas: 6, add_stipend: false }, + ), + Some((6, 20891, 0, 1, 2000503536)), + ), + ]; + + for (input, remaining) in tests { + let ( + ( + eth_gas_limit, + extra_ref_time, + extra_proof, + ref_time_charge, + proof_size_charge, + deposit_charge, + ), + call_resource, + ) = input; + ExtBuilder::default() + .with_next_fee_multiplier(FixedU128::from_rational(1, 5)) + .build() + .execute_with(|| { + #[cfg(test)] + let eth_tx_info = EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); + let mut transaction_meter = + TransactionMeter::::new(TransactionLimits::EthereumGas { + eth_gas_limit, + maybe_weight_limit: None, + eth_tx_info, + }) + .unwrap(); + + transaction_meter + .charge_deposit( + &(if deposit_charge >= 0 { + StorageDeposit::Charge(deposit_charge as u64) + } else { + StorageDeposit::Refund(-deposit_charge as u64) + }), + ) + .unwrap(); + + transaction_meter + .charge_weight_token(TestToken(ref_time_charge, proof_size_charge)) + .unwrap(); + + let nested = transaction_meter.new_nested(&call_resource); + + if let Some(( + gas_left, + ref_time_left, + proof_size_left, + deposit_left, + gas_consumed, + )) = remaining + { + let nested = nested.unwrap(); + assert_eq!(gas_left, nested.eth_gas_left().unwrap()); + assert_eq!( + Weight::from_parts(ref_time_left, proof_size_left), + nested.weight_left().unwrap() + ); + assert_eq!(deposit_left, nested.deposit_left().unwrap()); + assert_eq!(gas_consumed, nested.total_consumed_gas()); + } else { + assert!(nested.is_err()); + } + }); + } +} + +#[test] +fn substrate_nesting_charges_works() { + use Charge::{D, W}; + + let tests = vec![ + ( + (5_000_000_000, 1_000_000_000, 2_000, 1000, 100, 1000i64, 1000), + vec![ + (W(100, 100), Some((800, 400, 3042, 160, 2000007700))), + (D(100), Some((300, 150, 3042, 60, 2000008200))), + ], + ), + ( + (5_000_000_000, 419_615_482, 2_000, 1000, 100, 100, 1000), + vec![ + (W(100, 100), Some((566, 400, 0, 113, 839234398))), + (W(100, 0), Some((566, 300, 0, 113, 839234398))), + (D(100), Some((66, 50, 0, 13, 839234898))), + (W(50, 0), Some((0, 0, 0, 0, 839234964))), + (D(-300), Some((1500, 750, 0, 300, 839233464))), + (W(50, 0), Some((1400, 700, 0, 280, 839233564))), + (W(0, 1), None), + ], + ), + ( + (5_000_000_000, 100_000_000, 2_000, 1000, 100, 100, 10000000), + vec![ + (D(100), Some((9999500, 305541962, 26, 1999900, 801087925))), + (W(100, 0), Some((9999500, 305541862, 26, 1999900, 801087925))), + (W(0, 20), Some((2370105, 305541862, 6, 474021, 808717320))), + ], + ), + ]; + + for (input, charges) in tests { + let ( + eth_gas_limit, + extra_ref_time, + extra_proof, + ref_time_charge, + proof_size_charge, + deposit_charge, + gas_limit, + ) = input; + ExtBuilder::default() + .with_next_fee_multiplier(FixedU128::from_rational(1, 5)) + .build() + .execute_with(|| { + let eth_tx_info = + EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); + let mut transaction_meter = + TransactionMeter::::new(TransactionLimits::EthereumGas { + eth_gas_limit, + maybe_weight_limit: None, + eth_tx_info, + }) + .unwrap(); + + transaction_meter + .charge_deposit( + &(if deposit_charge >= 0 { + StorageDeposit::Charge(deposit_charge as u64) + } else { + StorageDeposit::Refund((-deposit_charge) as u64) + }), + ) + .unwrap(); + + transaction_meter + .charge_weight_token(TestToken(ref_time_charge, proof_size_charge)) + .unwrap(); + + let mut nested = transaction_meter + .new_nested(&CallResources::Ethereum { gas: gas_limit, add_stipend: false }) + .unwrap(); + + for (charge, remaining) in charges { + let is_ok = match charge { + W(ref_time_charge, proof_size_charge) => nested + .charge_weight_token(TestToken(ref_time_charge, proof_size_charge)) + .is_ok(), + D(deposit_charge) => nested + .charge_deposit( + &(if deposit_charge >= 0 { + StorageDeposit::Charge(deposit_charge as u64) + } else { + StorageDeposit::Refund(-deposit_charge as u64) + }), + ) + .is_ok(), + }; + + if let Some(( + gas_left, + ref_time_left, + proof_size_left, + deposit_left, + gas_consumed, + )) = remaining + { + assert!(is_ok); + assert_eq!(gas_left, nested.eth_gas_left().unwrap()); + assert_eq!( + Weight::from_parts(ref_time_left, proof_size_left), + nested.weight_left().unwrap() + ); + assert_eq!(deposit_left, nested.deposit_left().unwrap()); + assert_eq!(gas_consumed, nested.total_consumed_gas()); + } else { + assert!(!is_ok); + } + } + }); + } +} diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 3fd129a171262..0af794f6ec7e3 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -51,7 +51,7 @@ use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ generic::Header, traits::{BlakeTwo256, Convert, IdentityLookup, One}, - AccountId32, BuildStorage, MultiAddress, MultiSignature, Perbill, Storage, + AccountId32, BuildStorage, FixedU128, MultiAddress, MultiSignature, Perbill, Storage, }; pub type Address = MultiAddress; @@ -324,7 +324,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 = BlockRatioFee<1, 1, Self>; + type WeightToFee = BlockRatioFee<2, 1, Self>; type LengthToFee = FixedFee<100, ::Balance>; type FeeMultiplierUpdate = ConstFeeMultiplier; } @@ -441,6 +441,7 @@ pub struct ExtBuilder { code_hashes: Vec, genesis_config: Option>, genesis_state_overrides: Option, + next_fee_multiplier: Option, } impl Default for ExtBuilder { @@ -451,6 +452,7 @@ impl Default for ExtBuilder { code_hashes: vec![], genesis_config: Some(crate::GenesisConfig::::default()), genesis_state_overrides: None, + next_fee_multiplier: None, } } } @@ -469,6 +471,10 @@ impl ExtBuilder { self.code_hashes = code_hashes; self } + pub fn with_next_fee_multiplier(mut self, next_fee_multiplier: FixedU128) -> Self { + self.next_fee_multiplier = Some(next_fee_multiplier); + self + } pub fn set_associated_consts(&self) { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); } @@ -494,6 +500,12 @@ impl ExtBuilder { .assimilate_storage(&mut t) .unwrap(); + if let Some(multiplier) = self.next_fee_multiplier { + pallet_transaction_payment::GenesisConfig:: { multiplier, ..Default::default() } + .assimilate_storage(&mut t) + .unwrap(); + } + if let Some(genesis_config) = self.genesis_config { genesis_config.assimilate_storage(&mut t).unwrap(); } diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index b2ea928d319dc..465981ea7d565 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -74,7 +74,7 @@ fn eth_call_transfer_with_dust_works() { let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - ::FeeInfo::deposit_txfee(::Currency::issue(5_000_000_000)); + ::FeeInfo::deposit_txfee(::Currency::issue(10_000_000_000)); let balance = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::(100, 10)); From a20f8ed0a8b6c2302c1d5a97cfcd5c0ccde8b2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:29:19 -0300 Subject: [PATCH 18/69] Fix weight tracing (issue 8362) --- substrate/frame/revive/src/exec.rs | 28 +++++++++++++----------- substrate/frame/revive/src/lib.rs | 2 ++ substrate/frame/revive/src/primitives.rs | 4 ++++ substrate/frame/revive/src/tests/pvm.rs | 15 +++++-------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 89acf3a990079..10768d00f1fc1 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1248,10 +1248,10 @@ where } self.transient_storage.start_transaction(); + let is_first_frame = self.frames.len() == 0; let do_transaction = || -> ExecResult { let caller = self.caller(); - let is_first_frame = self.frames.len() == 0; let bump_nonce = self.exec_config.bump_nonce; let frame = top_frame_mut!(self); let account_id = &frame.account_id.clone(); @@ -1435,12 +1435,13 @@ where // `with_transactional` executed successfully, and we have the expected output. Ok((success, output)) => { if_tracing(|tracer| { - let gas_consumed = top_frame!(self) - .frame_meter - .eth_gas_consumed() - .as_positive() - .unwrap_or_default() - .into(); + let frame_meter = &top_frame!(self).frame_meter; + let gas_consumed = if is_first_frame { + frame_meter.total_consumed_gas().into() + } else { + frame_meter.eth_gas_consumed().as_positive().unwrap_or_default().into() + }; + match &output { Ok(output) => tracer.exit_child_span(&output, gas_consumed), Err(e) => tracer.exit_child_span_with_error(e.error.into(), gas_consumed), @@ -1453,12 +1454,13 @@ where // has changed. Err(error) => { if_tracing(|tracer| { - let gas_consumed = top_frame!(self) - .frame_meter - .eth_gas_consumed() - .as_positive() - .unwrap_or_default() - .into(); + let frame_meter = &top_frame!(self).frame_meter; + let gas_consumed = if is_first_frame { + frame_meter.total_consumed_gas().into() + } else { + frame_meter.eth_gas_consumed().as_positive().unwrap_or_default().into() + }; + tracer.exit_child_span_with_error(error.into(), gas_consumed); }); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 1ee195dd04966..2a62a56596ab8 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1606,6 +1606,7 @@ impl Pallet { weight_consumed: transaction_meter.weight_consumed(), weight_required: transaction_meter.weight_required(), storage_deposit: transaction_meter.deposit_consumed(), + gas_consumed: transaction_meter.total_consumed_gas(), max_storage_deposit: transaction_meter.deposit_required(), } } @@ -1692,6 +1693,7 @@ impl Pallet { weight_consumed: transaction_meter.weight_consumed(), weight_required: transaction_meter.weight_required(), storage_deposit: transaction_meter.deposit_consumed(), + gas_consumed: transaction_meter.total_consumed_gas(), max_storage_deposit: transaction_meter.deposit_required(), } } diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 61ba00edc740f..0fbf0e2fd04d5 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -68,6 +68,8 @@ pub struct ContractResult { /// This can be higher than the final storage_deposit due to refunds /// This is always a StorageDeposit::Charge(..) pub max_storage_deposit: StorageDeposit, + /// The amount of Ethereum gas that has been consumed during execution. + pub gas_consumed: Balance, /// The execution result of the vm binary code. pub result: Result, } @@ -79,6 +81,7 @@ impl Default for ContractResult { weight_required: Default::default(), storage_deposit: Default::default(), max_storage_deposit: Default::default(), + gas_consumed: Default::default(), result: Ok(Default::default()), } } @@ -263,6 +266,7 @@ impl ContractResult { weight_required: self.weight_required, storage_deposit: self.storage_deposit, max_storage_deposit: self.max_storage_deposit, + gas_consumed: self.gas_consumed, result: self.result.map(map_fn), } } diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 465981ea7d565..e69dba54e0516 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -4076,17 +4076,14 @@ fn call_tracing_works() { ]; // Verify that the first trace report the same weight reported by bare_call - // TODO: fix tracing ( https://github.com/paritytech/polkadot-sdk/issues/8362 ) - /* - let mut tracer = CallTracer::new(false, |w| w); + let mut tracer = CallTracer::new(CallTracerConfig {with_logs: true, only_top_call: false}); let gas_used = trace(&mut tracer, || { - builder::bare_call(addr).data((3u32, addr_callee).encode()).build().weight_consumed + let a = builder::bare_call(addr).data((3u32, addr_callee).encode()).build(); + a.gas_consumed }); - let trace = tracer.collect_trace().unwrap(); - assert_eq!(&trace.gas_used, &gas_used); - */ - - + let gas_trace = tracer.collect_trace().unwrap(); + assert_eq!(&gas_trace.gas_used, &gas_used.into()); + for config in tracer_configs { let logs = if config.with_logs { vec![ From 3fff1a5407287b711b1fb3635d239b453f086821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 19 Nov 2025 04:42:41 -0300 Subject: [PATCH 19/69] Fix max deposit calculation --- substrate/frame/revive/src/metering/mod.rs | 1 + substrate/frame/revive/src/metering/storage.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 250553cd4ecc4..e8e5d327a16d0 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -134,6 +134,7 @@ impl ResourceMeter { /// Absorb only the weight consumption from a nested frame meter. pub fn absorb_weight_meter_only(&mut self, other: FrameMeter) { self.weight.absorb_nested(other.weight); + self.deposit.absorb_only_max_charged(other.deposit); } /// Absorb all resource consumption from a nested frame meter. diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index 334362db4f3d9..47a6935938184 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -320,6 +320,21 @@ where } } + /// Absorb only the maximum charge of the child meter. + /// + /// This should be called whenever a sub call ends and reverts. + /// + /// # Parameters + /// + /// - `absorbed`: The child storage meter + pub fn absorb_only_max_charged(&mut self, absorbed: RawMeter) { + // No need to recalculate max_charged for `absorbed` here. With `info` we can now calculate + // the correct `own_contribution` of `absorbed` but that can only be less + self.max_charged = self + .max_charged + .max(self.consumed().saturating_add(&absorbed.max_charged()).charge_or_zero()); + } + pub fn terminate_absorb( &mut self, contract_account: T::AccountId, From 1c928c91714a2a090806ba7b4bd29204ee40c6e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 19 Nov 2025 04:51:22 -0300 Subject: [PATCH 20/69] Add test for max deposits --- substrate/frame/revive/src/metering/storage.rs | 11 +++++++++++ substrate/frame/revive/src/tests/pvm.rs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index 47a6935938184..bcaa452dcd0cc 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -1171,4 +1171,15 @@ mod tests { assert_eq!(meter.consumed(), Deposit::Refund(3)); assert_eq!(meter.max_charged(), Deposit::Charge(47)); } + + #[test] + fn max_deposits_work_for_reverts() { + clear_ext(); + let mut meter = TestMeter::new(None); + let mut nested1 = meter.nested(None); + nested1.record_charge(&Deposit::Charge(10)); + + meter.absorb_only_max_charged(nested1); + assert_eq!(meter.max_charged(), Deposit::Charge(10)); + } } diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index e69dba54e0516..4c0b0b6b35c25 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -4083,7 +4083,7 @@ fn call_tracing_works() { }); let gas_trace = tracer.collect_trace().unwrap(); assert_eq!(&gas_trace.gas_used, &gas_used.into()); - + for config in tracer_configs { let logs = if config.with_logs { vec![ From 14947c1a8f3367667c3d03786cdc351a444c946b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:29:26 -0300 Subject: [PATCH 21/69] Fix issue with dry running length encoding --- substrate/frame/revive/src/evm/call.rs | 20 +++++++++++++++++--- substrate/frame/revive/src/lib.rs | 8 ++++++-- substrate/frame/revive/src/limits.rs | 5 +++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs index ea4bc8fc6d970..a23505052432f 100644 --- a/substrate/frame/revive/src/evm/call.rs +++ b/substrate/frame/revive/src/evm/call.rs @@ -19,8 +19,10 @@ use crate::{ evm::{fees::InfoT, runtime::SetWeightLimit}, - extract_code_and_data, BalanceOf, CallOf, Config, GenericTransaction, Pallet, Weight, Zero, - LOG_TARGET, RUNTIME_PALLETS_ADDR, + extract_code_and_data, + limits::ENCODING_LENGTH_SAFETY_MARGIN, + BalanceOf, CallOf, Config, GenericTransaction, Pallet, Weight, Zero, LOG_TARGET, + RUNTIME_PALLETS_ADDR, }; use alloc::{boxed::Box, vec::Vec}; use codec::DecodeLimit; @@ -47,6 +49,10 @@ pub struct CallInfo { } /// Decode `tx` into a dispatchable call. +/// +/// signed_transaction is Some(..) for extrinsic execution (when called from +/// `try_into_checked_extrinsic`) and it is `None` for dry running (when called from +/// `dry_run_eth_transact`) pub fn create_call( tx: GenericTransaction, signed_transaction: Option<(u32, Vec)>, @@ -56,6 +62,7 @@ where T: Config, CallOf: SetWeightLimit, { + let is_dry_run = signed_transaction.is_none(); let base_fee = >::evm_base_fee(); let Some(gas) = tx.gas else { @@ -198,8 +205,15 @@ where } }; + // Add some buffer for dry running + let encoding_len_with_margin = if is_dry_run { + encoded_len.saturating_add(ENCODING_LENGTH_SAFETY_MARGIN) + } else { + encoded_len + }; + // the overall fee of the extrinsic including the gas limit - let tx_fee = ::FeeInfo::tx_fee(encoded_len, &call); + let tx_fee = ::FeeInfo::tx_fee(encoding_len_with_margin, &call); // the leftover we make available to the deposit collection system let storage_deposit = eth_fee.checked_sub(tx_fee.into()).ok_or_else(|| { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 2a62a56596ab8..2685b4ea0601f 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -53,6 +53,7 @@ use crate::{ TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack}, + limits::ENCODING_LENGTH_SAFETY_MARGIN, storage::{AccountType, DeletionQueueManager}, tracing::if_tracing, vm::{pvm::extract_code_and_data, CodeInfo, RuntimeCosts}, @@ -1736,7 +1737,7 @@ impl Pallet { tx.gas_price = Some(effective_gas_price); if tx.max_priority_fee_per_gas.is_none() { - tx.max_priority_fee_per_gas = Some(effective_gas_price); + tx.max_priority_fee_per_gas = Some(0.into()); } if tx.max_fee_per_gas.is_none() { tx.max_fee_per_gas = Some(effective_gas_price); @@ -1922,7 +1923,10 @@ impl Pallet { } // not enough gas supplied to pay for both the tx fees and the storage deposit - let transaction_fee = T::FeeInfo::tx_fee(call_info.encoded_len, &call_info.call); + let transaction_fee = T::FeeInfo::tx_fee( + call_info.encoded_len.saturating_add(ENCODING_LENGTH_SAFETY_MARGIN), + &call_info.call, + ); let available_fee = T::FeeInfo::remaining_txfee(); if transaction_fee > available_fee { Err(EthTransactError::Message(format!( diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 75c8358f8d8e7..25848db290aca 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -99,6 +99,11 @@ pub const EVM_STACK_LIMIT: u32 = 1024; /// The call stipend gas amount defined in the EVM pub const SOLIDITY_CALL_STIPEND: u32 = 2300; +/// The RLP encoding length of an Ethereum transaction can change when input values are changed +/// slightly. We want to ensure that the encoding length during gas estimation is at least the +/// encoding length of the actual transaction +pub const ENCODING_LENGTH_SAFETY_MARGIN: u32 = 10; + /// Limits that are only enforced on code upload. /// /// # Note From 74135f898d7238435aebf30b7538aa8f0949a342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:45:26 -0300 Subject: [PATCH 22/69] Set max block gas to u64::MAX --- substrate/frame/revive/src/lib.rs | 33 ++----------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 2685b4ea0601f..980a8b6a09b15 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1735,10 +1735,7 @@ impl Pallet { // create_call expects tx.gas_price to be the effective gas price tx.gas_price = Some(effective_gas_price); - - if tx.max_priority_fee_per_gas.is_none() { - tx.max_priority_fee_per_gas = Some(0.into()); - } + tx.max_priority_fee_per_gas = Some(0.into()); if tx.max_fee_per_gas.is_none() { tx.max_fee_per_gas = Some(effective_gas_price); } @@ -2044,28 +2041,7 @@ impl Pallet { /// Get the block gas limit. 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 max_storage_deposit = Self::max_storage_deposit(); - let max_length_fee = - T::FeeInfo::length_to_fee(*T::BlockLength::get().max.get(DispatchClass::Normal)); - - let variable_fee = T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(max_storage_deposit.saturating_add(max_length_fee)); - - Self::evm_gas_from_weight(max_block_weight).saturating_add(variable_fee.into()) - } - - /// This is not an enforced limit but an estimate of the maximum storage deposit occurring in - /// practice. It is used to determine the block gas limit - pub fn max_storage_deposit() -> BalanceOf { - // Assume that transactions in that block deploy new contracts and upload contract code up - // to the maximum extrinsic length of the block. - T::DepositPerItem::get() - .saturating_mul((*T::BlockLength::get().max.get(DispatchClass::Normal)).into()) + u64::MAX.into() } /// The maximum weight an `eth_transact` is allowed to consume. @@ -2301,11 +2277,6 @@ impl Pallet { }) } - /// Convert a weight to a gas value. - fn evm_gas_from_weight(weight: Weight) -> U256 { - T::FeeInfo::weight_to_fee(&weight).into() - } - /// Transfer a deposit from some account to another. /// /// `from` is usually the transaction origin and `to` a contract or From 631f6d50b85cfa9b0d64a5fd0a2ed746b4ad6856 Mon Sep 17 00:00:00 2001 From: Omar Abdulla Date: Thu, 20 Nov 2025 14:38:58 +0300 Subject: [PATCH 23/69] Lower the concurrency of the DT framework This commit lowers the number of tests that the DT framework runs concurrently. We have observed that with this PR if we operate with the regular number of concurrent tests (1000) then we start to observe the nodes not including transactions in blocks. Lowering the concurrency of tests means that they would take longer to execute but it was observed that we would no longer get the timeout errors if the concurrency is lowered. After this commit, we should no longer see the "failed to observe tx within timeout errors" or they should be greatly reduced. --- .github/workflows/tests-evm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 55ae82b229603..9731e7f80b0ec 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -71,7 +71,7 @@ jobs: --platform ${{ matrix.platform }} \ --concurrency.number-of-nodes 10 \ --concurrency.number-of-threads 10 \ - --concurrency.number-of-concurrent-tasks 1000 \ + --concurrency.number-of-concurrent-tasks 10 \ --working-directory ./workdir \ --revive-dev-node.consensus manual-seal-200 \ --revive-dev-node.path ./target/release/revive-dev-node \ From c7eefbdfb46a3d1a5c9b43438ec22936188e0879 Mon Sep 17 00:00:00 2001 From: Omar Abdulla Date: Thu, 20 Nov 2025 15:32:42 +0300 Subject: [PATCH 24/69] Update the concurrency value of the DT framework. This commit increases the concurrency value of the DT framework from 10 to 50 to make the tests run slightly faster --- .github/workflows/tests-evm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 9731e7f80b0ec..9056cf81d5625 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -23,7 +23,7 @@ jobs: needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} if: ${{ needs.preflight.outputs.changes_rust }} - timeout-minutes: 60 + timeout-minutes: 90 container: image: ${{ needs.preflight.outputs.IMAGE }} permissions: From 2f5077e6832cc1f45deb27126ffffb29fc9bc99e Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 20 Nov 2025 21:48:02 +0100 Subject: [PATCH 25/69] fix BENCH_INIT_CODE --- substrate/frame/revive/src/vm/evm/instructions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index 54d86f4d78d54..dd88d7d947229 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -44,7 +44,7 @@ mod tx_info; pub mod utility; #[cfg(feature = "runtime-benchmarks")] -pub const BENCH_INIT_CODE: u8 = 0xff; // Arbitrary unused opcode for benchmarking +pub const BENCH_INIT_CODE: u8 = 0xEF; // Arbitrary unused opcode for benchmarking pub fn exec_instruction( interpreter: &mut Interpreter, From 2f6624a7baf3981d43b56d9c638a885fd6d0b036 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Thu, 20 Nov 2025 22:23:59 +0100 Subject: [PATCH 26/69] master merge fix The crate benchmark was using the invalid opcode, instead of using a new one --- substrate/frame/revive/src/benchmarking.rs | 2 +- substrate/frame/revive/src/vm/evm/instructions/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index d301334e4b037..077146fe4bae9 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -2102,7 +2102,7 @@ mod benchmarks { i: Linear<{ 10 * 1024 }, { 48 * 1024 }>, ) -> Result<(), BenchmarkError> { use crate::vm::evm::instructions::BENCH_INIT_CODE; - let mut setup = CallSetup::::new(VmBinaryModule::dummy()); + let mut setup = CallSetup::::new(VmBinaryModule::evm_sized(0)); setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone())); setup.set_balance(caller_funding::()); diff --git a/substrate/frame/revive/src/vm/evm/instructions/mod.rs b/substrate/frame/revive/src/vm/evm/instructions/mod.rs index dd88d7d947229..a507cd1a7cb19 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/mod.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/mod.rs @@ -44,7 +44,7 @@ mod tx_info; pub mod utility; #[cfg(feature = "runtime-benchmarks")] -pub const BENCH_INIT_CODE: u8 = 0xEF; // Arbitrary unused opcode for benchmarking +pub const BENCH_INIT_CODE: u8 = 0x1F; // Arbitrary unused opcode for benchmarking pub fn exec_instruction( interpreter: &mut Interpreter, From 37e46f9c0f53357394490f503cebbbe4013f3573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Fri, 21 Nov 2025 03:42:39 -0300 Subject: [PATCH 27/69] Add tracing logs --- substrate/frame/revive/src/lib.rs | 25 +++++ substrate/frame/revive/src/metering/mod.rs | 122 ++++++++++++++++++++- 2 files changed, 144 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 980a8b6a09b15..309e8a999b3e0 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1602,6 +1602,18 @@ impl Pallet { }; let result = Self::run_guarded(try_call); + log::trace!(target: LOG_TARGET, "Bare call ends: weight_consumed={:?}\ + weight_required={:?} \ + storage_deposit={:?} \ + gas_consumed={:?} \ + max_storage_deposit={:?}", + transaction_meter.weight_consumed(), + transaction_meter.weight_required(), + transaction_meter.deposit_consumed(), + transaction_meter.total_consumed_gas(), + transaction_meter.deposit_required() + ); + ContractResult { result: result.map_err(|r| r.error), weight_consumed: transaction_meter.weight_consumed(), @@ -1687,6 +1699,19 @@ impl Pallet { result }; let output = Self::run_guarded(try_instantiate); + + log::trace!(target: LOG_TARGET, "Bare instantiate ends: weight_consumed={:?}\ + weight_required={:?} \ + storage_deposit={:?} \ + gas_consumed={:?} \ + max_storage_deposit={:?}", + transaction_meter.weight_consumed(), + transaction_meter.weight_required(), + transaction_meter.deposit_consumed(), + transaction_meter.total_consumed_gas(), + transaction_meter.deposit_required() + ); + ContractResult { result: output .map(|(addr, result)| InstantiateReturnValue { result, addr }) diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index e8e5d327a16d0..022f6f55c2be8 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -24,7 +24,7 @@ mod tests; use crate::{ evm::fees::InfoT, exec::CallResources, storage::ContractInfo, vm::evm::Halt, BalanceOf, Config, - Error, ExecConfig, ExecOrigin as Origin, SignedGas, StorageDeposit, + Error, ExecConfig, ExecOrigin as Origin, SignedGas, StorageDeposit, LOG_TARGET, }; use frame_support::{DebugNoBound, DefaultNoBound}; use num_traits::Zero; @@ -123,18 +123,80 @@ impl Default for TransactionLimits { impl ResourceMeter { /// Create a new nested meter with derived resource limits. pub fn new_nested(&self, limit: &CallResources) -> Result, DispatchError> { - match &self.transaction_limits { + log::trace!( + target: LOG_TARGET, + "Creating nested meter from parent: \ + weight_left={:?}, \ + deposit_left={:?}, \ + weight_consumed={:?}, \ + deposit_consumed={:?}", + self.weight_left(), + self.deposit_left(), + self.weight_consumed(), + self.deposit_consumed(), + ); + + let new_meter = match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => math::ethereum_execution::new_nested_meter(self, limit, eth_tx_info), TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::new_nested_meter(self, limit), - } + }; + + log::trace!( + target: LOG_TARGET, + "Creating nested meter done: \ + weight_left={:?}, \ + deposit_left={:?}, \ + weight_consumed={:?}, \ + deposit_consumed={:?}", + new_meter.as_ref().map(|s| s.weight_left()), + new_meter.as_ref().map(|s| s.deposit_left()), + new_meter.as_ref().map(|s| s.weight_consumed()), + new_meter.as_ref().map(|s| s.deposit_consumed()), + ); + + new_meter } /// Absorb only the weight consumption from a nested frame meter. pub fn absorb_weight_meter_only(&mut self, other: FrameMeter) { + log::trace!( + target: LOG_TARGET, + "Absorb weight meter only: \ + parent_weight_left={:?}, \ + parent_deposit_left={:?}, \ + parent_weight_consumed={:?}, \ + parent_deposit_consumed={:?}, \ + child_weight_left={:?}, \ + child_deposit_left={:?}, \ + child_weight_consumed={:?}, \ + child_deposit_consumed={:?}", + self.weight_left(), + self.deposit_left(), + self.weight_consumed(), + self.deposit_consumed(), + other.weight_left(), + other.deposit_left(), + other.weight_consumed(), + other.deposit_consumed(), + ); + self.weight.absorb_nested(other.weight); self.deposit.absorb_only_max_charged(other.deposit); + + log::trace!( + target: LOG_TARGET, + "Absorb weight meter done: \ + parent_weight_left={:?}, \ + parent_deposit_left={:?}, \ + parent_weight_consumed={:?}, \ + parent_deposit_consumed={:?}", + self.weight_left(), + self.deposit_left(), + self.weight_consumed(), + self.deposit_consumed(), + ); } /// Absorb all resource consumption from a nested frame meter. @@ -144,8 +206,42 @@ impl ResourceMeter { contract: &T::AccountId, info: Option<&mut ContractInfo>, ) { + log::trace!( + target: LOG_TARGET, + "Absorb all meters: \ + parent_weight_left={:?}, \ + parent_deposit_left={:?}, \ + parent_weight_consumed={:?}, \ + parent_deposit_consumed={:?}, \ + child_weight_left={:?}, \ + child_deposit_left={:?}, \ + child_weight_consumed={:?}, \ + child_deposit_consumed={:?}", + self.weight_left(), + self.deposit_left(), + self.weight_consumed(), + self.deposit_consumed(), + other.weight_left(), + other.deposit_left(), + other.weight_consumed(), + other.deposit_consumed(), + ); + self.weight.absorb_nested(other.weight); self.deposit.absorb(other.deposit, contract, info); + + log::trace!( + target: LOG_TARGET, + "Absorb all meters done: \ + parent_weight_left={:?}, \ + parent_deposit_left={:?}, \ + parent_weight_consumed={:?}, \ + parent_deposit_consumed={:?}", + self.weight_left(), + self.deposit_left(), + self.weight_consumed(), + self.deposit_consumed(), + ); } /// Charge a weight token against this meter's remaining weight limit. @@ -334,6 +430,11 @@ impl TransactionMeter { /// - An ethereum-style gas-based meter or /// - A substrate-style meter with explicit weight and deposit limits pub fn new(transaction_limits: TransactionLimits) -> Result { + log::debug!( + target: LOG_TARGET, + "Start new meter: transaction_limits={transaction_limits:?}", + ); + match transaction_limits { TransactionLimits::EthereumGas { eth_gas_limit, maybe_weight_limit, eth_tx_info } => math::ethereum_execution::new_root(eth_gas_limit, maybe_weight_limit, eth_tx_info), @@ -358,6 +459,21 @@ impl TransactionMeter { origin: &Origin, exec_config: &ExecConfig, ) -> Result, DispatchError> { + log::debug!( + target: LOG_TARGET, + "Transaction meter finishes: \ + weight_left={:?}, \ + deposit_left={:?}, \ + weight_consumed={:?}, \ + deposit_consumed={:?}, \ + eth_gas_consumed={:?}", + self.weight_left(), + self.deposit_left(), + self.weight_consumed(), + self.deposit_consumed(), + self.eth_gas_consumed(), + ); + if self.deposit_left().is_none() { // Deposit limit exceeded return Err(>::StorageDepositNotEnoughFunds.into()); From 7fd98e66623cf9bf85905f29493fbdab3563df11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Fri, 21 Nov 2025 05:20:19 -0300 Subject: [PATCH 28/69] Clean up create call arguments --- substrate/frame/revive/src/evm/call.rs | 22 ++++++++++++++++------ substrate/frame/revive/src/evm/runtime.rs | 7 +++++-- substrate/frame/revive/src/lib.rs | 11 ++++++++--- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs index a23505052432f..dedf870a90f08 100644 --- a/substrate/frame/revive/src/evm/call.rs +++ b/substrate/frame/revive/src/evm/call.rs @@ -48,6 +48,16 @@ pub struct CallInfo { pub eth_gas_limit: U256, } +/// Mode for creating a call from an ethereum transaction. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum CreateCallMode { + /// Mode for extrinsic execution. Carries the encoding length of the extrinsic and the + /// RLP-encoded Ethereum transaction + ExtrinsicExecution(u32, Vec), + /// Mode for dry running + DryRun, +} + /// Decode `tx` into a dispatchable call. /// /// signed_transaction is Some(..) for extrinsic execution (when called from @@ -55,14 +65,13 @@ pub struct CallInfo { /// `dry_run_eth_transact`) pub fn create_call( tx: GenericTransaction, - signed_transaction: Option<(u32, Vec)>, - apply_weight_cap: bool, + mode: CreateCallMode, ) -> Result, InvalidTransaction> where T: Config, CallOf: SetWeightLimit, { - let is_dry_run = signed_transaction.is_none(); + let is_dry_run = matches!(mode, CreateCallMode::DryRun); let base_fee = >::evm_base_fee(); let Some(gas) = tx.gas else { @@ -94,7 +103,7 @@ where } let (encoded_len, transaction_encoded) = - if let Some((encoded_len, transaction_encoded)) = signed_transaction { + if let CreateCallMode::ExtrinsicExecution(encoded_len, transaction_encoded) = mode { (encoded_len, transaction_encoded) } else { let unsigned_tx = tx.clone().try_into_unsigned().map_err(|_| { @@ -193,7 +202,7 @@ where call.set_weight_limit(weight_limit); - if apply_weight_cap { + if !is_dry_run { let max_weight = >::evm_max_extrinsic_weight(); let info = ::FeeInfo::dispatch_info(&call); let overweight_by = info.total_weight().saturating_sub(max_weight); @@ -205,7 +214,8 @@ where } }; - // Add some buffer for dry running + // Add some buffer for dry running because the length of the RLP encoded Ethereum transaction + // for actual execution might differ slightly. let encoding_len_with_margin = if is_dry_run { encoded_len.saturating_add(ENCODING_LENGTH_SAFETY_MARGIN) } else { diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index c803595f015ce..b89cda9471f6c 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -20,6 +20,7 @@ use crate::{ api::{GenericTransaction, TransactionSigned}, create_call, fees::InfoT, + CreateCallMode, }, AccountIdOf, AddressMapper, BalanceOf, CallOf, Config, Pallet, Zero, LOG_TARGET, }; @@ -314,8 +315,10 @@ pub trait EthExtra { })?; log::debug!(target: LOG_TARGET, "Decoded Ethereum transaction with signer: {signer_addr:?} nonce: {nonce:?}"); - let call_info = - create_call::(tx, Some((encoded_len as u32, payload.to_vec())), true)?; + let call_info = create_call::( + tx, + CreateCallMode::ExtrinsicExecution(encoded_len as u32, payload.to_vec()), + )?; let storage_credit = ::Currency::withdraw( &signer, call_info.storage_deposit, diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 309e8a999b3e0..5b09c13b860e7 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -49,8 +49,8 @@ pub mod weights; use crate::{ evm::{ block_hash::EthereumBlockBuilderIR, block_storage, create_call, fees::InfoT as FeeInfo, - runtime::SetWeightLimit, CallTracer, GenericTransaction, PrestateTracer, Trace, Tracer, - TracerType, TYPE_EIP1559, + runtime::SetWeightLimit, CallTracer, CreateCallMode, GenericTransaction, PrestateTracer, + Trace, Tracer, TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack}, limits::ENCODING_LENGTH_SAFETY_MARGIN, @@ -1781,7 +1781,7 @@ impl Pallet { // we need to parse the weight from the transaction so that it is run // using the exact weight limit passed by the eth wallet - let mut call_info = create_call::(tx, None, false) + let mut call_info = create_call::(tx, CreateCallMode::DryRun) .map_err(|err| EthTransactError::Message(format!("Invalid call: {err:?}")))?; // the dry-run might leave out certain fields @@ -1933,6 +1933,11 @@ impl Pallet { let total_weight = T::FeeInfo::dispatch_info(&call_info.call).total_weight(); let max_weight = Self::evm_max_extrinsic_weight(); if total_weight.any_gt(max_weight) { + log::debug!(target: LOG_TARGET, "Transaction weight estimate exceeds extrinsic maximum: \ + total_weight={total_weight:?} \ + max_weight={max_weight:?}", + ); + Err(EthTransactError::Message(format!( "\ The transaction consumes more than the allowed weight. \ From c6041225c8d2809e5ebd24feba3a4fa6431061c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:55:46 -0300 Subject: [PATCH 29/69] Add optimizations and correct numerical calculations --- .../fixtures/contracts/CatchConstructor.sol | 58 +++++++ substrate/frame/revive/src/evm/call.rs | 39 ++--- substrate/frame/revive/src/evm/fees.rs | 93 +++++++++-- substrate/frame/revive/src/exec.rs | 8 +- substrate/frame/revive/src/exec/mock_ext.rs | 7 +- substrate/frame/revive/src/lib.rs | 35 ++-- substrate/frame/revive/src/limits.rs | 5 - substrate/frame/revive/src/metering/mod.rs | 150 +++++++++++++----- substrate/frame/revive/src/metering/tests.rs | 80 +++++++++- substrate/frame/revive/src/metering/weight.rs | 57 +++---- substrate/frame/revive/src/storage.rs | 2 +- .../frame/revive/src/tests/sol/contract.rs | 4 +- 12 files changed, 413 insertions(+), 125 deletions(-) create mode 100644 substrate/frame/revive/fixtures/contracts/CatchConstructor.sol diff --git a/substrate/frame/revive/fixtures/contracts/CatchConstructor.sol b/substrate/frame/revive/fixtures/contracts/CatchConstructor.sol new file mode 100644 index 0000000000000..31fa78c3cf44c --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/CatchConstructor.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +// External contract used for try / catch examples +contract CatchConstructorFoo { + address public owner; + + constructor(address _owner) { + require(_owner != address(0), "invalid address"); + assert(_owner != 0x0000000000000000000000000000000000000001); + owner = _owner; + } + + function myFunc(uint x) public pure returns (string memory) { + require(x != 0, "require failed"); + return "my func was called"; + } +} + +contract CatchConstructorTest { + event Log(string message); + event LogBytes(bytes data); + + CatchConstructorFoo public foo; + + constructor() { + // This CatchConstructorFoo contract is used for example of try catch with external call + foo = new CatchConstructorFoo(msg.sender); + } + + // Example of try / catch with external call + // tryCatchExternalCall(0) => Log("external call failed") + // tryCatchExternalCall(1) => Log("my func was called") + function tryCatchExternalCall(uint _i) public { + try foo.myFunc(_i) returns (string memory result) { + emit Log(result); + } catch { + emit Log("external call failed"); + } + } + + // Example of try / catch with contract creation + // tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address") + // tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("") + // tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("CatchConstructorFoo created") + function tryCatchNewContract(address _owner) public { + try new CatchConstructorFoo(_owner) returns (CatchConstructorFoo foo) { + // you can use variable foo here + emit Log("CatchConstructorFoo created"); + } catch Error(string memory reason) { + // catch failing revert() and require() + emit Log(reason); + } catch (bytes memory reason) { + // catch failing assert() + emit LogBytes(reason); + } + } +} \ No newline at end of file diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs index dedf870a90f08..02864f37bdd80 100644 --- a/substrate/frame/revive/src/evm/call.rs +++ b/substrate/frame/revive/src/evm/call.rs @@ -18,17 +18,18 @@ //! Functionality to decode an eth transaction into an dispatchable call. use crate::{ - evm::{fees::InfoT, runtime::SetWeightLimit}, - extract_code_and_data, - limits::ENCODING_LENGTH_SAFETY_MARGIN, - BalanceOf, CallOf, Config, GenericTransaction, Pallet, Weight, Zero, LOG_TARGET, - RUNTIME_PALLETS_ADDR, + evm::{ + fees::{compute_max_integer_quotient, InfoT}, + runtime::SetWeightLimit, + }, + extract_code_and_data, BalanceOf, CallOf, Config, GenericTransaction, Pallet, Weight, Zero, + LOG_TARGET, RUNTIME_PALLETS_ADDR, }; use alloc::{boxed::Box, vec::Vec}; use codec::DecodeLimit; use frame_support::MAX_EXTRINSIC_DEPTH; use sp_core::{Get, U256}; -use sp_runtime::{transaction_validity::InvalidTransaction, FixedPointNumber, SaturatedConversion}; +use sp_runtime::{transaction_validity::InvalidTransaction, SaturatedConversion}; /// Result of decoding an eth transaction into a dispatchable call. pub struct CallInfo { @@ -106,7 +107,14 @@ where if let CreateCallMode::ExtrinsicExecution(encoded_len, transaction_encoded) = mode { (encoded_len, transaction_encoded) } else { - let unsigned_tx = tx.clone().try_into_unsigned().map_err(|_| { + // For dry runs, we need to ensure that the RLP encoding length is at least the length + // of the encoding of the actual transaction submitted later + let mut maximized_tx = tx.clone(); + maximized_tx.gas = Some(U256::MAX); + maximized_tx.gas_price = Some(U256::MAX); + maximized_tx.max_priority_fee_per_gas = Some(U256::MAX); + + let unsigned_tx = maximized_tx.try_into_unsigned().map_err(|_| { log::debug!(target: LOG_TARGET, "Invalid transaction type."); InvalidTransaction::Call })?; @@ -188,8 +196,11 @@ where log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover base and len fee. eth_fee={eth_fee:?} fixed_fee={fixed_fee:?}"); InvalidTransaction::Payment })?; - let unadjusted = ::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(>::saturated_from(adjusted)); + + let unadjusted = compute_max_integer_quotient( + ::FeeInfo::next_fee_multiplier(), + >::saturated_from(adjusted), + ); unadjusted }; @@ -214,16 +225,8 @@ where } }; - // Add some buffer for dry running because the length of the RLP encoded Ethereum transaction - // for actual execution might differ slightly. - let encoding_len_with_margin = if is_dry_run { - encoded_len.saturating_add(ENCODING_LENGTH_SAFETY_MARGIN) - } else { - encoded_len - }; - // the overall fee of the extrinsic including the gas limit - let tx_fee = ::FeeInfo::tx_fee(encoding_len_with_margin, &call); + let tx_fee = ::FeeInfo::tx_fee(encoded_len, &call); // the leftover we make available to the deposit collection system let storage_deposit = eth_fee.checked_sub(tx_fee.into()).ok_or_else(|| { diff --git a/substrate/frame/revive/src/evm/fees.rs b/substrate/frame/revive/src/evm/fees.rs index 2c0bc93943689..ab8f91a4b1692 100644 --- a/substrate/frame/revive/src/evm/fees.rs +++ b/substrate/frame/revive/src/evm/fees.rs @@ -34,10 +34,11 @@ use frame_support::{ weights::WeightToFee, }; use frame_system::Config as SysConfig; -use num_traits::Zero; +use num_traits::{One, Zero}; use pallet_transaction_payment::{ Config as TxConfig, MultiplierUpdate, NextFeeMultiplier, Pallet as TxPallet, TxCreditHold, }; +use sp_arithmetic::{FixedPointOperand, SignedRounding}; use sp_runtime::{ generic::UncheckedExtrinsic, traits::{ @@ -326,14 +327,12 @@ where /// 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); + let ref_time_to_fee = ::WeightToFee::REF_TIME_TO_FEE; + let proof_size_to_fee = ::WeightToFee::proof_size_to_fee(); + + let (ref_time, proof_size) = + compute_max_integer_pair_quotient((ref_time_to_fee, proof_size_to_fee), fee); + Weight::from_parts(ref_time.saturated_into(), proof_size.saturated_into()) } @@ -376,3 +375,79 @@ mod seal { impl Sealed for super::Info {} impl Sealed for () {} } + +/// Determine the maximal integer `n` so that `multiplier.saturating_mul_int(n) <= product` +/// +/// FixedU128 wraps a 128 bit unsigned integer `self.0` and it is interpreted to represent the real +/// number self.0 / FixedU128::DIV, where FixedU128::DIV is 1_000_000_000_000_000_000. +/// +/// Given an integer `n`, the operation `multiplier.saturating_mul_int(n)` is defined as +/// `div_round_down(multiplier.0 * n, FixedU128::DIV)` +/// where `div_round_down` is integer division where the result is rounded down. +/// +/// To determine the maximal integer `n` so that `multiplier.saturating_mul_int(n) <= product` is +/// therefore equivalent to determining the maximal `n` such that +/// `div_round_down(multiplier.0 * n, FixedU128::DIV) <= product` +/// This is equivalent to the condition +/// `multiplier.0 * n <= product * FixedU128::DIV + FixedU128::DIV - 1` +/// This is equivalent to +/// `multiplier.0 * n < (product + 1) * FixedU128::DIV` +/// This is equivalent to +/// `n < div_round_up((product + 1) * FixedU128::DIV, multiplier.0)` +/// where `div_round_up` is integer division where the result is rounded up. +/// Since we look for a maximal `n` with this condition, the result is +/// `n = div_round_up((product + 1) * FixedU128::DIV, multiplier.0) - 1`. +/// +/// We can take advantage of the function `FixedU128::checked_rounding_div`, which, given two fixed +/// point numbers `a` and `b`, just computes `a.0 * FixedU128::DIV / b.0`. It also allows to specify +/// the rounding mode `SignedRounding::Major`, which means the that result of the division is +/// rounded up. +pub fn compute_max_integer_quotient( + multiplier: FixedU128, + product: F, +) -> F { + let one = F::one(); + let product_plus_one = FixedU128::from_inner(product.saturating_add(one).saturated_into()); + + product_plus_one + .checked_rounding_div(multiplier, SignedRounding::Major) + .map(|f| f.into_inner().saturated_into::().saturating_sub(one)) + .unwrap_or(F::max_value()) +} + +/// same as compute_max_integer_quotient but applied to a pair +pub fn compute_max_integer_pair_quotient( + multiplier: (FixedU128, FixedU128), + product: F, +) -> (F, F) { + let one = F::one(); + let product_plus_one = FixedU128::from_inner(product.saturating_add(one).saturated_into()); + + let result1 = product_plus_one + .checked_rounding_div(multiplier.0, SignedRounding::Major) + .map(|f| f.into_inner().saturated_into::().saturating_sub(one)) + .unwrap_or(F::max_value()); + + let result2 = product_plus_one + .checked_rounding_div(multiplier.1, SignedRounding::Major) + .map(|f| f.into_inner().saturated_into::().saturating_sub(one)) + .unwrap_or(F::max_value()); + + (result1, result2) +} + +#[test] +fn compute_max_quotient_works() { + let product1 = 8625031518u64; + let product2 = 2597808837u64; + + let multiplier = FixedU128::from_rational(4_000_000_000_000, 10 * 1024 * 1024); + + assert_eq!(compute_max_integer_quotient(multiplier, product1), 22610); + assert_eq!(compute_max_integer_quotient(multiplier, product2), 6810); + + // This shows that just dividing by the multiplier does not give the correct result, neither + // when rounding up, nor when rounding down + assert_eq!(multiplier.reciprocal().unwrap().saturating_mul_int(product1), 22610); + assert_eq!(multiplier.reciprocal().unwrap().saturating_mul_int(product2), 6809); +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 5ddf28cd7ff37..1ff075f0a0cf8 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -524,7 +524,7 @@ pub trait PrecompileExt: sealing::Sealed { ) -> Result; /// Charges `diff` from the meter. - fn charge_storage(&mut self, diff: &storage::Diff); + fn charge_storage(&mut self, diff: &storage::Diff) -> DispatchResult; } /// Describes the different functions that can be exported by an [`Executable`]. @@ -1393,7 +1393,7 @@ where frame.frame_meter.charge_contract_deposit_and_transfer( frame.account_id.clone(), StorageDeposit::Charge(deposit), - ); + )?; frame } else { self.top_frame_mut() @@ -1844,7 +1844,7 @@ where frame .frame_meter - .charge_contract_deposit_and_transfer(frame.account_id.clone(), deposit); + .charge_contract_deposit_and_transfer(frame.account_id.clone(), deposit)?; >::increment_refcount(hash)?; let removed = >::decrement_refcount(prev_hash)?; @@ -2354,7 +2354,7 @@ where ) } - fn charge_storage(&mut self, diff: &storage::Diff) { + fn charge_storage(&mut self, diff: &storage::Diff) -> DispatchResult { assert!(self.has_contract_info()); self.top_frame_mut().frame_meter.record_contract_storage_changes(diff) } diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index 97f3538d02574..edb2f60a4fa3d 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -25,7 +25,8 @@ use crate::{ precompiles::Diff, storage::{ContractInfo, WriteOutcome}, transient_storage::TransientStorage, - BalanceOf, Code, CodeRemoved, Config, ExecReturnValue, ImmutableData, ReentrancyProtection, + BalanceOf, Code, CodeRemoved, Config, DispatchResult, ExecReturnValue, ImmutableData, + ReentrancyProtection, }; use alloc::vec::Vec; use core::marker::PhantomData; @@ -251,7 +252,9 @@ impl PrecompileExt for MockExt { panic!("MockExt::set_storage") } - fn charge_storage(&mut self, _diff: &Diff) {} + fn charge_storage(&mut self, _diff: &Diff) -> DispatchResult { + Ok(()) + } } impl PrecompileWithInfoExt for MockExt { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 5b09c13b860e7..94d3cf50ce717 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -53,7 +53,6 @@ use crate::{ Trace, Tracer, TracerType, TYPE_EIP1559, }, exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack}, - limits::ENCODING_LENGTH_SAFETY_MARGIN, storage::{AccountType, DeletionQueueManager}, tracing::if_tracing, vm::{pvm::extract_code_and_data, CodeInfo, RuntimeCosts}, @@ -1602,10 +1601,11 @@ impl Pallet { }; let result = Self::run_guarded(try_call); - log::trace!(target: LOG_TARGET, "Bare call ends: weight_consumed={:?}\ - weight_required={:?} \ - storage_deposit={:?} \ - gas_consumed={:?} \ + log::trace!(target: LOG_TARGET, "Bare call ends: \ + weight_consumed={:?}, \ + weight_required={:?}, \ + storage_deposit={:?}, \ + gas_consumed={:?}, \ max_storage_deposit={:?}", transaction_meter.weight_consumed(), transaction_meter.weight_required(), @@ -1950,15 +1950,12 @@ impl Pallet { } // not enough gas supplied to pay for both the tx fees and the storage deposit - let transaction_fee = T::FeeInfo::tx_fee( - call_info.encoded_len.saturating_add(ENCODING_LENGTH_SAFETY_MARGIN), - &call_info.call, - ); + let transaction_fee = T::FeeInfo::tx_fee(call_info.encoded_len, &call_info.call); let available_fee = T::FeeInfo::remaining_txfee(); if transaction_fee > available_fee { Err(EthTransactError::Message(format!( "Not enough gas supplied: Off by: {:?}", - call_info.tx_fee.saturating_sub(available_fee), + transaction_fee.saturating_sub(available_fee), )))?; } @@ -1970,15 +1967,15 @@ impl Pallet { .into(); log::debug!(target: LOG_TARGET, "\ - dry_run_eth_transact: \ - weight_limit={} \ - total_weight={total_weight} \ - max_weight={max_weight} \ - weight_left={} \ - eth_gas={eth_gas}) \ - encoded_len={} \ - tx_fee={transaction_fee:?} \ - storage_deposit={:?}\ + dry_run_eth_transact finished: \ + weight_limit={}, \ + total_weight={total_weight}, \ + max_weight={max_weight}, \ + weight_left={}, \ + eth_gas={eth_gas}, \ + encoded_len={}, \ + tx_fee={transaction_fee:?}, \ + storage_deposit={:?}, \ max_storage_deposit={:?}\ ", dry_run.weight_required, diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 25848db290aca..75c8358f8d8e7 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -99,11 +99,6 @@ pub const EVM_STACK_LIMIT: u32 = 1024; /// The call stipend gas amount defined in the EVM pub const SOLIDITY_CALL_STIPEND: u32 = 2300; -/// The RLP encoding length of an Ethereum transaction can change when input values are changed -/// slightly. We want to ensure that the encoding length during gas estimation is at least the -/// encoding length of the actual transaction -pub const ENCODING_LENGTH_SAFETY_MARGIN: u32 = 10; - /// Limits that are only enforced on code upload. /// /// # Note diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 022f6f55c2be8..28644443e854f 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -136,12 +136,14 @@ impl ResourceMeter { self.deposit_consumed(), ); - let new_meter = match &self.transaction_limits { + let mut new_meter = match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => math::ethereum_execution::new_nested_meter(self, limit, eth_tx_info), TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::new_nested_meter(self, limit), - }; + }?; + + new_meter.adjust_effective_weight_limit()?; log::trace!( target: LOG_TARGET, @@ -150,13 +152,13 @@ impl ResourceMeter { deposit_left={:?}, \ weight_consumed={:?}, \ deposit_consumed={:?}", - new_meter.as_ref().map(|s| s.weight_left()), - new_meter.as_ref().map(|s| s.deposit_left()), - new_meter.as_ref().map(|s| s.weight_consumed()), - new_meter.as_ref().map(|s| s.deposit_consumed()), + new_meter.weight_left(), + new_meter.deposit_left(), + new_meter.weight_consumed(), + new_meter.deposit_consumed(), ); - new_meter + Ok(new_meter) } /// Absorb only the weight consumption from a nested frame meter. @@ -230,6 +232,9 @@ impl ResourceMeter { self.weight.absorb_nested(other.weight); self.deposit.absorb(other.deposit, contract, info); + let result = self.adjust_effective_weight_limit(); + debug_assert!(result.is_ok(), "Absorbing nested meters should not exceed limits"); + log::trace!( target: LOG_TARGET, "Absorb all meters done: \ @@ -247,25 +252,21 @@ impl ResourceMeter { /// Charge a weight token against this meter's remaining weight limit. /// /// Returns `Err(Error::OutOfGas)` if the weight limit would be exceeded. + #[inline] pub fn charge_weight_token>( &mut self, token: Tok, ) -> Result { - // TODO: optimize - let weight_left = self.weight_left().ok_or(>::OutOfGas)?; - - self.weight.charge(token, weight_left) + self.weight.charge(token) } /// Try to charge a weight token or halt if not enough weight is left. + #[inline] pub fn charge_or_halt>( &mut self, token: Tok, ) -> ControlFlow { - // TODO: optimize - let weight_left = self.weight_left().unwrap_or_default(); - - self.weight.charge_or_halt(token, weight_left) + self.weight.charge_or_halt(token) } /// Adjust an earlier weight charge with the actual weight consumed. @@ -279,12 +280,7 @@ impl ResourceMeter { /// - Converts engine fuel units to weight units /// - Updates meter state to match actual VM resource usage pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> { - // TODO: optimize - let weight_left = self.weight_left().ok_or(>::OutOfGas)?; - let weight_consumed = self.weight.weight_consumed(); - - self.weight - .sync_from_executor(engine_fuel, weight_left.saturating_add(weight_consumed)) + self.weight.sync_from_executor(engine_fuel) } /// Convert meter state to PolkaVM executor fuel units. @@ -293,28 +289,36 @@ impl ResourceMeter { /// - Computing remaining available weight /// - Converting weight units to VM fuel units and return pub fn sync_to_executor(&mut self) -> polkavm::Gas { - // TODO: optimize - let weight_left = self.weight_left().unwrap_or_default(); - - self.weight.sync_to_executor(weight_left) + self.weight.sync_to_executor() } /// Consume all remaining weight in the meter. pub fn consume_all_weight(&mut self) { - // TODO: optimize - let weight_left = self.weight_left().unwrap_or_default(); - let weight_consumed = self.weight.weight_consumed(); - - self.weight.consume_all(weight_left.saturating_add(weight_consumed)); + self.weight.consume_all(); } /// Record a storage deposit charge against this meter. pub fn charge_deposit(&mut self, deposit: &DepositOf) -> DispatchResult { + log::trace!( + target: LOG_TARGET, + "Charge deposit: \ + deposit={:?}, \ + deposit_left={:?}, \ + deposit_consumed={:?}, \ + max_charged={:?}", + deposit, + self.deposit_left(), + self.deposit_consumed(), + self.deposit.max_charged(), + ); + self.deposit.record_charge(deposit); + self.adjust_effective_weight_limit()?; if self.deposit.is_root { if self.deposit_left().is_none() { self.deposit.reset(); + self.adjust_effective_weight_limit()?; return Err(>::StorageDepositLimitExhausted.into()); } } @@ -421,6 +425,22 @@ impl ResourceMeter { math::substrate_execution::eth_gas_consumed(self), } } + + /// Determine and set the new effective weight limit of the weight meter. + /// This function needs to be called whenever there is a change in the deposit meter. + fn adjust_effective_weight_limit(&mut self) -> DispatchResult { + if matches!(self.transaction_limits, TransactionLimits::WeightAndDeposit { .. }) { + return Ok(()) + } + + if let Some(weight_left) = self.weight_left() { + let new_effective_limit = self.weight.weight_consumed().saturating_add(weight_left); + self.weight.set_effective_weight_limit(new_effective_limit); + Ok(()) + } else { + Err(>::OutOfGas.into()) + } + } } impl TransactionMeter { @@ -435,12 +455,29 @@ impl TransactionMeter { "Start new meter: transaction_limits={transaction_limits:?}", ); - match transaction_limits { + let mut transaction_meter = match transaction_limits { TransactionLimits::EthereumGas { eth_gas_limit, maybe_weight_limit, eth_tx_info } => math::ethereum_execution::new_root(eth_gas_limit, maybe_weight_limit, eth_tx_info), TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } => math::substrate_execution::new_root(weight_limit, deposit_limit), - } + }?; + + transaction_meter.adjust_effective_weight_limit()?; + + log::trace!( + target: LOG_TARGET, + "New meter done: \ + weight_left={:?}, \ + deposit_left={:?}, \ + weight_consumed={:?}, \ + deposit_consumed={:?}", + transaction_meter.weight_left(), + transaction_meter.deposit_left(), + transaction_meter.weight_consumed(), + transaction_meter.deposit_consumed(), + ); + + Ok(transaction_meter) } /// Convenience constructor for substrate-style weight+deposit limits. @@ -492,6 +529,9 @@ impl TransactionMeter { ) { self.deposit .terminate_absorb(contract_account, contract_info, beneficiary, delete_code); + + let result = self.adjust_effective_weight_limit(); + debug_assert!(result.is_ok(), "Absorbing terminated meters should not exceed limits"); } } @@ -504,19 +544,47 @@ impl FrameMeter { &mut self, contract: T::AccountId, amount: DepositOf, - ) { - self.deposit.charge_deposit(contract, amount) + ) -> DispatchResult { + log::trace!( + target: LOG_TARGET, + "Charge deposit and transfer: \ + amount={:?}, \ + deposit_left={:?}, \ + deposit_consumed={:?}, \ + max_charged={:?}", + amount, + self.deposit_left(), + self.deposit_consumed(), + self.deposit.max_charged(), + ); + + self.deposit.charge_deposit(contract, amount); + self.adjust_effective_weight_limit() } /// Record storage changes of a contract. - pub fn record_contract_storage_changes(&mut self, diff: &Diff) { + pub fn record_contract_storage_changes(&mut self, diff: &Diff) -> DispatchResult { + log::trace!( + target: LOG_TARGET, + "Charge contract storage: \ + diff={:?}, \ + deposit_left={:?}, \ + deposit_consumed={:?}, \ + max_charged={:?}", + diff, + self.deposit_left(), + self.deposit_consumed(), + self.deposit.max_charged(), + ); + self.deposit.charge(diff); + self.adjust_effective_weight_limit() } /// [`Self::charge_contract_deposit_and_transfer`] and [`Self::record_contract_storage_changes`] /// does not enforce the storage limit since we want to do this check as late as possible to /// allow later refunds to offset earlier charges. - pub fn finalize(&mut self, info: Option<&mut ContractInfo>) -> Result<(), DispatchError> { + pub fn finalize(&mut self, info: Option<&mut ContractInfo>) -> DispatchResult { self.deposit.finalize_own_contributions(info); if self.deposit_left().is_none() { @@ -562,6 +630,16 @@ impl EthTxInfo { &consumed_weight.saturating_add(self.extra_weight), )); + log::trace!(target: LOG_TARGET, "Gas consumption calculation: \ + consumed_weight={consumed_weight:?}, \ + consumed_deposit={consumed_deposit:?}, \ + deposit_gas={deposit_gas:?}, \ + fixed_fee_gas={fixed_fee_gas:?}, \ + scaled_gas={scaled_gas:?}, \ + weight_fee={weight_fee:?}, \ + extra_weight={:?}", + self.extra_weight,); + scaled_gas.saturating_add(&weight_fee) } diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs index 7cd3c84b7b1f0..aa0879b9715ae 100644 --- a/substrate/frame/revive/src/metering/tests.rs +++ b/substrate/frame/revive/src/metering/tests.rs @@ -16,14 +16,16 @@ // limitations under the License. use crate::{ - test_utils::{builder::Contract, ALICE}, + test_utils::{builder::Contract, ALICE, ALICE_ADDR}, tests::{builder, ExtBuilder, Test}, CallResources, Code, Config, EthTxInfo, StorageDeposit, TransactionLimits, TransactionMeter, WeightToken, }; use alloy_core::sol_types::SolCall; use frame_support::traits::fungible::Mutate; -use pallet_revive_fixtures::{compile_module_with_type, Deposit, FixtureType}; +use pallet_revive_fixtures::{ + compile_module_with_type, CatchConstructorTest, Deposit, FixtureType, +}; use sp_runtime::{FixedU128, Weight}; use test_case::test_case; @@ -621,3 +623,77 @@ fn substrate_nesting_charges_works() { }); } } + +#[test] +fn catch_constructor_test() { + use crate::{evm::*, tracing::trace}; + use frame_support::assert_ok; + + let (code, _) = compile_module_with_type("CatchConstructorTest", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 10_000_000_000_000); + + let Contract { addr: test_address, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let first_estimate = crate::Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(ALICE_ADDR), + to: Some(test_address), + input: CatchConstructorTest::tryCatchNewContractCall { _owner: [0u8; 20].into() } + .abi_encode() + .into(), + ..Default::default() + }, + Default::default(), + ); + + assert_ok!(first_estimate.as_ref()); + + let second_estimate = crate::Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(ALICE_ADDR), + to: Some(test_address), + gas: Some(first_estimate.unwrap().eth_gas.into()), + input: CatchConstructorTest::tryCatchNewContractCall { _owner: [0u8; 20].into() } + .abi_encode() + .into(), + ..Default::default() + }, + Default::default(), + ); + + assert_ok!(second_estimate); + + let make_call = |eth_gas_limit: u64| { + builder::bare_call(test_address) + .data( + CatchConstructorTest::tryCatchNewContractCall { _owner: [0u8; 20].into() } + .abi_encode(), + ) + .transaction_limits(crate::TransactionLimits::EthereumGas { + eth_gas_limit: eth_gas_limit.into(), + maybe_weight_limit: None, + eth_tx_info: crate::EthTxInfo::new(0, Default::default()), + }) + .build() + }; + + let results = make_call(u64::MAX); + + let mut tracer = + CallTracer::new(CallTracerConfig { with_logs: true, only_top_call: false }); + + trace(&mut tracer, || { + let results = make_call( + results + .gas_consumed + .saturating_add(::ExistentialDeposit::get()), + ); + assert_ok!(results.result); + }); + let gas_trace = tracer.collect_trace().unwrap(); + assert_eq!("revert: invalid address", gas_trace.calls[0].revert_reason.as_ref().unwrap()); + }); +} diff --git a/substrate/frame/revive/src/metering/weight.rs b/substrate/frame/revive/src/metering/weight.rs index 9ee7bd57a0060..568b073fdc3e5 100644 --- a/substrate/frame/revive/src/metering/weight.rs +++ b/substrate/frame/revive/src/metering/weight.rs @@ -124,7 +124,12 @@ pub struct ErasedToken { #[derive(DefaultNoBound)] pub struct WeightMeter { + /// The overall weight limit of this weight meter. If it is None, then there is no restriction pub weight_limit: Option, + /// The current actual effective weight limit. Used to check whether the weight meter ran out + /// of resources. This weight limit needs to be adapted whenever the metering runs in ethereum + /// mode and there is a charge on the deposit meter. + effective_weight_limit: Weight, /// Amount of weight already consumed. Must be < `weight_limit`. weight_consumed: Weight, /// Due to `adjust_weight` and `nested` the `weight_consumed` can temporarily peak above its @@ -143,6 +148,7 @@ impl WeightMeter { pub fn new(weight_limit: Option, stipend: Option) -> Self { WeightMeter { weight_limit, + effective_weight_limit: weight_limit.unwrap_or_default(), weight_consumed: Default::default(), weight_consumed_highest: stipend.unwrap_or_default(), engine_meter: EngineMeter::new(), @@ -152,6 +158,10 @@ impl WeightMeter { } } + pub fn set_effective_weight_limit(&mut self, limit: Weight) { + self.effective_weight_limit = limit; + } + /// Absorb the remaining weight of a nested meter after we are done using it. pub fn absorb_nested(&mut self, nested: Self) { self.weight_consumed_highest = self @@ -171,11 +181,7 @@ impl WeightMeter { /// NOTE that amount isn't consumed if there is not enough weight. This is considered /// safe because we always charge weight before performing any resource-spending action. #[inline] - pub fn charge>( - &mut self, - token: Tok, - weight_left: Weight, - ) -> Result { + pub fn charge>(&mut self, token: Tok) -> Result { #[cfg(test)] { // Unconditionally add the token to the storage. @@ -183,24 +189,26 @@ impl WeightMeter { ErasedToken { description: format!("{:?}", token), token: Box::new(token) }; self.tokens.push(erased_tok); } + let amount = token.weight(); // It is OK to not charge anything on failure because we always charge _before_ we perform // any action - if amount.any_gt(weight_left) { + let new_consumed = self.weight_consumed.saturating_add(amount); + if new_consumed.any_gt(self.effective_weight_limit) { Err(>::OutOfGas)?; } - self.weight_consumed = self.weight_consumed.saturating_add(amount); + self.weight_consumed = new_consumed; Ok(ChargedAmount(amount)) } /// Charge the specified token amount of weight or halt if not enough weight is left. + #[inline] pub fn charge_or_halt>( &mut self, token: Tok, - weight_left: Weight, ) -> ControlFlow { - self.charge(token, weight_left) + self.charge(token) .map_or_else(|_| ControlFlow::Break(Error::::OutOfGas.into()), ControlFlow::Continue) } @@ -221,18 +229,14 @@ impl WeightMeter { /// Needs to be called when entering a host function to update this meter with the /// gas that was tracked by the executor. It tracks the latest seen total value /// in order to compute the delta that needs to be charged. - pub fn sync_from_executor( - &mut self, - engine_fuel: polkavm::Gas, - weight_limit: Weight, - ) -> Result<(), DispatchError> { + pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> { let weight_consumed = self .engine_meter .set_fuel(engine_fuel.try_into().map_err(|_| Error::::OutOfGas)?); self.weight_consumed.saturating_accrue(weight_consumed); - if self.weight_consumed.any_gt(weight_limit) { - self.weight_consumed = weight_limit; + if self.weight_consumed.any_gt(self.effective_weight_limit) { + self.weight_consumed = self.effective_weight_limit; Err(>::OutOfGas)?; } @@ -247,8 +251,8 @@ impl WeightMeter { /// /// It is important that this does **not** actually sync with the executor. That has /// to be done by the caller. - pub fn sync_to_executor(&mut self, weight_left: Weight) -> polkavm::Gas { - self.engine_meter.sync_remaining_ref_time(weight_left.ref_time()) + pub fn sync_to_executor(&mut self) -> polkavm::Gas { + self.engine_meter.sync_remaining_ref_time(self.weight_left().ref_time()) } /// Returns the amount of weight that is required to run the same call. @@ -264,14 +268,13 @@ impl WeightMeter { self.weight_consumed } - pub fn consume_all(&mut self, weight_limit: Weight) { - self.weight_consumed = weight_limit; + pub fn consume_all(&mut self) { + self.weight_consumed = self.effective_weight_limit; } /// Returns how much weight left from the initial budget. - #[cfg(test)] pub fn weight_left(&self) -> Weight { - self.weight_limit.unwrap().saturating_sub(self.weight_consumed) + self.effective_weight_limit.saturating_sub(self.weight_consumed) } #[cfg(test)] @@ -345,7 +348,7 @@ mod tests { #[test] fn tracing() { let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0)), None); - assert!(!weight_meter.charge(SimpleToken(1), weight_meter.weight_left()).is_err()); + assert!(!weight_meter.charge(SimpleToken(1)).is_err()); let mut tokens = weight_meter.tokens().iter(); match_tokens!(tokens, SimpleToken(1),); @@ -355,7 +358,7 @@ mod tests { #[test] fn refuse_to_execute_anything_if_zero() { let mut weight_meter = WeightMeter::::new(Some(Weight::zero()), None); - assert!(weight_meter.charge(SimpleToken(1), weight_meter.weight_left()).is_err()); + assert!(weight_meter.charge(SimpleToken(1)).is_err()); } /// Previously, passing a `Weight` of 0 to `nested` would consume all of the meter's current @@ -408,10 +411,10 @@ mod tests { let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(200, 0)), None); // The first charge is should lead to OOG. - assert!(weight_meter.charge(SimpleToken(300), weight_meter.weight_left()).is_err()); + assert!(weight_meter.charge(SimpleToken(300)).is_err()); // The weight meter should still contain the full 200. - assert!(weight_meter.charge(SimpleToken(200), weight_meter.weight_left()).is_ok()); + assert!(weight_meter.charge(SimpleToken(200)).is_ok()); } // Charging the exact amount that the user paid for should be @@ -419,6 +422,6 @@ mod tests { #[test] fn charge_exact_amount() { let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(25, 0)), None); - assert!(!weight_meter.charge(SimpleToken(25), weight_meter.weight_left()).is_err()); + assert!(!weight_meter.charge(SimpleToken(25)).is_err()); } } diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index b43252856f922..5ce417b01afb8 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -340,7 +340,7 @@ impl ContractInfo { }, (None, None) => (), } - frame_meter.record_contract_storage_changes(&diff); + frame_meter.record_contract_storage_changes(&diff)?; } match &new_value { diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index 79e141f8f9664..206f599c3c8ae 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -26,7 +26,7 @@ use crate::{ test_utils::{builder::Contract, deposit_limit, ALICE, ALICE_ADDR, BOB_ADDR, WEIGHT_LIMIT}, tests::{builder, ExtBuilder, MockHandlerImpl, Test}, BalanceOf, Code, Config, DelegateInfo, DispatchError, Error, ExecConfig, ExecOrigin, - ExecReturnValue, + ExecReturnValue, Weight, }; use alloy_core::{ primitives::{Bytes, FixedBytes}, @@ -752,7 +752,7 @@ fn subcall_effectively_limited_substrate_tx(caller_type: FixtureType, callee_typ ) .exec_config(config.clone()) .transaction_limits(TransactionLimits::WeightAndDeposit { - weight_limit: WEIGHT_LIMIT, + weight_limit: Weight::from_parts(50_000_000_000, 10 * 1024 * 1024), deposit_limit: case.deposit_limit, }) .build(); From 8bbb05ad13112706acbd08cd1997816adfcdd4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:16:20 -0300 Subject: [PATCH 30/69] Fix code in other crates --- polkadot/zombienet-sdk-tests/tests/parachains/weights.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/zombienet-sdk-tests/tests/parachains/weights.rs b/polkadot/zombienet-sdk-tests/tests/parachains/weights.rs index 54502c1c3a991..78b739dcce6e9 100644 --- a/polkadot/zombienet-sdk-tests/tests/parachains/weights.rs +++ b/polkadot/zombienet-sdk-tests/tests/parachains/weights.rs @@ -306,7 +306,7 @@ async fn instantiate_params( // Make sure we have enough gas and multiply by 4, since without it the calls fail not enough // gas. - Ok((dry_run.gas_required.ref_time * 4, dry_run.gas_required.proof_size * 4, deposit * 4)) + Ok((dry_run.weight_required.ref_time * 4, dry_run.weight_required.proof_size * 4, deposit * 4)) } async fn call_params( @@ -323,7 +323,7 @@ async fn call_params( StorageDeposit::Refund(_) => 0, }; - Ok((dry_run.gas_required.ref_time, dry_run.gas_required.proof_size, deposit)) + Ok((dry_run.weight_required.ref_time, dry_run.weight_required.proof_size, deposit)) } async fn call_contract( From 8f07b7550e5db213da3948e5dec427cb61766573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sat, 22 Nov 2025 17:48:47 -0300 Subject: [PATCH 31/69] Add configuration to set Ethereum gas scale --- .../frame/revive/dev-node/runtime/src/lib.rs | 1 + substrate/frame/revive/src/exec.rs | 4 +- substrate/frame/revive/src/lib.rs | 29 ++- substrate/frame/revive/src/metering/gas.rs | 170 ++++++++++++++++++ substrate/frame/revive/src/metering/math.rs | 87 ++++----- substrate/frame/revive/src/metering/mod.rs | 49 ++--- substrate/frame/revive/src/metering/tests.rs | 78 ++++---- substrate/frame/revive/src/primitives.rs | 105 +---------- substrate/frame/revive/src/tests/pvm.rs | 30 +++- 9 files changed, 332 insertions(+), 221 deletions(-) create mode 100644 substrate/frame/revive/src/metering/gas.rs diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index 43adcb24a04a1..d28b6f64d9cbd 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -356,6 +356,7 @@ impl pallet_revive::Config for Runtime { type Time = Timestamp; type FeeInfo = FeeInfo; type DebugEnabled = ConstBool; + type GasScale = ConstU128<5000>; } pallet_revive::impl_runtime_apis_plus_revive_traits!( diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 1ff075f0a0cf8..b6e02881b78d0 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1439,7 +1439,7 @@ where let gas_consumed = if is_first_frame { frame_meter.total_consumed_gas().into() } else { - frame_meter.eth_gas_consumed().as_positive().unwrap_or_default().into() + frame_meter.eth_gas_consumed().into() }; match &output { @@ -1458,7 +1458,7 @@ where let gas_consumed = if is_first_frame { frame_meter.total_consumed_gas().into() } else { - frame_meter.eth_gas_consumed().as_positive().unwrap_or_default().into() + frame_meter.eth_gas_consumed().into() }; tracer.exit_child_span_with_error(error.into(), gas_consumed); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 94d3cf50ce717..b02fd61724480 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -101,8 +101,9 @@ pub use crate::{ }, exec::{CallResources, DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin}, metering::{ - weight::Token as WeightToken, EthTxInfo, FrameMeter, ResourceMeter, TransactionLimits, - TransactionMeter, + gas::{InternalGas, SignedGas}, + weight::Token as WeightToken, + EthTxInfo, FrameMeter, ResourceMeter, TransactionLimits, TransactionMeter, }, pallet::{genesis, *}, storage::{AccountInfo, ContractInfo}, @@ -349,6 +350,10 @@ pub mod pallet { /// Allows debug-mode configuration, such as enabling unlimited contract size. #[pallet::constant] type DebugEnabled: Get; + + #[pallet::constant] + #[pallet::no_default_bounds] + type GasScale: Get>; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -377,6 +382,7 @@ pub mod pallet { pub const DepositPerByte: Balance = deposit(0, 1); pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); pub const MaxEthExtrinsicWeight: FixedU128 = FixedU128::from_rational(9, 10); + pub const GasScale: Balance = 10 as Balance; } /// A type providing default configurations for this pallet in testing environment. @@ -431,6 +437,7 @@ pub mod pallet { type FeeInfo = (); type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; type DebugEnabled = ConstBool; + type GasScale = GasScale; } } @@ -1959,12 +1966,12 @@ impl Pallet { )))?; } - // We add `1` to account for the potential rounding error of the multiplication. - // Returning a larger value here just increases the the pre-dispatch weight. - let eth_gas: U256 = T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(transaction_fee.saturating_add(dry_run.max_storage_deposit)) - .saturating_add(1_u32.into()) - .into(); + let total_cost = transaction_fee.saturating_add(dry_run.max_storage_deposit); + let total_cost_wei = Pallet::::convert_native_to_evm(total_cost); + let (mut eth_gas, rest) = total_cost_wei.div_mod(base_fee); + if !rest.is_zero() { + eth_gas = eth_gas.saturating_add(1_u32.into()); + } log::debug!(target: LOG_TARGET, "\ dry_run_eth_transact finished: \ @@ -2086,8 +2093,12 @@ impl Pallet { /// Get the base gas price. pub fn evm_base_fee() -> U256 { + let gas_scale = ::GasScale::get(); let multiplier = T::FeeInfo::next_fee_multiplier(); - multiplier.saturating_mul_int::(T::NativeToEthRatio::get().into()).into() + multiplier + .saturating_mul_int::(T::NativeToEthRatio::get().into()) + .saturating_mul(gas_scale.saturated_into()) + .into() } /// Build an EVM tracer from the given tracer type. diff --git a/substrate/frame/revive/src/metering/gas.rs b/substrate/frame/revive/src/metering/gas.rs new file mode 100644 index 0000000000000..3a554eddf6ffc --- /dev/null +++ b/substrate/frame/revive/src/metering/gas.rs @@ -0,0 +1,170 @@ +// 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. + +use crate::{evm::fees::InfoT, BalanceOf, Config, StorageDeposit}; +use frame_support::{traits::tokens::Balance as BalanceT, DebugNoBound}; +use sp_core::Get; +use sp_runtime::FixedPointNumber; + +/// Internal scaled representation of Ethereum Gas +/// Compared to the Ethereum Gas amounts that are visible externally, this is scaled by +/// `Config::GasScale` +#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)] +pub struct InternalGas(Balance); + +impl InternalGas { + pub fn into_external_gas(self) -> Balance + where + T: Config, + { + let gas_scale = ::GasScale::get(); + + self.0 / gas_scale + } + + pub fn into_weight_fee(self) -> Balance { + self.0 + } + + pub fn from_weight_fee(weight_fee: Balance) -> Self { + Self(weight_fee) + } + + pub fn from_external_gas(gas: Balance) -> Self + where + T: Config, + { + let gas_scale = ::GasScale::get(); + + Self(gas.saturating_mul(gas_scale)) + } + + pub fn saturating_add(&self, rhs: &Self) -> Self { + Self(self.0.saturating_add(rhs.0)) + } + + pub fn saturating_sub(&self, rhs: &Self) -> Self { + Self(self.0.saturating_sub(rhs.0)) + } + + pub fn min(self, other: Self) -> Self { + Self(self.0.min(other.0)) + } + + pub fn into_adjusted_deposit(self) -> Balance + where + T: Config, + { + let multiplier = T::FeeInfo::next_fee_multiplier(); + + multiplier.saturating_mul_int(self.0) + } +} + +/// The signed version of internal gas. +/// The structure of this type resembles `StorageDeposit` but the enum variants have a more obvious +/// name to avoid confusion and errors +#[derive(Clone, Eq, PartialEq, DebugNoBound)] +pub enum SignedGas { + /// Positive gas amount + Positive(InternalGas>), + /// Negative gas amount + Negative(InternalGas>), +} + +impl Default for SignedGas { + fn default() -> Self { + Self::Positive(Default::default()) + } +} + +impl SignedGas { + pub fn from_weight_fee(weight_fee: BalanceOf) -> Self { + Self::Positive(InternalGas::from_weight_fee(weight_fee)) + } + + pub fn from_external_gas(gas: BalanceOf) -> Self { + Self::Positive(InternalGas::from_external_gas::(gas)) + } + + /// This is essentially a saturating signed add. + pub fn saturating_add(&self, rhs: &Self) -> Self { + use SignedGas::*; + match (self, rhs) { + (Positive(lhs), Positive(rhs)) => Positive(lhs.saturating_add(rhs)), + (Negative(lhs), Negative(rhs)) => Negative(lhs.saturating_add(rhs)), + (Positive(lhs), Negative(rhs)) => + if lhs.0 >= rhs.0 { + Positive(lhs.saturating_sub(rhs)) + } else { + Negative(rhs.saturating_sub(lhs)) + }, + (Negative(lhs), Positive(rhs)) => + if lhs.0 > rhs.0 { + Negative(lhs.saturating_sub(rhs)) + } else { + Positive(rhs.saturating_sub(lhs)) + }, + } + } + + /// This is essentially a saturating signed sub. + pub fn saturating_sub(&self, rhs: &Self) -> Self { + use SignedGas::*; + match (self, rhs) { + (Positive(lhs), Negative(rhs)) => Positive(lhs.saturating_add(rhs)), + (Negative(lhs), Positive(rhs)) => Negative(lhs.saturating_add(rhs)), + (Positive(lhs), Positive(rhs)) => + if lhs.0 >= rhs.0 { + Positive(lhs.saturating_sub(rhs)) + } else { + Negative(rhs.saturating_sub(lhs)) + }, + (Negative(lhs), Negative(rhs)) => + if lhs.0 > rhs.0 { + Negative(lhs.saturating_sub(rhs)) + } else { + Positive(rhs.saturating_sub(lhs)) + }, + } + } + + /// transform a storage deposit into a gas value and treat a charge as a positive number + pub fn from_adjusted_deposit_charge(deposit: &StorageDeposit>) -> Self { + use SignedGas::*; + + let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); + match deposit { + StorageDeposit::Charge(amount) => + Positive(InternalGas(multiplier.saturating_mul_int(*amount))), + StorageDeposit::Refund(amount) if *amount == Default::default() => + Positive(InternalGas(*amount)), + StorageDeposit::Refund(amount) => + Negative(InternalGas(multiplier.saturating_mul_int(*amount))), + } + } + + /// Return the balance of the `SignedGas` if it is `Positive`, otherwise return `None` + pub fn as_positive(&self) -> Option>> { + use SignedGas::*; + + match self { + Positive(amount) => Some(amount.clone()), + Negative(_amount) => None, + } + } +} diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index f81a69ba5a2e5..110593928a2de 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -17,10 +17,12 @@ use super::{ BalanceOf, CallResources, Config, DispatchError, Error, EthTxInfo, FixedPointNumber, FixedU128, - FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, Saturating, SignedGas, - State, StorageDeposit, TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, + FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, State, StorageDeposit, + TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, +}; +use crate::{ + limits::SOLIDITY_CALL_STIPEND, metering::weight::Token, vm::evm::EVMGas, InternalGas, SignedGas, }; -use crate::{limits::SOLIDITY_CALL_STIPEND, metering::weight::Token, vm::evm::EVMGas}; use core::marker::PhantomData; fn determine_call_stipend() -> Weight { @@ -104,10 +106,15 @@ pub mod substrate_execution { // then cap that gas by the requested `gas`. Distribute the capped gas // back into weight and deposit portions using the same ratio so that // the nested frame receives proportional limits. - let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); - let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(deposit_left); - let gas_left = weight_gas.saturating_add(deposit_gas); + let weight_gas = InternalGas::from_weight_fee( + T::FeeInfo::weight_to_fee_average(&weight_left), + ); + let deposit_gas = InternalGas::from_weight_fee( + T::FeeInfo::next_fee_multiplier_reciprocal() + .saturating_mul_int(deposit_left), + ); + let gas_left = + (weight_gas.saturating_add(&deposit_gas)).into_external_gas::(); let gas_limit = gas_left.min(*gas); let ratio = if gas_left.is_zero() { @@ -163,14 +170,18 @@ pub mod substrate_execution { /// /// Converts the remaining weight and deposit into their gas-equivalents (via `FeeInfo`) and /// returns the sum. Returns `None` if either component there is not enough as left - pub fn eth_gas_left(meter: &ResourceMeter) -> Option> { - match (meter.weight_left(), meter.deposit_left()) { + pub fn eth_gas_left( + meter: &ResourceMeter, + ) -> Option>> { + match (weight_left(meter), deposit_left(meter)) { (Some(weight_left), Some(deposit_left)) => { - let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); - let deposit_gas = - T::FeeInfo::next_fee_multiplier_reciprocal().saturating_mul_int(deposit_left); + let weight_gas = + InternalGas::from_weight_fee(T::FeeInfo::weight_to_fee_average(&weight_left)); + let deposit_gas = InternalGas::from_weight_fee( + T::FeeInfo::next_fee_multiplier_reciprocal().saturating_mul_int(deposit_left), + ); - Some(weight_gas.saturating_add(deposit_gas)) + Some(weight_gas.saturating_add(&deposit_gas)) }, _ => None, } @@ -212,17 +223,10 @@ pub mod substrate_execution { let total_consumed_deposit = meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); - let consumed_weight_gas = T::FeeInfo::weight_to_fee_average(&total_consumed_weight); - - let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); - let consumed_deposit_gas = match total_consumed_deposit { - StorageDeposit::Charge(amount) => - SignedGas::Positive(multiplier.saturating_mul_int(amount)), - StorageDeposit::Refund(amount) => - SignedGas::Negative(multiplier.saturating_mul_int(amount)), - }; + let consumed_weight_fee = T::FeeInfo::weight_to_fee_average(&total_consumed_weight); + let consumed_deposit_gas = SignedGas::from_adjusted_deposit_charge(&total_consumed_deposit); - consumed_deposit_gas.saturating_add(&SignedGas::Positive(consumed_weight_gas)) + consumed_deposit_gas.saturating_add(&SignedGas::from_weight_fee(consumed_weight_fee)) } /// Compute the gas (signed) during the lifetime of this meter for Substrate-style execution. @@ -233,22 +237,17 @@ pub mod substrate_execution { let total_consumed_weight = meter.total_consumed_weight_before.saturating_add(self_consumed_weight); - let consumed_weight_gas_before = SignedGas::Positive(T::FeeInfo::weight_to_fee_average( - &meter.total_consumed_weight_before, - )); + let consumed_weight_gas_before = SignedGas::from_weight_fee( + T::FeeInfo::weight_to_fee_average(&meter.total_consumed_weight_before), + ); let consumed_weight_gas = - SignedGas::Positive(T::FeeInfo::weight_to_fee_average(&total_consumed_weight)); + SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee_average(&total_consumed_weight)); let self_consumed_weight_gas = consumed_weight_gas.saturating_sub(&consumed_weight_gas_before); - let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); - let self_consumed_deposit_gas = match self_consumed_deposit { - StorageDeposit::Charge(amount) => - SignedGas::Positive(multiplier.saturating_mul_int(amount)), - StorageDeposit::Refund(amount) => - SignedGas::Negative(multiplier.saturating_mul_int(amount)), - }; + let self_consumed_deposit_gas = + SignedGas::from_adjusted_deposit_charge(&self_consumed_deposit); self_consumed_deposit_gas.saturating_add(&self_consumed_weight_gas) } @@ -271,7 +270,7 @@ pub mod ethereum_execution { let meter = TransactionMeter { weight: WeightMeter::new(maybe_weight_limit, None), deposit: RootStorageMeter::new(None), - max_total_gas: SignedGas::Positive(eth_gas_limit), + max_total_gas: SignedGas::from_external_gas(eth_gas_limit), total_consumed_weight_before: Default::default(), total_consumed_deposit_before: Default::default(), transaction_limits: TransactionLimits::EthereumGas { @@ -340,8 +339,8 @@ pub mod ethereum_execution { }; let deposit_left = { - let unbounded_deposit_left: BalanceOf = - T::FeeInfo::next_fee_multiplier().saturating_mul_int(gas_left); + let unbounded_deposit_left = gas_left.into_adjusted_deposit::(); + match meter.deposit.limit { Some(deposit_limit) => unbounded_deposit_left.min( self_consumed_deposit @@ -362,6 +361,8 @@ pub mod ethereum_execution { ), CallResources::Ethereum { gas, add_stipend } => { + let internal_gas_limit = InternalGas::from_external_gas::(*gas); + let (gas_limit, stipend) = if *add_stipend { let weight_stipend = determine_call_stipend::(); if weight_left.any_lt(weight_stipend) { @@ -369,11 +370,13 @@ pub mod ethereum_execution { } ( - gas.saturating_add(T::FeeInfo::weight_to_fee(&weight_stipend)), + internal_gas_limit.saturating_add(&InternalGas::from_weight_fee( + T::FeeInfo::weight_to_fee(&weight_stipend), + )), Some(weight_stipend), ) } else { - (*gas, None) + (internal_gas_limit, None) }; ( @@ -428,7 +431,7 @@ pub mod ethereum_execution { pub fn eth_gas_left( meter: &ResourceMeter, eth_tx_info: &EthTxInfo, - ) -> Option> { + ) -> Option>> { let self_consumed_weight = meter.weight.weight_consumed(); let self_consumed_deposit = meter.deposit.consumed(); @@ -473,8 +476,8 @@ pub mod ethereum_execution { meter: &ResourceMeter, eth_tx_info: &EthTxInfo, ) -> Option> { - let eth_gas_left = eth_gas_left(meter, eth_tx_info)?; - let deposit_left = T::FeeInfo::next_fee_multiplier().saturating_mul_int(eth_gas_left); + let gas_left = eth_gas_left(meter, eth_tx_info)?; + let deposit_left = gas_left.into_adjusted_deposit::(); Some(match meter.deposit.limit { Some(deposit_limit) => { diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 28644443e854f..65709ffb0b0d0 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod gas; pub mod math; pub mod storage; pub mod weight; @@ -30,7 +31,7 @@ use frame_support::{DebugNoBound, DefaultNoBound}; use num_traits::Zero; use core::{fmt::Debug, marker::PhantomData, ops::ControlFlow}; -use sp_runtime::{FixedPointNumber, Saturating, Weight}; +use sp_runtime::{FixedPointNumber, Weight}; use storage::{DepositOf, Diff, GenericMeter as GenericStorageMeter, Meter as RootStorageMeter}; use weight::{ChargedAmount, Token, WeightMeter}; @@ -126,6 +127,7 @@ impl ResourceMeter { log::trace!( target: LOG_TARGET, "Creating nested meter from parent: \ + limit={limit:?}, \ weight_left={:?}, \ deposit_left={:?}, \ weight_consumed={:?}, \ @@ -333,12 +335,14 @@ impl ResourceMeter { /// - For substrate mode: converts weight+deposit to gas equivalent /// Returns None if resources are exhausted or conversion fails. pub fn eth_gas_left(&self) -> Option> { - match &self.transaction_limits { + let internal_gas = match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => math::ethereum_execution::eth_gas_left(self, eth_tx_info), TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::eth_gas_left(self), - } + }?; + + Some(internal_gas.into_external_gas::()) } /// Get remaining weight available. @@ -385,7 +389,7 @@ impl ResourceMeter { math::substrate_execution::total_consumed_gas(self), }; - signed_gas.as_positive().unwrap_or_default() + signed_gas.as_positive().unwrap_or_default().into_external_gas::() } /// Get total weight consumed @@ -417,13 +421,15 @@ impl ResourceMeter { } /// Get the Ethereum gas that has been consumed during the lifetime of this meter - pub fn eth_gas_consumed(&self) -> SignedGas { - match &self.transaction_limits { + pub fn eth_gas_consumed(&self) -> BalanceOf { + let signed_gas = match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => math::ethereum_execution::eth_gas_consumed(self, eth_tx_info), TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::eth_gas_consumed(self), - } + }; + + signed_gas.as_positive().unwrap_or_default().into_external_gas::() } /// Determine and set the new effective weight limit of the weight meter. @@ -621,12 +627,12 @@ impl EthTxInfo { consumed_weight: &Weight, consumed_deposit: &DepositOf, ) -> SignedGas { - let deposit_gas = SignedGas::from_deposit_charge(consumed_deposit); - let fixed_fee_gas = SignedGas::Positive(T::FeeInfo::fixed_fee(self.encoded_len)); - let scaled_gas = (deposit_gas.saturating_add(&fixed_fee_gas)) - .scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()); + let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len); + let deposit_and_fixed_fee = + consumed_deposit.saturating_add(&DepositOf::::Charge(fixed_fee)); + let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee); - let weight_fee = SignedGas::Positive(T::FeeInfo::weight_to_fee( + let weight_gas = SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee( &consumed_weight.saturating_add(self.extra_weight), )); @@ -634,13 +640,11 @@ impl EthTxInfo { consumed_weight={consumed_weight:?}, \ consumed_deposit={consumed_deposit:?}, \ deposit_gas={deposit_gas:?}, \ - fixed_fee_gas={fixed_fee_gas:?}, \ - scaled_gas={scaled_gas:?}, \ - weight_fee={weight_fee:?}, \ + weight_gas={weight_gas:?}, \ extra_weight={:?}", self.extra_weight,); - scaled_gas.saturating_add(&weight_fee) + deposit_gas.saturating_add(&weight_gas) } /// Calculate maximal possible remaining weight that can be consumed given a particular gas @@ -653,17 +657,18 @@ impl EthTxInfo { total_weight_consumption: &Weight, total_deposit_consumption: &DepositOf, ) -> Option { - let numerator = SignedGas::from_deposit_charge(total_deposit_consumption) - .saturating_add(&SignedGas::Positive(T::FeeInfo::fixed_fee(self.encoded_len))); - let consumable_fee = max_total_gas.saturating_sub( - &numerator.scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()), - ); + let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len); + let deposit_and_fixed_fee = + total_deposit_consumption.saturating_add(&DepositOf::::Charge(fixed_fee)); + let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee); + + let consumable_fee = max_total_gas.saturating_sub(&deposit_gas); let SignedGas::Positive(consumable_fee) = consumable_fee else { return None; }; - T::FeeInfo::fee_to_weight(consumable_fee) + T::FeeInfo::fee_to_weight(consumable_fee.into_weight_fee()) .checked_sub(&total_weight_consumption.saturating_add(self.extra_weight)) } } diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs index aa0879b9715ae..03f7b65b48a60 100644 --- a/substrate/frame/revive/src/metering/tests.rs +++ b/substrate/frame/revive/src/metering/tests.rs @@ -89,6 +89,8 @@ fn max_consumed_deposit_integration_refunds_subframes(fixture_type: FixtureType) #[test] fn substrate_metering_initialization_works() { + let gas_scale = ::GasScale::get(); + let tests = vec![ (5_000_000_000, 1_000_000_000, 2_000, Some((2999999500, 1499999750, 11107, 599999900))), (6_000_000_000, 1_000_000_000, 2_000, Some((3999999500, 1999999750, 13728, 799999900))), @@ -110,14 +112,14 @@ fn substrate_metering_initialization_works() { EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit, + eth_gas_limit: eth_gas_limit / gas_scale, maybe_weight_limit: None, eth_tx_info, }); if let Some((gas_left, ref_time_left, proof_size_left, deposit_left)) = remaining { let transaction_meter = transaction_meter.unwrap(); - assert_eq!(gas_left, transaction_meter.eth_gas_left().unwrap()); + assert_eq!(gas_left / gas_scale, transaction_meter.eth_gas_left().unwrap()); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), transaction_meter.weight_left().unwrap() @@ -145,7 +147,7 @@ fn substrate_metering_initialization_works() { EthTxInfo::::new(100, Weight::from_parts(1_000_000_000, 2_000)); let transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit: 5_000_000_000, + eth_gas_limit: 5_000_000_000 / gas_scale, maybe_weight_limit: Some(Weight::from_parts( ref_time_limit, proof_size_limit, @@ -166,6 +168,7 @@ fn substrate_metering_initialization_works() { fn substrate_metering_charges_works() { use Charge::{D, W}; + let gas_scale = ::GasScale::get(); let tests = vec![ ( (5_000_000_000, 1_000_000_000, 2_000), @@ -236,7 +239,7 @@ fn substrate_metering_charges_works() { EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let mut transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit, + eth_gas_limit: eth_gas_limit / gas_scale, maybe_weight_limit: None, eth_tx_info, }) @@ -267,13 +270,16 @@ fn substrate_metering_charges_works() { )) = remaining { assert!(is_ok); - assert_eq!(gas_left, transaction_meter.eth_gas_left().unwrap()); + assert_eq!(gas_left / gas_scale, transaction_meter.eth_gas_left().unwrap()); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), transaction_meter.weight_left().unwrap() ); assert_eq!(deposit_left, transaction_meter.deposit_left().unwrap()); - assert_eq!(gas_consumed, transaction_meter.total_consumed_gas()); + assert_eq!( + gas_consumed / gas_scale, + transaction_meter.total_consumed_gas() + ); } else { assert!(!is_ok); } @@ -286,6 +292,7 @@ fn substrate_metering_charges_works() { fn substrate_nesting_works() { use CallResources::{Ethereum, NoLimits, WeightDeposit}; + let gas_scale = ::GasScale::get(); let tests = vec![ ( ((5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000i64), NoLimits), @@ -374,9 +381,9 @@ fn substrate_nesting_works() { ( ( (5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000), - Ethereum { gas: 2999992499, add_stipend: false }, + Ethereum { gas: 2999992490, add_stipend: false }, ), - Some((2999992499, 1499996249, 10107, 599998499, 2000007500)), + Some((2999992490, 1499996245, 10107, 599998498, 2000007500)), ), ( ( @@ -388,16 +395,9 @@ fn substrate_nesting_works() { ( ( (5_000_000_000, 1_000_000_000, 3000, 2000, 100000, -7000000000), - Ethereum { gas: 708617664, add_stipend: false }, - ), - Some((708617664, 18999997749, 1857, 141723532, 4291382335)), - ), - ( - ( - (5_000_000_000, 1_000_000_000, 3000, 2000, 100000, -7000000000), - Ethereum { gas: 708617666, add_stipend: false }, + Ethereum { gas: 708617660, add_stipend: false }, ), - Some((708617665, 18999997750, 1857, 141723533, 4291382335)), + Some((708617660, 18999997747, 1857, 141723532, 4291382335)), ), ( ( @@ -409,37 +409,37 @@ fn substrate_nesting_works() { ( ( (5_000_000_000, 1_000_000_000, 3000, 2000, 10106, 91452), - Ethereum { gas: 5, add_stipend: false }, + Ethereum { gas: 500, add_stipend: false }, ), Some((4, 1499769120, 0, 0, 4999999996)), ), ( ( (5_000_000_000, 1_000_000_000, 3000, 2000, 10106, 91452), - Ethereum { gas: 3, add_stipend: false }, + Ethereum { gas: 300, add_stipend: false }, ), - Some((3, 1499769119, 0, 0, 4999999996)), + Some((4, 1499769120, 0, 0, 4999999996)), ), ( ( (5_000_000_000, 1_000_000_000, 3000, 2000, 1010, 91452), - Ethereum { gas: 3, add_stipend: false }, + Ethereum { gas: 300, add_stipend: false }, ), - Some((3, 1, 1232, 0, 2000461760)), + Some((300, 150, 1232, 60, 2000461760)), ), ( ( (5_000_000_000, 1_000_000_000, 3000, 2000, 2242, 91452), - Ethereum { gas: 6, add_stipend: false }, + Ethereum { gas: 600, add_stipend: false }, ), - Some((6, 3, 0, 1, 2000461760)), + Some((600, 300, 0, 120, 2000461760)), ), ( ( (5_000_000_000, 1_000_000_000, 3000, 2000, 2243, 91452), - Ethereum { gas: 6, add_stipend: false }, + Ethereum { gas: 600, add_stipend: false }, ), - Some((6, 20891, 0, 1, 2000503536)), + Some((600, 21188, 0, 120, 2000503536)), ), ]; @@ -463,9 +463,9 @@ fn substrate_nesting_works() { let eth_tx_info = EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let mut transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit, + eth_gas_limit: eth_gas_limit / gas_scale, maybe_weight_limit: None, - eth_tx_info, + eth_tx_info: eth_tx_info.clone(), }) .unwrap(); @@ -483,7 +483,11 @@ fn substrate_nesting_works() { .charge_weight_token(TestToken(ref_time_charge, proof_size_charge)) .unwrap(); - let nested = transaction_meter.new_nested(&call_resource); + let scaled_call_resource = match call_resource { + Ethereum { gas, add_stipend } => Ethereum { gas: gas / gas_scale, add_stipend }, + _ => call_resource, + }; + let nested = transaction_meter.new_nested(&scaled_call_resource); if let Some(( gas_left, @@ -494,13 +498,13 @@ fn substrate_nesting_works() { )) = remaining { let nested = nested.unwrap(); - assert_eq!(gas_left, nested.eth_gas_left().unwrap()); + assert_eq!(gas_left / gas_scale, nested.eth_gas_left().unwrap()); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), nested.weight_left().unwrap() ); assert_eq!(deposit_left, nested.deposit_left().unwrap()); - assert_eq!(gas_consumed, nested.total_consumed_gas()); + assert_eq!(gas_consumed / gas_scale, nested.total_consumed_gas()); } else { assert!(nested.is_err()); } @@ -512,6 +516,7 @@ fn substrate_nesting_works() { fn substrate_nesting_charges_works() { use Charge::{D, W}; + let gas_scale = ::GasScale::get(); let tests = vec![ ( (5_000_000_000, 1_000_000_000, 2_000, 1000, 100, 1000i64, 1000), @@ -560,7 +565,7 @@ fn substrate_nesting_charges_works() { EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let mut transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit, + eth_gas_limit: eth_gas_limit / gas_scale, maybe_weight_limit: None, eth_tx_info, }) @@ -581,7 +586,10 @@ fn substrate_nesting_charges_works() { .unwrap(); let mut nested = transaction_meter - .new_nested(&CallResources::Ethereum { gas: gas_limit, add_stipend: false }) + .new_nested(&CallResources::Ethereum { + gas: gas_limit / gas_scale, + add_stipend: false, + }) .unwrap(); for (charge, remaining) in charges { @@ -609,13 +617,13 @@ fn substrate_nesting_charges_works() { )) = remaining { assert!(is_ok); - assert_eq!(gas_left, nested.eth_gas_left().unwrap()); + assert_eq!(gas_left / gas_scale, nested.eth_gas_left().unwrap()); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), nested.weight_left().unwrap() ); assert_eq!(deposit_left, nested.deposit_left().unwrap()); - assert_eq!(gas_consumed, nested.total_consumed_gas()); + assert_eq!(gas_consumed / gas_scale, nested.total_consumed_gas()); } else { assert!(!is_ok); } diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 0fbf0e2fd04d5..c5154d7055aea 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -23,13 +23,13 @@ use crate::{ }; use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{traits::tokens::Balance, weights::Weight, DebugNoBound}; +use frame_support::{traits::tokens::Balance, weights::Weight}; use pallet_revive_uapi::ReturnFlags; use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::{ traits::{One, Saturating, Zero}, - DispatchError, FixedPointNumber, FixedU128, RuntimeDebug, + DispatchError, RuntimeDebug, }; /// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and @@ -356,107 +356,6 @@ where } } -/// The type for Ethereum gas. We need to deal with negative and positive values and the structure -/// of this type resembles `StorageDeposit` but the enum variants have a more obvious name to avoid -/// confusion and errors -#[derive( - Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, DebugNoBound, TypeInfo, -)] -pub enum SignedGas { - /// Positive gas amount - Positive(BalanceOf), - /// Negative gas amount - Negative(BalanceOf), -} - -impl Default for SignedGas { - fn default() -> Self { - Self::Positive(Default::default()) - } -} - -impl SignedGas { - /// This is essentially a saturating signed add. - pub fn saturating_add(&self, rhs: &Self) -> Self { - use SignedGas::*; - match (self, rhs) { - (Positive(lhs), Positive(rhs)) => Positive(lhs.saturating_add(*rhs)), - (Negative(lhs), Negative(rhs)) => Negative(lhs.saturating_add(*rhs)), - (Positive(lhs), Negative(rhs)) => - if lhs >= rhs { - Positive(lhs.saturating_sub(*rhs)) - } else { - Negative(rhs.saturating_sub(*lhs)) - }, - (Negative(lhs), Positive(rhs)) => - if lhs > rhs { - Negative(lhs.saturating_sub(*rhs)) - } else { - Positive(rhs.saturating_sub(*lhs)) - }, - } - } - - /// This is essentially a saturating signed sub. - pub fn saturating_sub(&self, rhs: &Self) -> Self { - use SignedGas::*; - match (self, rhs) { - (Positive(lhs), Negative(rhs)) => Positive(lhs.saturating_add(*rhs)), - (Negative(lhs), Positive(rhs)) => Negative(lhs.saturating_add(*rhs)), - (Positive(lhs), Positive(rhs)) => - if lhs >= rhs { - Positive(lhs.saturating_sub(*rhs)) - } else { - Negative(rhs.saturating_sub(*lhs)) - }, - (Negative(lhs), Negative(rhs)) => - if lhs > rhs { - Negative(lhs.saturating_sub(*rhs)) - } else { - Positive(rhs.saturating_sub(*lhs)) - }, - } - } - - /// transform a storage deposit into a gas value and treat a charge as a positive number - pub fn from_deposit_charge(deposit: &StorageDeposit>) -> Self { - use SignedGas::*; - match deposit { - StorageDeposit::Charge(amount) => Positive(*amount), - StorageDeposit::Refund(amount) if *amount == Default::default() => Positive(*amount), - StorageDeposit::Refund(amount) => Negative(*amount), - } - } - - /// transform a storage deposit into a gas value and treat a refund as a positive number - pub fn from_deposit_refund(deposit: &StorageDeposit>) -> Self { - use SignedGas::*; - match deposit { - StorageDeposit::Refund(amount) => Positive(*amount), - StorageDeposit::Charge(amount) if *amount == Default::default() => Positive(*amount), - StorageDeposit::Charge(amount) => Negative(*amount), - } - } - - /// Scale this scaled gas value by a `FixedU128` factor - pub fn scale_by_factor(&self, rhs: &FixedU128) -> Self { - use SignedGas::*; - match self { - Positive(amount) => Positive(rhs.saturating_mul_int(*amount)), - Negative(amount) => Negative(rhs.saturating_mul_int(*amount)), - } - } - - /// Return the balance of the `SignedGas` if it is `Positive`, otherwise return `None` - pub fn as_positive(&self) -> Option> { - use SignedGas::*; - match self { - Positive(amount) => Some(*amount), - Negative(_amount) => None, - } - } -} - /// `Stack` wide configuration options. pub struct ExecConfig { /// Indicates whether the account nonce should be incremented after instantiating a new diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 9d9f01287180a..843f16a5f5a3d 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -5279,6 +5279,7 @@ fn self_destruct_by_syscall_tracing_works() { description: &'static str, create_tracer: Box Tracer>, expected_trace_fn: Box) -> Trace>, + modify_trace_fn: Option Trace>>, } let test_cases = vec![ @@ -5291,12 +5292,12 @@ fn self_destruct_by_syscall_tracing_works() { to: addr, call_type: CallType::Call, value: Some(U256::zero()), - gas: U256::from(2499916585034u64), - gas_used: U256::from(94847106u64), + gas: 0.into(), + gas_used: 0.into(), calls: vec![CallTrace { from: addr, to: DJANGO_ADDR, - gas: U256::from(2497550684361u64), + gas: 0.into(), call_type: CallType::Selfdestruct, value: Some(Pallet::::convert_native_to_evm(100_000u64)), @@ -5305,6 +5306,14 @@ fn self_destruct_by_syscall_tracing_works() { ..Default::default() }) }), + modify_trace_fn: Some(Box::new(|mut actual_trace| { + if let Trace::Call(trace) = &mut actual_trace { + trace.gas = 0.into(); + trace.gas_used = 0.into(); + trace.calls[0].gas = 0.into(); + } + actual_trace + })), }, TestCase { description: "PrestateTracer (diff mode)", @@ -5354,6 +5363,7 @@ fn self_destruct_by_syscall_tracing_works() { let expected: PrestateTrace = serde_json::from_str(&json).unwrap(); Trace::Prestate(expected) }), + modify_trace_fn: None, }, TestCase { description: "PrestateTracer (prestate mode)", @@ -5398,10 +5408,11 @@ fn self_destruct_by_syscall_tracing_works() { let expected: PrestateTrace = serde_json::from_str(&json).unwrap(); Trace::Prestate(expected) }), + modify_trace_fn: None, }, ]; - for TestCase { description, create_tracer, expected_trace_fn } in test_cases { + for TestCase { description, create_tracer, expected_trace_fn, modify_trace_fn } in test_cases { ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); @@ -5417,14 +5428,17 @@ fn self_destruct_by_syscall_tracing_works() { builder::call(addr).build().unwrap(); }); - let trace = tracer.collect_trace(); + let mut trace = tracer.collect_trace().unwrap(); - let trace_wrapped = trace.map(|t| match t { + if let Some(modify_trace_fn) = modify_trace_fn { + trace = modify_trace_fn(trace); + } + let trace_wrapped = match trace { crate::evm::Trace::Call(ct) => Trace::Call(ct), crate::evm::Trace::Prestate(pt) => Trace::Prestate(pt), - }); + }; - assert_eq!(trace_wrapped, Some(expected_trace), "Trace mismatch for: {}", description); + assert_eq!(trace_wrapped, expected_trace, "Trace mismatch for: {}", description); }); } } From 5e98c439ed2e5f9ec441320e891c3fe8e7a87d7e Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 21:05:53 +0000 Subject: [PATCH 32/69] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --bump patch' --- prdoc/pr_10393.prdoc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 prdoc/pr_10393.prdoc diff --git a/prdoc/pr_10393.prdoc b/prdoc/pr_10393.prdoc new file mode 100644 index 0000000000000..7937ce997fbd4 --- /dev/null +++ b/prdoc/pr_10393.prdoc @@ -0,0 +1,21 @@ +title: Add configuration to set Ethereum gas scale +doc: +- audience: Runtime Dev + description: |- + This PR adds a new configuration parameter (`GasScale`) to pallet-revive that allows to change the scale of the Ethereum gas and of the Ethereum gas price. + + Before this PR, the Ethereum gas price is simply the next fee multiplier of pallet-transaction-payment multiplied by `NativeToEthRatio`. Thus, on Polkadot this is 100_000_000 when the multiplier has its default value of 1. + + The required gas of a transaction is its total cost divided by the gas price, where the total cost is the transaction fee and the storage deposit. + + This leads to a situation where the required gas for a transaction on revive is usually orders of magnitude larger than the required amount of gas on Ethereum. This can lead to issues with tools or systems that interact with revive and hard code expected gas amounts or upper limits of gas amounts. + + By changing `GasScale`, the gas price used in revive is multiplied by `GasScale`. For that reason the used/estimated gas amounts get lower by the same factor. + + ## Technical Details + Internally, revive uses exactly the same gas price and gas units as before. Only at the interface these amounts and prices get scaled by `GasScale`. In order to avoid confusion and errors, this PR introduces a new type called `InternalGas`, which represents the unscaled, internal gas units. +crates: +- name: revive-dev-runtime + bump: patch +- name: pallet-revive + bump: patch From 2b823ccfe9779295a025ded399d170a780c57b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:19:06 -0300 Subject: [PATCH 33/69] Add GasScale config to other chains --- cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs | 1 + cumulus/parachains/runtimes/testing/penpal/src/lib.rs | 1 + substrate/bin/node/runtime/src/lib.rs | 1 + 3 files changed, 3 insertions(+) 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 6ee07f6fe0a8a..ab02feb299726 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1214,6 +1214,7 @@ impl pallet_revive::Config for Runtime { type FeeInfo = pallet_revive::evm::fees::Info; type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; type DebugEnabled = ConstBool; + type GasScale = ConstU128<1000>; } parameter_types! { diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 7cfd9467592ae..794d40d5fb148 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -842,6 +842,7 @@ impl pallet_revive::Config for Runtime { type FeeInfo = pallet_revive::evm::fees::Info; type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; type DebugEnabled = ConstBool; + type GasScale = ConstU128<1000>; } impl pallet_sudo::Config for Runtime { diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 3a54a6bb09f13..539f5fccd9eb9 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1539,6 +1539,7 @@ impl pallet_revive::Config for Runtime { type FeeInfo = pallet_revive::evm::fees::Info; type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; type DebugEnabled = ConstBool; + type GasScale = ConstU128<1000>; } impl pallet_sudo::Config for Runtime { From 15b8c760a15f337768e9ef5930afc5697d63e7b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sat, 22 Nov 2025 18:32:16 -0300 Subject: [PATCH 34/69] Address clippy complaints --- substrate/frame/revive/src/metering/gas.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/metering/gas.rs b/substrate/frame/revive/src/metering/gas.rs index 3a554eddf6ffc..a0ad04499b669 100644 --- a/substrate/frame/revive/src/metering/gas.rs +++ b/substrate/frame/revive/src/metering/gas.rs @@ -163,7 +163,7 @@ impl SignedGas { use SignedGas::*; match self { - Positive(amount) => Some(amount.clone()), + Positive(amount) => Some(*amount), Negative(_amount) => None, } } From cb293901b069dcf2bc76c70883c88c296814a71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sun, 23 Nov 2025 08:53:00 -0300 Subject: [PATCH 35/69] Increase endowments on dev-node --- substrate/frame/revive/dev-node/runtime/src/lib.rs | 2 +- substrate/frame/revive/src/evm/runtime.rs | 10 +++++----- substrate/frame/revive/src/lib.rs | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index d28b6f64d9cbd..71a446aaa1fb6 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -71,7 +71,7 @@ pub mod genesis_config_presets { use alloc::{vec, vec::Vec}; use serde_json::Value; - pub const ENDOWMENT: Balance = 1_000_000_001 * DOLLARS; + pub const ENDOWMENT: Balance = 100_000_000_001 * DOLLARS; fn well_known_accounts() -> Vec { Sr25519Keyring::well_known() diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index b89cda9471f6c..b9421bfa7fca4 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -320,11 +320,11 @@ pub trait EthExtra { CreateCallMode::ExtrinsicExecution(encoded_len as u32, payload.to_vec()), )?; let storage_credit = ::Currency::withdraw( - &signer, - call_info.storage_deposit, - Precision::Exact, - Preservation::Preserve, - Fortitude::Polite, + &signer, + call_info.storage_deposit, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, ).map_err(|_| { log::debug!(target: LOG_TARGET, "Not enough balance to hold additional storage deposit of {:?}", call_info.storage_deposit); InvalidTransaction::Payment diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index b02fd61724480..fc995ca063ef5 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1609,6 +1609,7 @@ impl Pallet { let result = Self::run_guarded(try_call); log::trace!(target: LOG_TARGET, "Bare call ends: \ + result={result:?}, \ weight_consumed={:?}, \ weight_required={:?}, \ storage_deposit={:?}, \ From aec797045f5c06ee2fae7371fdadd3ae18757e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sun, 23 Nov 2025 10:05:38 -0300 Subject: [PATCH 36/69] Increase the GasScale on the dev node --- .github/workflows/tests-evm.yml | 2 +- substrate/frame/revive/dev-node/runtime/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 33ac4c49a63ea..73085d5a38fd4 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -51,7 +51,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: paritytech/revive-differential-tests - ref: a6e4932a08b1ca231e4a02ca6e54e08a53f0e786 + ref: b145f45cd3114fd378ce3d5031b127fb39cf9cbd path: revive-differential-tests submodules: recursive - name: Installing Retester diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index 71a446aaa1fb6..cfe833369c266 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -356,7 +356,7 @@ impl pallet_revive::Config for Runtime { type Time = Timestamp; type FeeInfo = FeeInfo; type DebugEnabled = ConstBool; - type GasScale = ConstU128<5000>; + type GasScale = ConstU128<50000>; } pallet_revive::impl_runtime_apis_plus_revive_traits!( From bd87e40356efb8c130b76212f2747c5f131d5f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sun, 23 Nov 2025 10:26:09 -0300 Subject: [PATCH 37/69] Increase endowments on dev-node again --- substrate/frame/revive/dev-node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index cfe833369c266..32276fa837809 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -71,7 +71,7 @@ pub mod genesis_config_presets { use alloc::{vec, vec::Vec}; use serde_json::Value; - pub const ENDOWMENT: Balance = 100_000_000_001 * DOLLARS; + pub const ENDOWMENT: Balance = 1_000_000_000_001 * DOLLARS; fn well_known_accounts() -> Vec { Sr25519Keyring::well_known() From e722626d3b4a6e356c23f24077204cfaaf235c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sun, 23 Nov 2025 12:13:59 -0300 Subject: [PATCH 38/69] Set standard differential test concurrency --- .github/workflows/tests-evm.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 33ac4c49a63ea..b990749a671ef 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -23,7 +23,7 @@ jobs: needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} if: ${{ needs.preflight.outputs.changes_rust }} - timeout-minutes: 90 + timeout-minutes: 60 container: image: ${{ needs.preflight.outputs.IMAGE }} permissions: @@ -71,7 +71,7 @@ jobs: --platform ${{ matrix.platform }} \ --concurrency.number-of-nodes 10 \ --concurrency.number-of-threads 10 \ - --concurrency.number-of-concurrent-tasks 10 \ + --concurrency.number-of-concurrent-tasks 1000 \ --working-directory ./workdir \ --revive-dev-node.consensus manual-seal-200 \ --revive-dev-node.path ./target/release/revive-dev-node \ From 43f54eb7a15e0d48aca8447bc4c59f130ce73827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Sun, 23 Nov 2025 20:49:10 -0300 Subject: [PATCH 39/69] Improve code consistency --- substrate/frame/revive/src/evm/runtime.rs | 10 +- substrate/frame/revive/src/exec.rs | 4 +- substrate/frame/revive/src/lib.rs | 17 +-- substrate/frame/revive/src/metering/gas.rs | 148 ++++++++++++++++++++ substrate/frame/revive/src/metering/math.rs | 130 +++++++++-------- substrate/frame/revive/src/metering/mod.rs | 60 ++++---- substrate/frame/revive/src/primitives.rs | 105 +------------- 7 files changed, 255 insertions(+), 219 deletions(-) create mode 100644 substrate/frame/revive/src/metering/gas.rs diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index b89cda9471f6c..b9421bfa7fca4 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -320,11 +320,11 @@ pub trait EthExtra { CreateCallMode::ExtrinsicExecution(encoded_len as u32, payload.to_vec()), )?; let storage_credit = ::Currency::withdraw( - &signer, - call_info.storage_deposit, - Precision::Exact, - Preservation::Preserve, - Fortitude::Polite, + &signer, + call_info.storage_deposit, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, ).map_err(|_| { log::debug!(target: LOG_TARGET, "Not enough balance to hold additional storage deposit of {:?}", call_info.storage_deposit); InvalidTransaction::Payment diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 1ff075f0a0cf8..b6e02881b78d0 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1439,7 +1439,7 @@ where let gas_consumed = if is_first_frame { frame_meter.total_consumed_gas().into() } else { - frame_meter.eth_gas_consumed().as_positive().unwrap_or_default().into() + frame_meter.eth_gas_consumed().into() }; match &output { @@ -1458,7 +1458,7 @@ where let gas_consumed = if is_first_frame { frame_meter.total_consumed_gas().into() } else { - frame_meter.eth_gas_consumed().as_positive().unwrap_or_default().into() + frame_meter.eth_gas_consumed().into() }; tracer.exit_child_span_with_error(error.into(), gas_consumed); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 94d3cf50ce717..ce70aea8d4160 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -101,8 +101,8 @@ pub use crate::{ }, exec::{CallResources, DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin}, metering::{ - weight::Token as WeightToken, EthTxInfo, FrameMeter, ResourceMeter, TransactionLimits, - TransactionMeter, + gas::SignedGas, weight::Token as WeightToken, EthTxInfo, FrameMeter, ResourceMeter, + TransactionLimits, TransactionMeter, }, pallet::{genesis, *}, storage::{AccountInfo, ContractInfo}, @@ -1602,6 +1602,7 @@ impl Pallet { let result = Self::run_guarded(try_call); log::trace!(target: LOG_TARGET, "Bare call ends: \ + result={result:?}, \ weight_consumed={:?}, \ weight_required={:?}, \ storage_deposit={:?}, \ @@ -1959,12 +1960,12 @@ impl Pallet { )))?; } - // We add `1` to account for the potential rounding error of the multiplication. - // Returning a larger value here just increases the the pre-dispatch weight. - let eth_gas: U256 = T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(transaction_fee.saturating_add(dry_run.max_storage_deposit)) - .saturating_add(1_u32.into()) - .into(); + let total_cost = transaction_fee.saturating_add(dry_run.max_storage_deposit); + let total_cost_wei = Pallet::::convert_native_to_evm(total_cost); + let (mut eth_gas, rest) = total_cost_wei.div_mod(base_fee); + if !rest.is_zero() { + eth_gas = eth_gas.saturating_add(1_u32.into()); + } log::debug!(target: LOG_TARGET, "\ dry_run_eth_transact finished: \ diff --git a/substrate/frame/revive/src/metering/gas.rs b/substrate/frame/revive/src/metering/gas.rs new file mode 100644 index 0000000000000..bd4081baf5655 --- /dev/null +++ b/substrate/frame/revive/src/metering/gas.rs @@ -0,0 +1,148 @@ +// 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. + +use crate::{evm::fees::InfoT, BalanceOf, Config, StorageDeposit}; +use frame_support::DebugNoBound; +use sp_runtime::{FixedPointNumber, Saturating}; + +/// The type for negative and positive gas amounts +/// +/// The structure of this type resembles `StorageDeposit` but the enum variants have a more obvious +/// name to avoid confusion and errors +#[derive(Clone, Eq, PartialEq, DebugNoBound)] +pub enum SignedGas { + /// Positive gas amount + Positive(BalanceOf), + /// Negative gas amount + /// Invariant: BalanceOf is never 0 for `Negative` + Negative(BalanceOf), +} + +use SignedGas::{Negative, Positive}; + +impl Default for SignedGas { + fn default() -> Self { + Self::Positive(Default::default()) + } +} + +impl SignedGas { + /// Transform a weight fee into a gas amount. + pub fn from_weight_fee(weight_fee: BalanceOf) -> Self { + Self::Positive(weight_fee) + } + + /// Transform an Ethereum gas amount coming from outside the metering system and transform into + /// the internally used SignedGas. + pub fn from_ethereum_gas(gas: BalanceOf) -> Self { + Self::Positive(gas) + } + + /// Transform a storage deposit into a gas value. The value will be adjusted by dividing it + /// through the next fee multiplier. Charges are treated as a positive numbers and refunds as + /// negative numbers. + pub fn from_adjusted_deposit_charge(deposit: &StorageDeposit>) -> Self { + let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); + + match deposit { + StorageDeposit::Charge(amount) => Positive(multiplier.saturating_mul_int(*amount)), + StorageDeposit::Refund(amount) if *amount == Default::default() => Positive(*amount), + StorageDeposit::Refund(amount) => Negative(multiplier.saturating_mul_int(*amount)), + } + } + + /// Transform the gas amount to a weight fee amount + /// Returns None if the gas amount is negative. + pub fn to_weight_fee(&self) -> Option> { + match self { + Positive(amount) => Some(*amount), + Negative(..) => None, + } + } + + /// Transform the gas amount to an Ethereum gas amount usable for external purposes + /// Returns None if the gas amount is negative. + pub fn to_ethereum_gas(&self) -> Option> { + match self { + Positive(amount) => Some(*amount), + Negative(..) => None, + } + } + + /// Transform the gas amount to a deposit charge. The amount will be adjusted by multiplying it + /// with the next fee multiplier. + /// Returns None if the gas amount is negative. + pub fn to_adjusted_deposit_charge(&self) -> Option> { + match self { + Positive(amount) => { + let multiplier = T::FeeInfo::next_fee_multiplier(); + Some(multiplier.saturating_mul_int(*amount)) + }, + _ => None, + } + } + + /// This is essentially a saturating signed add. + pub fn saturating_add(&self, rhs: &Self) -> Self { + match (self, rhs) { + (Positive(lhs), Positive(rhs)) => Positive(lhs.saturating_add(*rhs)), + (Negative(lhs), Negative(rhs)) => Negative(lhs.saturating_add(*rhs)), + (Positive(lhs), Negative(rhs)) => + if lhs >= rhs { + Positive(lhs.saturating_sub(*rhs)) + } else { + Negative(rhs.saturating_sub(*lhs)) + }, + (Negative(lhs), Positive(rhs)) => + if lhs > rhs { + Negative(lhs.saturating_sub(*rhs)) + } else { + Positive(rhs.saturating_sub(*lhs)) + }, + } + } + + /// This is essentially a saturating signed sub. + pub fn saturating_sub(&self, rhs: &Self) -> Self { + match (self, rhs) { + (Positive(lhs), Negative(rhs)) => Positive(lhs.saturating_add(*rhs)), + (Negative(lhs), Positive(rhs)) => Negative(lhs.saturating_add(*rhs)), + (Positive(lhs), Positive(rhs)) => + if lhs >= rhs { + Positive(lhs.saturating_sub(*rhs)) + } else { + Negative(rhs.saturating_sub(*lhs)) + }, + (Negative(lhs), Negative(rhs)) => + if lhs > rhs { + Negative(lhs.saturating_sub(*rhs)) + } else { + Positive(rhs.saturating_sub(*lhs)) + }, + } + } + + // Determine the minimum of two signed gas values. + pub fn min(&self, other: &Self) -> Self { + match (self, other) { + (Positive(_), Negative(rhs)) => Negative(*rhs), + (Negative(lhs), Positive(_)) => Negative(*lhs), + (Positive(lhs), Positive(rhs)) => Positive((*lhs).min(*rhs)), + (Negative(lhs), Negative(rhs)) => Negative((*lhs).max(*rhs)), + } + } +} diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index f81a69ba5a2e5..8177a9296ca67 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -17,10 +17,10 @@ use super::{ BalanceOf, CallResources, Config, DispatchError, Error, EthTxInfo, FixedPointNumber, FixedU128, - FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, Saturating, SignedGas, - State, StorageDeposit, TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, + FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, State, StorageDeposit, + TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, }; -use crate::{limits::SOLIDITY_CALL_STIPEND, metering::weight::Token, vm::evm::EVMGas}; +use crate::{limits::SOLIDITY_CALL_STIPEND, metering::weight::Token, vm::evm::EVMGas, SignedGas}; use core::marker::PhantomData; fn determine_call_stipend() -> Weight { @@ -83,14 +83,14 @@ pub mod substrate_execution { let weight_left = meter .weight .weight_limit - .expect("Weight limits all always defined for WeightAndDeposit; qed") + .expect("Weight limits are always defined for WeightAndDeposit; qed") .checked_sub(&self_consumed_weight) .ok_or(>::OutOfGas)?; let deposit_limit = meter .deposit .limit - .expect("Deposit limits all always defined for WeightAndDeposit; qed"); + .expect("Deposit limits are always defined for WeightAndDeposit; qed"); let deposit_left = self_consumed_deposit .available(&deposit_limit) .ok_or(>::StorageDepositLimitExhausted)?; @@ -104,18 +104,26 @@ pub mod substrate_execution { // then cap that gas by the requested `gas`. Distribute the capped gas // back into weight and deposit portions using the same ratio so that // the nested frame receives proportional limits. - let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); - let deposit_gas = T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(deposit_left); - let gas_left = weight_gas.saturating_add(deposit_gas); - let gas_limit = gas_left.min(*gas); + let weight_gas_left = SignedGas::::from_weight_fee( + T::FeeInfo::weight_to_fee_average(&weight_left), + ); + let deposit_gas_left = SignedGas::::from_adjusted_deposit_charge( + &StorageDeposit::Charge(deposit_left), + ); + let Some(remaining_gas) = + (weight_gas_left.saturating_add(&deposit_gas_left)).to_ethereum_gas() + else { + return Err(>::OutOfGas.into()); + }; + + let gas_limit = remaining_gas.min(*gas); - let ratio = if gas_left.is_zero() { + let ratio = if remaining_gas.is_zero() { FixedU128::one() } else { FixedU128::from_rational( gas_limit.saturated_into(), - gas_left.saturated_into(), + remaining_gas.saturated_into(), ) }; @@ -163,14 +171,17 @@ pub mod substrate_execution { /// /// Converts the remaining weight and deposit into their gas-equivalents (via `FeeInfo`) and /// returns the sum. Returns `None` if either component there is not enough as left - pub fn eth_gas_left(meter: &ResourceMeter) -> Option> { - match (meter.weight_left(), meter.deposit_left()) { + pub fn gas_left(meter: &ResourceMeter) -> Option> { + match (weight_left(meter), deposit_left(meter)) { (Some(weight_left), Some(deposit_left)) => { - let weight_gas = T::FeeInfo::weight_to_fee_average(&weight_left); - let deposit_gas = - T::FeeInfo::next_fee_multiplier_reciprocal().saturating_mul_int(deposit_left); - - Some(weight_gas.saturating_add(deposit_gas)) + let weight_gas_left = SignedGas::::from_weight_fee( + T::FeeInfo::weight_to_fee_average(&weight_left), + ); + let deposit_gas_left = SignedGas::::from_adjusted_deposit_charge( + &StorageDeposit::Charge(deposit_left), + ); + + Some(weight_gas_left.saturating_add(&deposit_gas_left)) }, _ => None, } @@ -183,7 +194,7 @@ pub mod substrate_execution { let weight_limit = meter .weight .weight_limit - .expect("Weight limits all always defined for WeightAndDeposit; qed"); + .expect("Weight limits are always defined for WeightAndDeposit; qed"); weight_limit.checked_sub(&meter.weight.weight_consumed()) } @@ -195,7 +206,7 @@ pub mod substrate_execution { let deposit_limit = meter .deposit .limit - .expect("Deposit limits all always defined for WeightAndDeposit; qed"); + .expect("Deposit limits are always defined for WeightAndDeposit; qed"); meter.deposit.consumed().available(&deposit_limit) } @@ -212,17 +223,11 @@ pub mod substrate_execution { let total_consumed_deposit = meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); - let consumed_weight_gas = T::FeeInfo::weight_to_fee_average(&total_consumed_weight); - - let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); - let consumed_deposit_gas = match total_consumed_deposit { - StorageDeposit::Charge(amount) => - SignedGas::Positive(multiplier.saturating_mul_int(amount)), - StorageDeposit::Refund(amount) => - SignedGas::Negative(multiplier.saturating_mul_int(amount)), - }; + let consumed_weight_gas = + SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee_average(&total_consumed_weight)); + let consumed_deposit_gas = SignedGas::from_adjusted_deposit_charge(&total_consumed_deposit); - consumed_deposit_gas.saturating_add(&SignedGas::Positive(consumed_weight_gas)) + consumed_deposit_gas.saturating_add(&consumed_weight_gas) } /// Compute the gas (signed) during the lifetime of this meter for Substrate-style execution. @@ -233,22 +238,17 @@ pub mod substrate_execution { let total_consumed_weight = meter.total_consumed_weight_before.saturating_add(self_consumed_weight); - let consumed_weight_gas_before = SignedGas::Positive(T::FeeInfo::weight_to_fee_average( - &meter.total_consumed_weight_before, - )); + let consumed_weight_gas_before = SignedGas::from_weight_fee( + T::FeeInfo::weight_to_fee_average(&meter.total_consumed_weight_before), + ); let consumed_weight_gas = - SignedGas::Positive(T::FeeInfo::weight_to_fee_average(&total_consumed_weight)); + SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee_average(&total_consumed_weight)); let self_consumed_weight_gas = consumed_weight_gas.saturating_sub(&consumed_weight_gas_before); - let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); - let self_consumed_deposit_gas = match self_consumed_deposit { - StorageDeposit::Charge(amount) => - SignedGas::Positive(multiplier.saturating_mul_int(amount)), - StorageDeposit::Refund(amount) => - SignedGas::Negative(multiplier.saturating_mul_int(amount)), - }; + let self_consumed_deposit_gas = + SignedGas::from_adjusted_deposit_charge(&self_consumed_deposit); self_consumed_deposit_gas.saturating_add(&self_consumed_weight_gas) } @@ -271,7 +271,7 @@ pub mod ethereum_execution { let meter = TransactionMeter { weight: WeightMeter::new(maybe_weight_limit, None), deposit: RootStorageMeter::new(None), - max_total_gas: SignedGas::Positive(eth_gas_limit), + max_total_gas: SignedGas::from_ethereum_gas(eth_gas_limit), total_consumed_weight_before: Default::default(), total_consumed_deposit_before: Default::default(), transaction_limits: TransactionLimits::EthereumGas { @@ -316,11 +316,7 @@ pub mod ethereum_execution { let total_gas_consumption = eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit); - let Some(gas_left) = - meter.max_total_gas.saturating_sub(&total_gas_consumption).as_positive() - else { - return Err(>::OutOfGas.into()); - }; + let remaining_gas = meter.max_total_gas.saturating_sub(&total_gas_consumption); let weight_left = { let unbounded_weight_left = eth_tx_info @@ -340,8 +336,10 @@ pub mod ethereum_execution { }; let deposit_left = { - let unbounded_deposit_left: BalanceOf = - T::FeeInfo::next_fee_multiplier().saturating_mul_int(gas_left); + let Some(unbounded_deposit_left) = remaining_gas.to_adjusted_deposit_charge() else { + return Err(>::OutOfGas.into()); + }; + match meter.deposit.limit { Some(deposit_limit) => unbounded_deposit_left.min( self_consumed_deposit @@ -355,13 +353,15 @@ pub mod ethereum_execution { let (nested_gas_limit, nested_weight_limit, nested_deposit_limit, stipend) = { match limit { CallResources::NoLimits => ( - gas_left, + remaining_gas, if meter.weight.weight_limit.is_none() { None } else { Some(weight_left) }, if meter.deposit.limit.is_none() { None } else { Some(deposit_left) }, None, ), CallResources::Ethereum { gas, add_stipend } => { + let gas_limit = SignedGas::from_ethereum_gas(*gas); + let (gas_limit, stipend) = if *add_stipend { let weight_stipend = determine_call_stipend::(); if weight_left.any_lt(weight_stipend) { @@ -369,15 +369,17 @@ pub mod ethereum_execution { } ( - gas.saturating_add(T::FeeInfo::weight_to_fee(&weight_stipend)), + gas_limit.saturating_add(&SignedGas::::from_weight_fee( + T::FeeInfo::weight_to_fee(&weight_stipend), + )), Some(weight_stipend), ) } else { - (*gas, None) + (gas_limit, None) }; ( - gas_left.min(gas_limit), + remaining_gas.min(&gas_limit), if meter.weight.weight_limit.is_none() { None } else { Some(weight_left) }, if meter.deposit.limit.is_none() { None } else { Some(deposit_left) }, stipend, @@ -394,14 +396,10 @@ pub mod ethereum_execution { .saturating_add(&StorageDeposit::Charge(nested_deposit_limit)), ); - let Some(gas_limit) = - new_max_total_gas.saturating_sub(&total_gas_consumption).as_positive() - else { - return Err(>::OutOfGas.into()); - }; + let gas_limit = new_max_total_gas.saturating_sub(&total_gas_consumption); ( - gas_left.min(gas_limit), + remaining_gas.min(&gas_limit), Some(nested_weight_limit), Some(nested_deposit_limit), None, @@ -410,8 +408,7 @@ pub mod ethereum_execution { } }; - let nested_max_total_gas = - total_gas_consumption.saturating_add(&SignedGas::Positive(nested_gas_limit)); + let nested_max_total_gas = total_gas_consumption.saturating_add(&nested_gas_limit); Ok(FrameMeter:: { weight: WeightMeter::new(nested_weight_limit, stipend), @@ -425,10 +422,10 @@ pub mod ethereum_execution { } /// Compute remaining ethereum gas for an Ethereum-style execution. - pub fn eth_gas_left( + pub fn gas_left( meter: &ResourceMeter, eth_tx_info: &EthTxInfo, - ) -> Option> { + ) -> Option> { let self_consumed_weight = meter.weight.weight_consumed(); let self_consumed_deposit = meter.deposit.consumed(); @@ -440,7 +437,7 @@ pub mod ethereum_execution { let total_gas_consumption = eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit); - meter.max_total_gas.saturating_sub(&total_gas_consumption).as_positive() + Some(meter.max_total_gas.saturating_sub(&total_gas_consumption)) } /// Return the remaining weight available to a nested frame under Ethereum-style execution. @@ -473,8 +470,7 @@ pub mod ethereum_execution { meter: &ResourceMeter, eth_tx_info: &EthTxInfo, ) -> Option> { - let eth_gas_left = eth_gas_left(meter, eth_tx_info)?; - let deposit_left = T::FeeInfo::next_fee_multiplier().saturating_mul_int(eth_gas_left); + let deposit_left = gas_left(meter, eth_tx_info)?.to_adjusted_deposit_charge()?; Some(match meter.deposit.limit { Some(deposit_limit) => { diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 28644443e854f..93e15d0e905c4 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod gas; pub mod math; pub mod storage; pub mod weight; @@ -30,7 +31,7 @@ use frame_support::{DebugNoBound, DefaultNoBound}; use num_traits::Zero; use core::{fmt::Debug, marker::PhantomData, ops::ControlFlow}; -use sp_runtime::{FixedPointNumber, Saturating, Weight}; +use sp_runtime::{FixedPointNumber, Weight}; use storage::{DepositOf, Diff, GenericMeter as GenericStorageMeter, Meter as RootStorageMeter}; use weight::{ChargedAmount, Token, WeightMeter}; @@ -126,6 +127,7 @@ impl ResourceMeter { log::trace!( target: LOG_TARGET, "Creating nested meter from parent: \ + limit={limit:?}, \ weight_left={:?}, \ deposit_left={:?}, \ weight_consumed={:?}, \ @@ -333,12 +335,13 @@ impl ResourceMeter { /// - For substrate mode: converts weight+deposit to gas equivalent /// Returns None if resources are exhausted or conversion fails. pub fn eth_gas_left(&self) -> Option> { - match &self.transaction_limits { + let gas_left = match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => - math::ethereum_execution::eth_gas_left(self, eth_tx_info), - TransactionLimits::WeightAndDeposit { .. } => - math::substrate_execution::eth_gas_left(self), - } + math::ethereum_execution::gas_left(self, eth_tx_info), + TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::gas_left(self), + }?; + + gas_left.to_ethereum_gas() } /// Get remaining weight available. @@ -385,7 +388,7 @@ impl ResourceMeter { math::substrate_execution::total_consumed_gas(self), }; - signed_gas.as_positive().unwrap_or_default() + signed_gas.to_ethereum_gas().unwrap_or_default() } /// Get total weight consumed @@ -417,13 +420,15 @@ impl ResourceMeter { } /// Get the Ethereum gas that has been consumed during the lifetime of this meter - pub fn eth_gas_consumed(&self) -> SignedGas { - match &self.transaction_limits { + pub fn eth_gas_consumed(&self) -> BalanceOf { + let signed_gas = match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => math::ethereum_execution::eth_gas_consumed(self, eth_tx_info), TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::eth_gas_consumed(self), - } + }; + + signed_gas.to_ethereum_gas().unwrap_or_default() } /// Determine and set the new effective weight limit of the weight meter. @@ -621,26 +626,16 @@ impl EthTxInfo { consumed_weight: &Weight, consumed_deposit: &DepositOf, ) -> SignedGas { - let deposit_gas = SignedGas::from_deposit_charge(consumed_deposit); - let fixed_fee_gas = SignedGas::Positive(T::FeeInfo::fixed_fee(self.encoded_len)); - let scaled_gas = (deposit_gas.saturating_add(&fixed_fee_gas)) - .scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()); + let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len); + let deposit_and_fixed_fee = + consumed_deposit.saturating_add(&DepositOf::::Charge(fixed_fee)); + let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee); - let weight_fee = SignedGas::Positive(T::FeeInfo::weight_to_fee( + let weight_gas = SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee( &consumed_weight.saturating_add(self.extra_weight), )); - log::trace!(target: LOG_TARGET, "Gas consumption calculation: \ - consumed_weight={consumed_weight:?}, \ - consumed_deposit={consumed_deposit:?}, \ - deposit_gas={deposit_gas:?}, \ - fixed_fee_gas={fixed_fee_gas:?}, \ - scaled_gas={scaled_gas:?}, \ - weight_fee={weight_fee:?}, \ - extra_weight={:?}", - self.extra_weight,); - - scaled_gas.saturating_add(&weight_fee) + deposit_gas.saturating_add(&weight_gas) } /// Calculate maximal possible remaining weight that can be consumed given a particular gas @@ -653,15 +648,12 @@ impl EthTxInfo { total_weight_consumption: &Weight, total_deposit_consumption: &DepositOf, ) -> Option { - let numerator = SignedGas::from_deposit_charge(total_deposit_consumption) - .saturating_add(&SignedGas::Positive(T::FeeInfo::fixed_fee(self.encoded_len))); - let consumable_fee = max_total_gas.saturating_sub( - &numerator.scale_by_factor(&T::FeeInfo::next_fee_multiplier_reciprocal()), - ); + let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len); + let deposit_and_fixed_fee = + total_deposit_consumption.saturating_add(&DepositOf::::Charge(fixed_fee)); + let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee); - let SignedGas::Positive(consumable_fee) = consumable_fee else { - return None; - }; + let consumable_fee = max_total_gas.saturating_sub(&deposit_gas).to_weight_fee()?; T::FeeInfo::fee_to_weight(consumable_fee) .checked_sub(&total_weight_consumption.saturating_add(self.extra_weight)) diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 0fbf0e2fd04d5..c5154d7055aea 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -23,13 +23,13 @@ use crate::{ }; use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{traits::tokens::Balance, weights::Weight, DebugNoBound}; +use frame_support::{traits::tokens::Balance, weights::Weight}; use pallet_revive_uapi::ReturnFlags; use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::{ traits::{One, Saturating, Zero}, - DispatchError, FixedPointNumber, FixedU128, RuntimeDebug, + DispatchError, RuntimeDebug, }; /// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and @@ -356,107 +356,6 @@ where } } -/// The type for Ethereum gas. We need to deal with negative and positive values and the structure -/// of this type resembles `StorageDeposit` but the enum variants have a more obvious name to avoid -/// confusion and errors -#[derive( - Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, DebugNoBound, TypeInfo, -)] -pub enum SignedGas { - /// Positive gas amount - Positive(BalanceOf), - /// Negative gas amount - Negative(BalanceOf), -} - -impl Default for SignedGas { - fn default() -> Self { - Self::Positive(Default::default()) - } -} - -impl SignedGas { - /// This is essentially a saturating signed add. - pub fn saturating_add(&self, rhs: &Self) -> Self { - use SignedGas::*; - match (self, rhs) { - (Positive(lhs), Positive(rhs)) => Positive(lhs.saturating_add(*rhs)), - (Negative(lhs), Negative(rhs)) => Negative(lhs.saturating_add(*rhs)), - (Positive(lhs), Negative(rhs)) => - if lhs >= rhs { - Positive(lhs.saturating_sub(*rhs)) - } else { - Negative(rhs.saturating_sub(*lhs)) - }, - (Negative(lhs), Positive(rhs)) => - if lhs > rhs { - Negative(lhs.saturating_sub(*rhs)) - } else { - Positive(rhs.saturating_sub(*lhs)) - }, - } - } - - /// This is essentially a saturating signed sub. - pub fn saturating_sub(&self, rhs: &Self) -> Self { - use SignedGas::*; - match (self, rhs) { - (Positive(lhs), Negative(rhs)) => Positive(lhs.saturating_add(*rhs)), - (Negative(lhs), Positive(rhs)) => Negative(lhs.saturating_add(*rhs)), - (Positive(lhs), Positive(rhs)) => - if lhs >= rhs { - Positive(lhs.saturating_sub(*rhs)) - } else { - Negative(rhs.saturating_sub(*lhs)) - }, - (Negative(lhs), Negative(rhs)) => - if lhs > rhs { - Negative(lhs.saturating_sub(*rhs)) - } else { - Positive(rhs.saturating_sub(*lhs)) - }, - } - } - - /// transform a storage deposit into a gas value and treat a charge as a positive number - pub fn from_deposit_charge(deposit: &StorageDeposit>) -> Self { - use SignedGas::*; - match deposit { - StorageDeposit::Charge(amount) => Positive(*amount), - StorageDeposit::Refund(amount) if *amount == Default::default() => Positive(*amount), - StorageDeposit::Refund(amount) => Negative(*amount), - } - } - - /// transform a storage deposit into a gas value and treat a refund as a positive number - pub fn from_deposit_refund(deposit: &StorageDeposit>) -> Self { - use SignedGas::*; - match deposit { - StorageDeposit::Refund(amount) => Positive(*amount), - StorageDeposit::Charge(amount) if *amount == Default::default() => Positive(*amount), - StorageDeposit::Charge(amount) => Negative(*amount), - } - } - - /// Scale this scaled gas value by a `FixedU128` factor - pub fn scale_by_factor(&self, rhs: &FixedU128) -> Self { - use SignedGas::*; - match self { - Positive(amount) => Positive(rhs.saturating_mul_int(*amount)), - Negative(amount) => Negative(rhs.saturating_mul_int(*amount)), - } - } - - /// Return the balance of the `SignedGas` if it is `Positive`, otherwise return `None` - pub fn as_positive(&self) -> Option> { - use SignedGas::*; - match self { - Positive(amount) => Some(*amount), - Negative(_amount) => None, - } - } -} - /// `Stack` wide configuration options. pub struct ExecConfig { /// Indicates whether the account nonce should be incremented after instantiating a new From 338f0eeb910798e55ed255010792844eb9ce396f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Mon, 24 Nov 2025 04:01:46 -0300 Subject: [PATCH 40/69] Clean up code structure --- .github/workflows/tests-evm.yml | 4 +- substrate/frame/revive/src/lib.rs | 7 +- substrate/frame/revive/src/metering/gas.rs | 188 +++++++++----------- substrate/frame/revive/src/metering/math.rs | 95 +++++----- substrate/frame/revive/src/metering/mod.rs | 29 +-- 5 files changed, 142 insertions(+), 181 deletions(-) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 73085d5a38fd4..003fd0d5bef29 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -23,7 +23,7 @@ jobs: needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} if: ${{ needs.preflight.outputs.changes_rust }} - timeout-minutes: 90 + timeout-minutes: 60 container: image: ${{ needs.preflight.outputs.IMAGE }} permissions: @@ -71,7 +71,7 @@ jobs: --platform ${{ matrix.platform }} \ --concurrency.number-of-nodes 10 \ --concurrency.number-of-threads 10 \ - --concurrency.number-of-concurrent-tasks 10 \ + --concurrency.number-of-concurrent-tasks 1000 \ --working-directory ./workdir \ --revive-dev-node.consensus manual-seal-200 \ --revive-dev-node.path ./target/release/revive-dev-node \ diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index fc995ca063ef5..602c0d93d423c 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -101,9 +101,8 @@ pub use crate::{ }, exec::{CallResources, DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin}, metering::{ - gas::{InternalGas, SignedGas}, - weight::Token as WeightToken, - EthTxInfo, FrameMeter, ResourceMeter, TransactionLimits, TransactionMeter, + gas::SignedGas, weight::Token as WeightToken, EthTxInfo, FrameMeter, ResourceMeter, + TransactionLimits, TransactionMeter, }, pallet::{genesis, *}, storage::{AccountInfo, ContractInfo}, @@ -1609,7 +1608,7 @@ impl Pallet { let result = Self::run_guarded(try_call); log::trace!(target: LOG_TARGET, "Bare call ends: \ - result={result:?}, \ + result={result:?}, \ weight_consumed={:?}, \ weight_required={:?}, \ storage_deposit={:?}, \ diff --git a/substrate/frame/revive/src/metering/gas.rs b/substrate/frame/revive/src/metering/gas.rs index a0ad04499b669..0a84e4a2443d5 100644 --- a/substrate/frame/revive/src/metering/gas.rs +++ b/substrate/frame/revive/src/metering/gas.rs @@ -16,76 +16,25 @@ // limitations under the License. use crate::{evm::fees::InfoT, BalanceOf, Config, StorageDeposit}; -use frame_support::{traits::tokens::Balance as BalanceT, DebugNoBound}; +use frame_support::DebugNoBound; use sp_core::Get; -use sp_runtime::FixedPointNumber; - -/// Internal scaled representation of Ethereum Gas -/// Compared to the Ethereum Gas amounts that are visible externally, this is scaled by -/// `Config::GasScale` -#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)] -pub struct InternalGas(Balance); - -impl InternalGas { - pub fn into_external_gas(self) -> Balance - where - T: Config, - { - let gas_scale = ::GasScale::get(); - - self.0 / gas_scale - } - - pub fn into_weight_fee(self) -> Balance { - self.0 - } - - pub fn from_weight_fee(weight_fee: Balance) -> Self { - Self(weight_fee) - } +use sp_runtime::{FixedPointNumber, Saturating}; - pub fn from_external_gas(gas: Balance) -> Self - where - T: Config, - { - let gas_scale = ::GasScale::get(); - - Self(gas.saturating_mul(gas_scale)) - } - - pub fn saturating_add(&self, rhs: &Self) -> Self { - Self(self.0.saturating_add(rhs.0)) - } - - pub fn saturating_sub(&self, rhs: &Self) -> Self { - Self(self.0.saturating_sub(rhs.0)) - } - - pub fn min(self, other: Self) -> Self { - Self(self.0.min(other.0)) - } - - pub fn into_adjusted_deposit(self) -> Balance - where - T: Config, - { - let multiplier = T::FeeInfo::next_fee_multiplier(); - - multiplier.saturating_mul_int(self.0) - } -} - -/// The signed version of internal gas. +/// The type for negative and positive gas amounts +/// /// The structure of this type resembles `StorageDeposit` but the enum variants have a more obvious /// name to avoid confusion and errors #[derive(Clone, Eq, PartialEq, DebugNoBound)] pub enum SignedGas { /// Positive gas amount - Positive(InternalGas>), + Positive(BalanceOf), /// Negative gas amount - Negative(InternalGas>), + /// Invariant: BalanceOf is never 0 for `Negative` + Negative(BalanceOf), } +use SignedGas::{Negative, Positive}; + impl Default for SignedGas { fn default() -> Self { Self::Positive(Default::default()) @@ -93,78 +42,111 @@ impl Default for SignedGas { } impl SignedGas { + /// Transform a weight fee into a gas amount. pub fn from_weight_fee(weight_fee: BalanceOf) -> Self { - Self::Positive(InternalGas::from_weight_fee(weight_fee)) + Self::Positive(weight_fee) } - pub fn from_external_gas(gas: BalanceOf) -> Self { - Self::Positive(InternalGas::from_external_gas::(gas)) + /// Transform an Ethereum gas amount coming from outside the metering system and transform into + /// the internally used SignedGas. + pub fn from_ethereum_gas(gas: BalanceOf) -> Self { + let gas_scale = ::GasScale::get(); + Self::Positive(gas.saturating_mul(gas_scale)) + } + + /// Transform a storage deposit into a gas value. The value will be adjusted by dividing it + /// through the next fee multiplier. Charges are treated as a positive numbers and refunds as + /// negative numbers. + pub fn from_adjusted_deposit_charge(deposit: &StorageDeposit>) -> Self { + let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); + + match deposit { + StorageDeposit::Charge(amount) => Positive(multiplier.saturating_mul_int(*amount)), + StorageDeposit::Refund(amount) if *amount == Default::default() => Positive(*amount), + StorageDeposit::Refund(amount) => Negative(multiplier.saturating_mul_int(*amount)), + } + } + + /// Transform the gas amount to a weight fee amount + /// Returns None if the gas amount is negative. + pub fn to_weight_fee(&self) -> Option> { + match self { + Positive(amount) => Some(*amount), + Negative(..) => None, + } + } + + /// Transform the gas amount to an Ethereum gas amount usable for external purposes + /// Returns None if the gas amount is negative. + pub fn to_ethereum_gas(&self) -> Option> { + let gas_scale = ::GasScale::get(); + + match self { + Positive(amount) => Some((*amount) / gas_scale), + Negative(..) => None, + } + } + + /// Transform the gas amount to a deposit charge. The amount will be adjusted by multiplying it + /// with the next fee multiplier. + /// Returns None if the gas amount is negative. + pub fn to_adjusted_deposit_charge(&self) -> Option> { + match self { + Positive(amount) => { + let multiplier = T::FeeInfo::next_fee_multiplier(); + Some(multiplier.saturating_mul_int(*amount)) + }, + _ => None, + } } /// This is essentially a saturating signed add. pub fn saturating_add(&self, rhs: &Self) -> Self { - use SignedGas::*; match (self, rhs) { - (Positive(lhs), Positive(rhs)) => Positive(lhs.saturating_add(rhs)), - (Negative(lhs), Negative(rhs)) => Negative(lhs.saturating_add(rhs)), + (Positive(lhs), Positive(rhs)) => Positive(lhs.saturating_add(*rhs)), + (Negative(lhs), Negative(rhs)) => Negative(lhs.saturating_add(*rhs)), (Positive(lhs), Negative(rhs)) => - if lhs.0 >= rhs.0 { - Positive(lhs.saturating_sub(rhs)) + if lhs >= rhs { + Positive(lhs.saturating_sub(*rhs)) } else { - Negative(rhs.saturating_sub(lhs)) + Negative(rhs.saturating_sub(*lhs)) }, (Negative(lhs), Positive(rhs)) => - if lhs.0 > rhs.0 { - Negative(lhs.saturating_sub(rhs)) + if lhs > rhs { + Negative(lhs.saturating_sub(*rhs)) } else { - Positive(rhs.saturating_sub(lhs)) + Positive(rhs.saturating_sub(*lhs)) }, } } /// This is essentially a saturating signed sub. pub fn saturating_sub(&self, rhs: &Self) -> Self { - use SignedGas::*; match (self, rhs) { - (Positive(lhs), Negative(rhs)) => Positive(lhs.saturating_add(rhs)), - (Negative(lhs), Positive(rhs)) => Negative(lhs.saturating_add(rhs)), + (Positive(lhs), Negative(rhs)) => Positive(lhs.saturating_add(*rhs)), + (Negative(lhs), Positive(rhs)) => Negative(lhs.saturating_add(*rhs)), (Positive(lhs), Positive(rhs)) => - if lhs.0 >= rhs.0 { - Positive(lhs.saturating_sub(rhs)) + if lhs >= rhs { + Positive(lhs.saturating_sub(*rhs)) } else { - Negative(rhs.saturating_sub(lhs)) + Negative(rhs.saturating_sub(*lhs)) }, (Negative(lhs), Negative(rhs)) => - if lhs.0 > rhs.0 { - Negative(lhs.saturating_sub(rhs)) + if lhs > rhs { + Negative(lhs.saturating_sub(*rhs)) } else { - Positive(rhs.saturating_sub(lhs)) + Positive(rhs.saturating_sub(*lhs)) }, } } - /// transform a storage deposit into a gas value and treat a charge as a positive number - pub fn from_adjusted_deposit_charge(deposit: &StorageDeposit>) -> Self { - use SignedGas::*; - - let multiplier = T::FeeInfo::next_fee_multiplier_reciprocal(); - match deposit { - StorageDeposit::Charge(amount) => - Positive(InternalGas(multiplier.saturating_mul_int(*amount))), - StorageDeposit::Refund(amount) if *amount == Default::default() => - Positive(InternalGas(*amount)), - StorageDeposit::Refund(amount) => - Negative(InternalGas(multiplier.saturating_mul_int(*amount))), - } - } - - /// Return the balance of the `SignedGas` if it is `Positive`, otherwise return `None` - pub fn as_positive(&self) -> Option>> { - use SignedGas::*; - - match self { - Positive(amount) => Some(*amount), - Negative(_amount) => None, + // Determine the minimum of two signed gas values. + pub fn min(&self, other: &Self) -> Self { + match (self, other) { + (Positive(_), Negative(rhs)) => Negative(*rhs), + (Negative(lhs), Positive(_)) => Negative(*lhs), + (Positive(lhs), Positive(rhs)) => Positive((*lhs).min(*rhs)), + (Negative(lhs), Negative(rhs)) => Negative((*lhs).max(*rhs)), } } } diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index 110593928a2de..8177a9296ca67 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -20,9 +20,7 @@ use super::{ FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, State, StorageDeposit, TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, }; -use crate::{ - limits::SOLIDITY_CALL_STIPEND, metering::weight::Token, vm::evm::EVMGas, InternalGas, SignedGas, -}; +use crate::{limits::SOLIDITY_CALL_STIPEND, metering::weight::Token, vm::evm::EVMGas, SignedGas}; use core::marker::PhantomData; fn determine_call_stipend() -> Weight { @@ -85,14 +83,14 @@ pub mod substrate_execution { let weight_left = meter .weight .weight_limit - .expect("Weight limits all always defined for WeightAndDeposit; qed") + .expect("Weight limits are always defined for WeightAndDeposit; qed") .checked_sub(&self_consumed_weight) .ok_or(>::OutOfGas)?; let deposit_limit = meter .deposit .limit - .expect("Deposit limits all always defined for WeightAndDeposit; qed"); + .expect("Deposit limits are always defined for WeightAndDeposit; qed"); let deposit_left = self_consumed_deposit .available(&deposit_limit) .ok_or(>::StorageDepositLimitExhausted)?; @@ -106,23 +104,26 @@ pub mod substrate_execution { // then cap that gas by the requested `gas`. Distribute the capped gas // back into weight and deposit portions using the same ratio so that // the nested frame receives proportional limits. - let weight_gas = InternalGas::from_weight_fee( + let weight_gas_left = SignedGas::::from_weight_fee( T::FeeInfo::weight_to_fee_average(&weight_left), ); - let deposit_gas = InternalGas::from_weight_fee( - T::FeeInfo::next_fee_multiplier_reciprocal() - .saturating_mul_int(deposit_left), + let deposit_gas_left = SignedGas::::from_adjusted_deposit_charge( + &StorageDeposit::Charge(deposit_left), ); - let gas_left = - (weight_gas.saturating_add(&deposit_gas)).into_external_gas::(); - let gas_limit = gas_left.min(*gas); + let Some(remaining_gas) = + (weight_gas_left.saturating_add(&deposit_gas_left)).to_ethereum_gas() + else { + return Err(>::OutOfGas.into()); + }; - let ratio = if gas_left.is_zero() { + let gas_limit = remaining_gas.min(*gas); + + let ratio = if remaining_gas.is_zero() { FixedU128::one() } else { FixedU128::from_rational( gas_limit.saturated_into(), - gas_left.saturated_into(), + remaining_gas.saturated_into(), ) }; @@ -170,18 +171,17 @@ pub mod substrate_execution { /// /// Converts the remaining weight and deposit into their gas-equivalents (via `FeeInfo`) and /// returns the sum. Returns `None` if either component there is not enough as left - pub fn eth_gas_left( - meter: &ResourceMeter, - ) -> Option>> { + pub fn gas_left(meter: &ResourceMeter) -> Option> { match (weight_left(meter), deposit_left(meter)) { (Some(weight_left), Some(deposit_left)) => { - let weight_gas = - InternalGas::from_weight_fee(T::FeeInfo::weight_to_fee_average(&weight_left)); - let deposit_gas = InternalGas::from_weight_fee( - T::FeeInfo::next_fee_multiplier_reciprocal().saturating_mul_int(deposit_left), + let weight_gas_left = SignedGas::::from_weight_fee( + T::FeeInfo::weight_to_fee_average(&weight_left), + ); + let deposit_gas_left = SignedGas::::from_adjusted_deposit_charge( + &StorageDeposit::Charge(deposit_left), ); - Some(weight_gas.saturating_add(&deposit_gas)) + Some(weight_gas_left.saturating_add(&deposit_gas_left)) }, _ => None, } @@ -194,7 +194,7 @@ pub mod substrate_execution { let weight_limit = meter .weight .weight_limit - .expect("Weight limits all always defined for WeightAndDeposit; qed"); + .expect("Weight limits are always defined for WeightAndDeposit; qed"); weight_limit.checked_sub(&meter.weight.weight_consumed()) } @@ -206,7 +206,7 @@ pub mod substrate_execution { let deposit_limit = meter .deposit .limit - .expect("Deposit limits all always defined for WeightAndDeposit; qed"); + .expect("Deposit limits are always defined for WeightAndDeposit; qed"); meter.deposit.consumed().available(&deposit_limit) } @@ -223,10 +223,11 @@ pub mod substrate_execution { let total_consumed_deposit = meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit); - let consumed_weight_fee = T::FeeInfo::weight_to_fee_average(&total_consumed_weight); + let consumed_weight_gas = + SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee_average(&total_consumed_weight)); let consumed_deposit_gas = SignedGas::from_adjusted_deposit_charge(&total_consumed_deposit); - consumed_deposit_gas.saturating_add(&SignedGas::from_weight_fee(consumed_weight_fee)) + consumed_deposit_gas.saturating_add(&consumed_weight_gas) } /// Compute the gas (signed) during the lifetime of this meter for Substrate-style execution. @@ -270,7 +271,7 @@ pub mod ethereum_execution { let meter = TransactionMeter { weight: WeightMeter::new(maybe_weight_limit, None), deposit: RootStorageMeter::new(None), - max_total_gas: SignedGas::from_external_gas(eth_gas_limit), + max_total_gas: SignedGas::from_ethereum_gas(eth_gas_limit), total_consumed_weight_before: Default::default(), total_consumed_deposit_before: Default::default(), transaction_limits: TransactionLimits::EthereumGas { @@ -315,11 +316,7 @@ pub mod ethereum_execution { let total_gas_consumption = eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit); - let Some(gas_left) = - meter.max_total_gas.saturating_sub(&total_gas_consumption).as_positive() - else { - return Err(>::OutOfGas.into()); - }; + let remaining_gas = meter.max_total_gas.saturating_sub(&total_gas_consumption); let weight_left = { let unbounded_weight_left = eth_tx_info @@ -339,7 +336,9 @@ pub mod ethereum_execution { }; let deposit_left = { - let unbounded_deposit_left = gas_left.into_adjusted_deposit::(); + let Some(unbounded_deposit_left) = remaining_gas.to_adjusted_deposit_charge() else { + return Err(>::OutOfGas.into()); + }; match meter.deposit.limit { Some(deposit_limit) => unbounded_deposit_left.min( @@ -354,14 +353,14 @@ pub mod ethereum_execution { let (nested_gas_limit, nested_weight_limit, nested_deposit_limit, stipend) = { match limit { CallResources::NoLimits => ( - gas_left, + remaining_gas, if meter.weight.weight_limit.is_none() { None } else { Some(weight_left) }, if meter.deposit.limit.is_none() { None } else { Some(deposit_left) }, None, ), CallResources::Ethereum { gas, add_stipend } => { - let internal_gas_limit = InternalGas::from_external_gas::(*gas); + let gas_limit = SignedGas::from_ethereum_gas(*gas); let (gas_limit, stipend) = if *add_stipend { let weight_stipend = determine_call_stipend::(); @@ -370,17 +369,17 @@ pub mod ethereum_execution { } ( - internal_gas_limit.saturating_add(&InternalGas::from_weight_fee( + gas_limit.saturating_add(&SignedGas::::from_weight_fee( T::FeeInfo::weight_to_fee(&weight_stipend), )), Some(weight_stipend), ) } else { - (internal_gas_limit, None) + (gas_limit, None) }; ( - gas_left.min(gas_limit), + remaining_gas.min(&gas_limit), if meter.weight.weight_limit.is_none() { None } else { Some(weight_left) }, if meter.deposit.limit.is_none() { None } else { Some(deposit_left) }, stipend, @@ -397,14 +396,10 @@ pub mod ethereum_execution { .saturating_add(&StorageDeposit::Charge(nested_deposit_limit)), ); - let Some(gas_limit) = - new_max_total_gas.saturating_sub(&total_gas_consumption).as_positive() - else { - return Err(>::OutOfGas.into()); - }; + let gas_limit = new_max_total_gas.saturating_sub(&total_gas_consumption); ( - gas_left.min(gas_limit), + remaining_gas.min(&gas_limit), Some(nested_weight_limit), Some(nested_deposit_limit), None, @@ -413,8 +408,7 @@ pub mod ethereum_execution { } }; - let nested_max_total_gas = - total_gas_consumption.saturating_add(&SignedGas::Positive(nested_gas_limit)); + let nested_max_total_gas = total_gas_consumption.saturating_add(&nested_gas_limit); Ok(FrameMeter:: { weight: WeightMeter::new(nested_weight_limit, stipend), @@ -428,10 +422,10 @@ pub mod ethereum_execution { } /// Compute remaining ethereum gas for an Ethereum-style execution. - pub fn eth_gas_left( + pub fn gas_left( meter: &ResourceMeter, eth_tx_info: &EthTxInfo, - ) -> Option>> { + ) -> Option> { let self_consumed_weight = meter.weight.weight_consumed(); let self_consumed_deposit = meter.deposit.consumed(); @@ -443,7 +437,7 @@ pub mod ethereum_execution { let total_gas_consumption = eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit); - meter.max_total_gas.saturating_sub(&total_gas_consumption).as_positive() + Some(meter.max_total_gas.saturating_sub(&total_gas_consumption)) } /// Return the remaining weight available to a nested frame under Ethereum-style execution. @@ -476,8 +470,7 @@ pub mod ethereum_execution { meter: &ResourceMeter, eth_tx_info: &EthTxInfo, ) -> Option> { - let gas_left = eth_gas_left(meter, eth_tx_info)?; - let deposit_left = gas_left.into_adjusted_deposit::(); + let deposit_left = gas_left(meter, eth_tx_info)?.to_adjusted_deposit_charge()?; Some(match meter.deposit.limit { Some(deposit_limit) => { diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 65709ffb0b0d0..93e15d0e905c4 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -335,14 +335,13 @@ impl ResourceMeter { /// - For substrate mode: converts weight+deposit to gas equivalent /// Returns None if resources are exhausted or conversion fails. pub fn eth_gas_left(&self) -> Option> { - let internal_gas = match &self.transaction_limits { + let gas_left = match &self.transaction_limits { TransactionLimits::EthereumGas { eth_tx_info, .. } => - math::ethereum_execution::eth_gas_left(self, eth_tx_info), - TransactionLimits::WeightAndDeposit { .. } => - math::substrate_execution::eth_gas_left(self), + math::ethereum_execution::gas_left(self, eth_tx_info), + TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::gas_left(self), }?; - Some(internal_gas.into_external_gas::()) + gas_left.to_ethereum_gas() } /// Get remaining weight available. @@ -389,7 +388,7 @@ impl ResourceMeter { math::substrate_execution::total_consumed_gas(self), }; - signed_gas.as_positive().unwrap_or_default().into_external_gas::() + signed_gas.to_ethereum_gas().unwrap_or_default() } /// Get total weight consumed @@ -429,7 +428,7 @@ impl ResourceMeter { math::substrate_execution::eth_gas_consumed(self), }; - signed_gas.as_positive().unwrap_or_default().into_external_gas::() + signed_gas.to_ethereum_gas().unwrap_or_default() } /// Determine and set the new effective weight limit of the weight meter. @@ -636,14 +635,6 @@ impl EthTxInfo { &consumed_weight.saturating_add(self.extra_weight), )); - log::trace!(target: LOG_TARGET, "Gas consumption calculation: \ - consumed_weight={consumed_weight:?}, \ - consumed_deposit={consumed_deposit:?}, \ - deposit_gas={deposit_gas:?}, \ - weight_gas={weight_gas:?}, \ - extra_weight={:?}", - self.extra_weight,); - deposit_gas.saturating_add(&weight_gas) } @@ -662,13 +653,9 @@ impl EthTxInfo { total_deposit_consumption.saturating_add(&DepositOf::::Charge(fixed_fee)); let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee); - let consumable_fee = max_total_gas.saturating_sub(&deposit_gas); - - let SignedGas::Positive(consumable_fee) = consumable_fee else { - return None; - }; + let consumable_fee = max_total_gas.saturating_sub(&deposit_gas).to_weight_fee()?; - T::FeeInfo::fee_to_weight(consumable_fee.into_weight_fee()) + T::FeeInfo::fee_to_weight(consumable_fee) .checked_sub(&total_weight_consumption.saturating_add(self.extra_weight)) } } From e6fcef85560d8325914523d57b96814635436f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Mon, 24 Nov 2025 05:01:54 -0300 Subject: [PATCH 41/69] Fix doc comment --- substrate/frame/revive/src/metering/gas.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/metering/gas.rs b/substrate/frame/revive/src/metering/gas.rs index bd4081baf5655..a95a41479d19f 100644 --- a/substrate/frame/revive/src/metering/gas.rs +++ b/substrate/frame/revive/src/metering/gas.rs @@ -28,7 +28,7 @@ pub enum SignedGas { /// Positive gas amount Positive(BalanceOf), /// Negative gas amount - /// Invariant: BalanceOf is never 0 for `Negative` + /// Invariant: `BalanceOf` is never 0 for `Negative` Negative(BalanceOf), } From 4f1419a6be3cc4902fa1eb03703faeacae1ce5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Mon, 24 Nov 2025 05:22:50 -0300 Subject: [PATCH 42/69] Lower differential testing concurrency --- .github/workflows/tests-evm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index b990749a671ef..39e81fd1d0e66 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -71,7 +71,7 @@ jobs: --platform ${{ matrix.platform }} \ --concurrency.number-of-nodes 10 \ --concurrency.number-of-threads 10 \ - --concurrency.number-of-concurrent-tasks 1000 \ + --concurrency.number-of-concurrent-tasks 100 \ --working-directory ./workdir \ --revive-dev-node.consensus manual-seal-200 \ --revive-dev-node.path ./target/release/revive-dev-node \ From cc321905a1ac6e54cc190cf9dc2910a2992bfb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Mon, 24 Nov 2025 06:12:46 -0300 Subject: [PATCH 43/69] Make tests less brittle --- substrate/frame/revive/src/tests/pvm.rs | 30 ++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 9d9f01287180a..843f16a5f5a3d 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -5279,6 +5279,7 @@ fn self_destruct_by_syscall_tracing_works() { description: &'static str, create_tracer: Box Tracer>, expected_trace_fn: Box) -> Trace>, + modify_trace_fn: Option Trace>>, } let test_cases = vec![ @@ -5291,12 +5292,12 @@ fn self_destruct_by_syscall_tracing_works() { to: addr, call_type: CallType::Call, value: Some(U256::zero()), - gas: U256::from(2499916585034u64), - gas_used: U256::from(94847106u64), + gas: 0.into(), + gas_used: 0.into(), calls: vec![CallTrace { from: addr, to: DJANGO_ADDR, - gas: U256::from(2497550684361u64), + gas: 0.into(), call_type: CallType::Selfdestruct, value: Some(Pallet::::convert_native_to_evm(100_000u64)), @@ -5305,6 +5306,14 @@ fn self_destruct_by_syscall_tracing_works() { ..Default::default() }) }), + modify_trace_fn: Some(Box::new(|mut actual_trace| { + if let Trace::Call(trace) = &mut actual_trace { + trace.gas = 0.into(); + trace.gas_used = 0.into(); + trace.calls[0].gas = 0.into(); + } + actual_trace + })), }, TestCase { description: "PrestateTracer (diff mode)", @@ -5354,6 +5363,7 @@ fn self_destruct_by_syscall_tracing_works() { let expected: PrestateTrace = serde_json::from_str(&json).unwrap(); Trace::Prestate(expected) }), + modify_trace_fn: None, }, TestCase { description: "PrestateTracer (prestate mode)", @@ -5398,10 +5408,11 @@ fn self_destruct_by_syscall_tracing_works() { let expected: PrestateTrace = serde_json::from_str(&json).unwrap(); Trace::Prestate(expected) }), + modify_trace_fn: None, }, ]; - for TestCase { description, create_tracer, expected_trace_fn } in test_cases { + for TestCase { description, create_tracer, expected_trace_fn, modify_trace_fn } in test_cases { ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 1_000_000); @@ -5417,14 +5428,17 @@ fn self_destruct_by_syscall_tracing_works() { builder::call(addr).build().unwrap(); }); - let trace = tracer.collect_trace(); + let mut trace = tracer.collect_trace().unwrap(); - let trace_wrapped = trace.map(|t| match t { + if let Some(modify_trace_fn) = modify_trace_fn { + trace = modify_trace_fn(trace); + } + let trace_wrapped = match trace { crate::evm::Trace::Call(ct) => Trace::Call(ct), crate::evm::Trace::Prestate(pt) => Trace::Prestate(pt), - }); + }; - assert_eq!(trace_wrapped, Some(expected_trace), "Trace mismatch for: {}", description); + assert_eq!(trace_wrapped, expected_trace, "Trace mismatch for: {}", description); }); } } From 539a9e976d02ae4eafb56774d183bbb4cd25ee24 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:54:38 +0000 Subject: [PATCH 44/69] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --bump patch' --- prdoc/pr_10166.prdoc | 98 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 prdoc/pr_10166.prdoc diff --git a/prdoc/pr_10166.prdoc b/prdoc/pr_10166.prdoc new file mode 100644 index 0000000000000..aa30a472ee93a --- /dev/null +++ b/prdoc/pr_10166.prdoc @@ -0,0 +1,98 @@ +title: Implement general gas tracking +doc: +- audience: Runtime Dev + description: |- + This PR implements [the general gas tracking spec](https://shade-verse-e97.notion.site/Revive-Resource-Management-2928532a7ab5808381b4e688fcc58838?pvs=74). + + This PR ballooned into something much bigger than I expected. Many of the changes are due to the fact that all tests and a lot of the other logic has some touch points with the resource management logic. Most of the actual changes in logic are just in the folder `metering` of pallet-revive. + + The main changes are that + - Metering now works differently depending on whether the transaction as a whole defines weight and deposit limits ("Substrate execution mode") or just an Ethereum gas limit ("Ethereum execution mode"). The Ethereum execution mode is used for all `eth_transact` extrinsics. + - There is a third resource (in addition to weight and storage deposits): Ethereum gas. In the Ethereum execution mode this is a shared resource (consumable through weight and through storage deposits). + + ## Metering logic + Almost all changes in this PR are confined to the folder `metering` of pallet-revive. Before this PR there were two meters: a weight meter and a gas meter. They have now been combined into a main meter called `ResourceMeter`. Outside code only interacts with the `ResourceMeter` and not individually with the gas or storage meter. The reason is that in Ethereum execution mode gas is a shared resource and interacting with one meter influences the limits of the other meter. + + Here are some finer points: + - The previous code of the gas and deposit meters has been moved to the `metering` folder + - Since outside code interacts only with the `ResourceMeter`, most functions now don't use a separate gas meter and deposit meter anymore but just a `ResourceMeter` + - Similar to the two two kinds of deposits meters (`Root` and `Nested`), there are two kind of `ResourceMeter`: the top-level `TransactionMeter` used at the beginning of a transaction and a `FrameMeter` used once per frame + - The limits of a `TransactionMeter` are specified through the `TransactionLimits` type, which distinguishes between Substrate and Ethereum execution mode. + - The limits of a `FrameMeter` is specified through the type `CallResources`, which can either be a) no limits (e.g., in the case of contract creation), or b) a weight and deposit, or c) a gas limit. + - The top level name of functions in the meters has been changed to be a bit more explicit about their purpose. + - This applied particularly to the methods at the end of the lifecycle: + - `enforce_limit` has been renamed to `finalize` as that describes the semantics better + - `try_into_deposit` has been renamed to `execute_postponed_deposits` + - For absorbing a frame meter into its parent meter, there are two different absorption functions: + - `absorb_weight_meter_only`: when a frame reverts. In this case we ignore all storage deposits from the reverting frame. We still need to absorb the observed maximum deposit so that we determine the correct maximum deposit during dry running. + - `absorb_all_meters`: when a frame was successful + - The weight meter now has an `effective_weight_limit`, which needs to be recalculated whenever the deposit meter changes and is for optimization purposes. + - The limits of the gas meter and deposit meters are now an `Option<...>`. When it is `None`, then this represents unlimited meters and this is only used for Ethereum style executions (the meters are not really unlimited, there will be a gas limit that effectively limits the resource usages of the weight and deposit meters). + - In the weight meters, the `sync_to_executor` and `sync_from_executor` are a bit simplified and there is no need for `engine_fuel_left` anymore. + + ## Other Changes + - The old name `gas` for weights has been consistently replaced by `weight` + - `eth_call` and `eth_instantiate_with_code` now take a `weight_limit` (used to ensure that weight does not exceed the max extrinsic weight) and an `eth_gas_limit` (the new externally defined limit) + - The numeric calculation in `compute_max_integer_quotient` and `compute_max_integer_pair_quotient` (defined in `substrate/frame/revive/src/evm/frees.rs`) are meant to divide a number by the next fee multiplier + - The call tracer does not take a `GasMapper` anymore as it will now be fed directly with the Ethereum gas values instead of weights + - Re-entrancy protection now has three modes: no protection, `Strict` protection and `AllowNext` + - `AllowNext` allows to re-enter the same contract but only for the next frame. This is required to implement reentrancy protection for simple transfers with call stipends + - For `Strict` protection we set `allows_reentry` of the caller to `false` before the creation of the new frame, for `AllowNext` we to it after the creation + - We define the max block gas as `u64::MAX` (as discussed with @pgherveou) + - I now calculate the maximal required storage deposits during dry running (called `max_storage_deposit` in the deposit meter). For example, if a transaction encounters a storage deposit that is later refunded, then the total storage deposit is zero. However, the caller needs to provide enough resources so that temporarily the execution does not run out of gas and terminates the call prematurely. + - The function `try_upload_code` now always takes a meter and records the storage deposit charge there + - In this PR I added logic to correctly handle call stipends (this fixes https://github.com/paritytech/contract-issues/issues/215) + + ## Fixes + This fixes a couple of issues + - fixes https://github.com/paritytech/contract-issues/issues/215 + - fixes https://github.com/paritytech/polkadot-sdk/issues/8362 + - fixes https://github.com/paritytech-secops/srlabs_findings/issues/589 + - fixes https://github.com/paritytech/contract-issues/issues/197 + - fixes https://github.com/paritytech/contract-issues/issues/208 + - fixes https://github.com/paritytech/contract-issues/issues/212 + + ## TODOs + * [x] Ignore deposit refunds for dry running + * [x] Properly enforce weight limits + * [x] Fix gas mapping in the tracer + * [x] Fix (?) gas mapping in block storage (`with_ethereum_context`) + * [x] Check dry running logic again, and create_call, also in `ExecConfig` + * [x] Introduce `SignedGas` + * [x] use `effective_gas_price` instead of next fee multiplier + * TBD, also see https://github.com/paritytech/polkadot-sdk/pull/10148#pullrequestreview-3407403361 + * [x] ensure that deducted amount is `effective_gas_price` * used gas + * this has already been addressed in https://github.com/paritytech/polkadot-sdk/pull/10148 + * [x] check logic of `ensure_not_overdrawn` + * [x] Optimize calculations + * [x] Check whether rounding is done correctly + * [x] add debug logging + * [x] Scale gas amounts charged in revm + * this has been addressed in another PR: https://github.com/paritytech/polkadot-sdk/pull/10393 + + Other TODOs + * [x] fix tests and benchmarks + * [x] add new tests + * [x] add code docs + * [x] resolve merge conflicts + * [ ] run benchmarks + * [x] add PR description +crates: +- name: asset-hub-westend-runtime + bump: patch +- name: polkadot-omni-node-lib + bump: patch +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive-eth-rpc + bump: patch +- name: assets-common + bump: patch +- name: pallet-revive-proc-macro + bump: patch +- name: pallet-assets-precompiles + bump: patch +- name: pallet-xcm-precompiles + bump: patch From f3a9a3e86a1f0441591bf612a3ef6823d882e825 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:54:39 +0000 Subject: [PATCH 45/69] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --bump patch --force' --- prdoc/pr_10393.prdoc | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/prdoc/pr_10393.prdoc b/prdoc/pr_10393.prdoc index 7937ce997fbd4..ea3a97de543d7 100644 --- a/prdoc/pr_10393.prdoc +++ b/prdoc/pr_10393.prdoc @@ -6,16 +6,34 @@ doc: Before this PR, the Ethereum gas price is simply the next fee multiplier of pallet-transaction-payment multiplied by `NativeToEthRatio`. Thus, on Polkadot this is 100_000_000 when the multiplier has its default value of 1. - The required gas of a transaction is its total cost divided by the gas price, where the total cost is the transaction fee and the storage deposit. + The required gas of a transaction is its total cost divided by the gas price, where the total cost is the sum of the transaction fee and the storage deposit. This leads to a situation where the required gas for a transaction on revive is usually orders of magnitude larger than the required amount of gas on Ethereum. This can lead to issues with tools or systems that interact with revive and hard code expected gas amounts or upper limits of gas amounts. - By changing `GasScale`, the gas price used in revive is multiplied by `GasScale`. For that reason the used/estimated gas amounts get lower by the same factor. + Setting `GasScale` has two effects: + - revive's Ethereum gas price is scaled up by the factor `GasScale` + - resulting used/estimated gas amounts get scaled down by the factor `GasScale`. ## Technical Details - Internally, revive uses exactly the same gas price and gas units as before. Only at the interface these amounts and prices get scaled by `GasScale`. In order to avoid confusion and errors, this PR introduces a new type called `InternalGas`, which represents the unscaled, internal gas units. + Internally, revive uses exactly the same gas price and gas units as before. Only at the interface these amounts and prices get scaled by `GasScale`. + + ## Recommended + This PR sets `GasScale` for the dev-node to 50_000. + + This is motivated by the fact that storing a value in a contract storage slot costs `DepositPerChildTrieItem + DepositPerByte * 32`, which is `2_000_000_000 + 10_000_000 * 32` (= `2_320_000_000`) plancks. Before this change the gas price was 1_000_000 wei, so that this + equated to 2_320_000_000 gas units. In EVM this operation requires 22_100 gas only. + + Thus, `GasScale` would need to be about 100_000 in order for `SSTORE` to have similar worst case gas requirements. + + ## Resolved Issues + + This PR addresses https://github.com/paritytech/contract-issues/issues/18 but we also need to find an appropriate `GasScale` for a mainnet installment of pallet-revive. crates: - name: revive-dev-runtime bump: patch - name: pallet-revive bump: patch +- name: asset-hub-westend-runtime + bump: patch +- name: penpal-runtime + bump: patch From 8388ab1bf0f6a4956afbe42946ae123bf8153a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:27:20 -0300 Subject: [PATCH 46/69] Update commit of differential testing CI --- .github/workflows/tests-evm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 003fd0d5bef29..5444a6a80faca 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -51,7 +51,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: paritytech/revive-differential-tests - ref: b145f45cd3114fd378ce3d5031b127fb39cf9cbd + ref: cc753a1a2ca6a409748e70e043d7be064e9ce741 path: revive-differential-tests submodules: recursive - name: Installing Retester From 5a0919bbf69937eea3052cace35ffdae565cb645 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 23:27:19 +0000 Subject: [PATCH 47/69] Update from github-actions[bot] running command 'bench --runtime dev --pallet pallet_revive' --- substrate/frame/revive/src/weights.rs | 1312 +++++++++++++------------ 1 file changed, 660 insertions(+), 652 deletions(-) diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index f1147bc84db63..e13b67edc4fd8 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -35,9 +35,9 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2025-11-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-11-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `38296b9fed24`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `44a3520f326f`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: @@ -183,8 +183,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `213` // Estimated: `1698` - // Minimum execution time: 3_212_000 picoseconds. - Weight::from_parts(3_435_000, 1698) + // Minimum execution time: 3_325_000 picoseconds. + Weight::from_parts(3_509_000, 1698) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -194,10 +194,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `491 + k * (69 ±0)` // Estimated: `481 + k * (70 ±0)` - // Minimum execution time: 14_823_000 picoseconds. - Weight::from_parts(14_976_000, 481) - // Standard Error: 1_014 - .saturating_add(Weight::from_parts(1_198_198, 0).saturating_mul(k.into())) + // Minimum execution time: 14_389_000 picoseconds. + Weight::from_parts(15_127_000, 481) + // Standard Error: 1_039 + .saturating_add(Weight::from_parts(1_209_966, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -221,10 +221,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1265 + c * (1 ±0)` // Estimated: `7200 + c * (1 ±0)` - // Minimum execution time: 95_906_000 picoseconds. - Weight::from_parts(137_008_318, 7200) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_477, 0).saturating_mul(c.into())) + // Minimum execution time: 100_357_000 picoseconds. + Weight::from_parts(143_652_444, 7200) + // Standard Error: 12 + .saturating_add(Weight::from_parts(1_441, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) @@ -246,10 +246,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1205` // Estimated: `7144` - // Minimum execution time: 89_753_000 picoseconds. - Weight::from_parts(93_826_418, 7144) - // Standard Error: 17 - .saturating_add(Weight::from_parts(113, 0).saturating_mul(c.into())) + // Minimum execution time: 92_748_000 picoseconds. + Weight::from_parts(97_747_165, 7144) + // Standard Error: 24 + .saturating_add(Weight::from_parts(46, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -266,12 +266,14 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `b` is `[0, 1]`. - fn basic_block_compilation(_b: u32, ) -> Weight { + fn basic_block_compilation(b: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `4609` // Estimated: `10549` - // Minimum execution time: 140_658_000 picoseconds. - Weight::from_parts(145_253_853, 10549) + // Minimum execution time: 144_014_000 picoseconds. + Weight::from_parts(149_812_683, 10549) + // Standard Error: 724_905 + .saturating_add(Weight::from_parts(1_749_116, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -295,12 +297,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `994` // Estimated: `6924` - // Minimum execution time: 761_511_000 picoseconds. - Weight::from_parts(84_218_529, 6924) - // Standard Error: 36 - .saturating_add(Weight::from_parts(20_410, 0).saturating_mul(c.into())) - // Standard Error: 28 - .saturating_add(Weight::from_parts(4_874, 0).saturating_mul(i.into())) + // Minimum execution time: 773_080_000 picoseconds. + Weight::from_parts(70_290_148, 6924) + // Standard Error: 37 + .saturating_add(Weight::from_parts(20_365, 0).saturating_mul(c.into())) + // Standard Error: 29 + .saturating_add(Weight::from_parts(5_006, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -329,14 +331,14 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `994` // Estimated: `6934` - // Minimum execution time: 370_631_000 picoseconds. - Weight::from_parts(241_842_187, 6934) - // Standard Error: 56 - .saturating_add(Weight::from_parts(16_174, 0).saturating_mul(c.into())) - // Standard Error: 43 - .saturating_add(Weight::from_parts(679, 0).saturating_mul(i.into())) - // Standard Error: 3_661_614 - .saturating_add(Weight::from_parts(12_103_675, 0).saturating_mul(d.into())) + // Minimum execution time: 405_718_000 picoseconds. + Weight::from_parts(270_737_149, 6934) + // Standard Error: 44 + .saturating_add(Weight::from_parts(15_826, 0).saturating_mul(c.into())) + // Standard Error: 34 + .saturating_add(Weight::from_parts(660, 0).saturating_mul(i.into())) + // Standard Error: 2_902_398 + .saturating_add(Weight::from_parts(8_450_702, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(10_u64)) .saturating_add(T::DbWeight::get().writes(10_u64)) } @@ -344,8 +346,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_071_000 picoseconds. - Weight::from_parts(3_232_000, 0) + // Minimum execution time: 3_011_000 picoseconds. + Weight::from_parts(3_274_000, 0) } /// Storage: `Revive::AccountInfoOf` (r:2 w:1) /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) @@ -365,11 +367,11 @@ impl WeightInfo for SubstrateWeight { fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `1735` - // Estimated: `7659` - // Minimum execution time: 186_506_000 picoseconds. - Weight::from_parts(190_843_676, 7659) + // Estimated: `7665` + // Minimum execution time: 187_515_000 picoseconds. + Weight::from_parts(194_934_584, 7665) // Standard Error: 11 - .saturating_add(Weight::from_parts(4_180, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_151, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -387,10 +389,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1958` - // Estimated: `7898` - // Minimum execution time: 99_804_000 picoseconds. - Weight::from_parts(101_727_000, 7898) + // Measured: `1947` + // Estimated: `7887` + // Minimum execution time: 104_210_000 picoseconds. + Weight::from_parts(110_220_000, 7887) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -413,12 +415,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `d` is `[0, 1]`. fn eth_call(d: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1958` - // Estimated: `7898` - // Minimum execution time: 162_250_000 picoseconds. - Weight::from_parts(168_112_555, 7898) - // Standard Error: 549_643 - .saturating_add(Weight::from_parts(4_435_744, 0).saturating_mul(d.into())) + // Measured: `1947` + // Estimated: `7887` + // Minimum execution time: 179_230_000 picoseconds. + Weight::from_parts(189_040_259, 7887) + // Standard Error: 869_758 + .saturating_add(Weight::from_parts(4_172_840, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -435,10 +437,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3823` - // Minimum execution time: 29_050_000 picoseconds. - Weight::from_parts(23_929_217, 3823) - // Standard Error: 13 - .saturating_add(Weight::from_parts(6_243, 0).saturating_mul(c.into())) + // Minimum execution time: 28_233_000 picoseconds. + Weight::from_parts(22_905_261, 3823) + // Standard Error: 12 + .saturating_add(Weight::from_parts(6_274, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -455,10 +457,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `392` // Estimated: `3857` - // Minimum execution time: 60_288_000 picoseconds. - Weight::from_parts(52_801_907, 3857) - // Standard Error: 19 - .saturating_add(Weight::from_parts(14_346, 0).saturating_mul(c.into())) + // Minimum execution time: 59_732_000 picoseconds. + Weight::from_parts(53_002_061, 3857) + // Standard Error: 17 + .saturating_add(Weight::from_parts(14_107, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -472,8 +474,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `524` // Estimated: `3989` - // Minimum execution time: 53_434_000 picoseconds. - Weight::from_parts(55_152_000, 3989) + // Minimum execution time: 53_247_000 picoseconds. + Weight::from_parts(54_239_000, 3989) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -489,10 +491,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `867` - // Estimated: `6807` - // Minimum execution time: 66_946_000 picoseconds. - Weight::from_parts(69_226_000, 6807) + // Measured: `833` + // Estimated: `6773` + // Minimum execution time: 67_638_000 picoseconds. + Weight::from_parts(69_822_000, 6773) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -506,8 +508,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `623` // Estimated: `4088` - // Minimum execution time: 60_387_000 picoseconds. - Weight::from_parts(62_012_000, 4088) + // Minimum execution time: 60_308_000 picoseconds. + Weight::from_parts(61_865_000, 4088) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -519,8 +521,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `93` // Estimated: `3558` - // Minimum execution time: 40_203_000 picoseconds. - Weight::from_parts(40_786_000, 3558) + // Minimum execution time: 40_748_000 picoseconds. + Weight::from_parts(41_916_000, 3558) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -534,8 +536,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `381` // Estimated: `3846` - // Minimum execution time: 19_128_000 picoseconds. - Weight::from_parts(19_977_000, 3846) + // Minimum execution time: 19_441_000 picoseconds. + Weight::from_parts(19_775_000, 3846) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -543,24 +545,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_560_000 picoseconds. - Weight::from_parts(9_060_136, 0) - // Standard Error: 289 - .saturating_add(Weight::from_parts(177_740, 0).saturating_mul(r.into())) + // Minimum execution time: 8_031_000 picoseconds. + Weight::from_parts(9_266_076, 0) + // Standard Error: 205 + .saturating_add(Weight::from_parts(186_444, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 335_000 picoseconds. - Weight::from_parts(382_000, 0) + // Minimum execution time: 377_000 picoseconds. + Weight::from_parts(403_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 310_000 picoseconds. - Weight::from_parts(343_000, 0) + // Minimum execution time: 351_000 picoseconds. + Weight::from_parts(395_000, 0) } /// Storage: `Revive::OriginalAccount` (r:1 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) @@ -568,8 +570,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `623` // Estimated: `4088` - // Minimum execution time: 11_432_000 picoseconds. - Weight::from_parts(12_236_000, 4088) + // Minimum execution time: 11_287_000 picoseconds. + Weight::from_parts(12_022_000, 4088) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) @@ -578,16 +580,16 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 9_959_000 picoseconds. - Weight::from_parts(10_217_000, 3938) + // Minimum execution time: 9_933_000 picoseconds. + Weight::from_parts(10_470_000, 3938) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `436` // Estimated: `0` - // Minimum execution time: 9_727_000 picoseconds. - Weight::from_parts(10_186_000, 0) + // Minimum execution time: 9_614_000 picoseconds. + Weight::from_parts(10_109_000, 0) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) @@ -597,51 +599,51 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `545` // Estimated: `4010` - // Minimum execution time: 13_769_000 picoseconds. - Weight::from_parts(14_300_000, 4010) + // Minimum execution time: 13_599_000 picoseconds. + Weight::from_parts(14_148_000, 4010) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_199_000 picoseconds. - Weight::from_parts(1_310_000, 0) + // Minimum execution time: 1_161_000 picoseconds. + Weight::from_parts(1_257_000, 0) } fn caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_110_000 picoseconds. - Weight::from_parts(1_253_000, 0) + // Minimum execution time: 1_061_000 picoseconds. + Weight::from_parts(1_188_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 301_000 picoseconds. - Weight::from_parts(345_000, 0) + // Minimum execution time: 303_000 picoseconds. + Weight::from_parts(349_000, 0) } fn weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_115_000 picoseconds. - Weight::from_parts(1_232_000, 0) + // Minimum execution time: 1_143_000 picoseconds. + Weight::from_parts(1_283_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: - // Measured: `140` + // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_868_000 picoseconds. - Weight::from_parts(6_235_000, 0) + // Minimum execution time: 1_827_000 picoseconds. + Weight::from_parts(1_929_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `538` + // Measured: `610` // Estimated: `0` - // Minimum execution time: 12_767_000 picoseconds. - Weight::from_parts(13_413_000, 0) + // Minimum execution time: 13_541_000 picoseconds. + Weight::from_parts(14_240_000, 0) } /// Storage: `Revive::OriginalAccount` (r:1 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) @@ -653,8 +655,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `884` // Estimated: `4349` - // Minimum execution time: 20_635_000 picoseconds. - Weight::from_parts(21_382_000, 4349) + // Minimum execution time: 20_559_000 picoseconds. + Weight::from_parts(21_367_000, 4349) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -664,10 +666,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `304 + n * (1 ±0)` // Estimated: `3769 + n * (1 ±0)` - // Minimum execution time: 6_022_000 picoseconds. - Weight::from_parts(8_663_240, 3769) - // Standard Error: 13 - .saturating_add(Weight::from_parts(530, 0).saturating_mul(n.into())) + // Minimum execution time: 5_923_000 picoseconds. + Weight::from_parts(6_753_149, 3769) + // Standard Error: 5 + .saturating_add(Weight::from_parts(523, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -678,67 +680,67 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_996_000 picoseconds. - Weight::from_parts(2_269_324, 0) + // Minimum execution time: 2_101_000 picoseconds. + Weight::from_parts(2_465_888, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(488, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(472, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 281_000 picoseconds. - Weight::from_parts(326_000, 0) + // Minimum execution time: 293_000 picoseconds. + Weight::from_parts(319_000, 0) } fn minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_294_000 picoseconds. - Weight::from_parts(1_440_000, 0) + // Minimum execution time: 1_322_000 picoseconds. + Weight::from_parts(1_476_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 294_000 picoseconds. - Weight::from_parts(329_000, 0) + // Minimum execution time: 287_000 picoseconds. + Weight::from_parts(319_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(314_000, 0) + Weight::from_parts(330_000, 0) } fn seal_gas_limit() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_754_000 picoseconds. - Weight::from_parts(1_890_000, 0) + // Minimum execution time: 293_000 picoseconds. + Weight::from_parts(339_000, 0) } fn seal_gas_price() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 956_000 picoseconds. - Weight::from_parts(1_071_000, 0) + // Minimum execution time: 986_000 picoseconds. + Weight::from_parts(1_085_000, 0) } fn seal_base_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_110_000 picoseconds. - Weight::from_parts(1_235_000, 0) + // Minimum execution time: 974_000 picoseconds. + Weight::from_parts(1_057_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 286_000 picoseconds. - Weight::from_parts(335_000, 0) + // Minimum execution time: 285_000 picoseconds. + Weight::from_parts(330_000, 0) } /// Storage: `Session::Validators` (r:1 w:0) /// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -746,8 +748,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `141` // Estimated: `1626` - // Minimum execution time: 22_075_000 picoseconds. - Weight::from_parts(22_522_000, 1626) + // Minimum execution time: 21_803_000 picoseconds. + Weight::from_parts(22_360_000, 1626) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::BlockHash` (r:1 w:0) @@ -756,41 +758,41 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `318` // Estimated: `3783` - // Minimum execution time: 7_711_000 picoseconds. - Weight::from_parts(8_210_000, 3783) + // Minimum execution time: 5_906_000 picoseconds. + Weight::from_parts(6_201_000, 3783) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 294_000 picoseconds. - Weight::from_parts(334_000, 0) + // Minimum execution time: 307_000 picoseconds. + Weight::from_parts(347_000, 0) } /// The range of component `n` is `[0, 1048572]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 440_000 picoseconds. - Weight::from_parts(356_536, 0) + // Minimum execution time: 453_000 picoseconds. + Weight::from_parts(489_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(203, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 277_000 picoseconds. - Weight::from_parts(310_000, 0) + // Minimum execution time: 289_000 picoseconds. + Weight::from_parts(343_000, 0) } /// The range of component `n` is `[0, 1048576]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 273_000 picoseconds. - Weight::from_parts(166_951, 0) + // Minimum execution time: 280_000 picoseconds. + Weight::from_parts(496_576, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) } @@ -799,20 +801,20 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 295_000 picoseconds. - Weight::from_parts(558_480, 0) + // Minimum execution time: 305_000 picoseconds. + Weight::from_parts(529_465, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(199, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) } /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 831_000 picoseconds. - Weight::from_parts(976_751, 0) - // Standard Error: 8_914 - .saturating_add(Weight::from_parts(158_748, 0).saturating_mul(r.into())) + // Minimum execution time: 927_000 picoseconds. + Weight::from_parts(1_059_312, 0) + // Standard Error: 6_858 + .saturating_add(Weight::from_parts(13_287, 0).saturating_mul(r.into())) } /// Storage: `Revive::DeletionQueueCounter` (r:1 w:1) /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) @@ -830,10 +832,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) fn seal_terminate_logic() -> Weight { // Proof Size summary in bytes: - // Measured: `978` - // Estimated: `6918` - // Minimum execution time: 116_713_000 picoseconds. - Weight::from_parts(119_806_000, 6918) + // Measured: `1050` + // Estimated: `6990` + // Minimum execution time: 118_234_000 picoseconds. + Weight::from_parts(122_191_000, 6990) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(8_u64)) } @@ -843,10 +845,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_940_000 picoseconds. - Weight::from_parts(5_263_000, 0) + // Minimum execution time: 5_221_000 picoseconds. + Weight::from_parts(5_319_000, 0) // Standard Error: 4 - .saturating_add(Weight::from_parts(1_190, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_209, 0).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -854,8 +856,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `648` // Estimated: `648` - // Minimum execution time: 9_019_000 picoseconds. - Weight::from_parts(9_631_000, 648) + // Minimum execution time: 7_453_000 picoseconds. + Weight::from_parts(7_862_000, 648) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -864,8 +866,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10658` // Estimated: `10658` - // Minimum execution time: 41_438_000 picoseconds. - Weight::from_parts(42_549_000, 10658) + // Minimum execution time: 41_255_000 picoseconds. + Weight::from_parts(42_397_000, 10658) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -874,8 +876,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `648` // Estimated: `648` - // Minimum execution time: 10_402_000 picoseconds. - Weight::from_parts(11_014_000, 648) + // Minimum execution time: 8_727_000 picoseconds. + Weight::from_parts(9_104_000, 648) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -885,8 +887,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10658` // Estimated: `10658` - // Minimum execution time: 43_813_000 picoseconds. - Weight::from_parts(45_432_000, 10658) + // Minimum execution time: 43_313_000 picoseconds. + Weight::from_parts(44_570_000, 10658) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -898,12 +900,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_316_000 picoseconds. - Weight::from_parts(11_278_881, 247) - // Standard Error: 127 - .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) - // Standard Error: 127 - .saturating_add(Weight::from_parts(2_288, 0).saturating_mul(o.into())) + // Minimum execution time: 9_291_000 picoseconds. + Weight::from_parts(10_116_310, 247) + // Standard Error: 56 + .saturating_add(Weight::from_parts(562, 0).saturating_mul(n.into())) + // Standard Error: 56 + .saturating_add(Weight::from_parts(766, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -911,14 +913,12 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `n` is `[0, 416]`. - fn clear_storage(n: u32, ) -> Weight { + fn clear_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `376` // Estimated: `376` - // Minimum execution time: 13_112_000 picoseconds. - Weight::from_parts(14_049_292, 376) - // Standard Error: 80 - .saturating_add(Weight::from_parts(382, 0).saturating_mul(n.into())) + // Minimum execution time: 11_317_000 picoseconds. + Weight::from_parts(12_313_550, 376) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -929,10 +929,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_429_000 picoseconds. - Weight::from_parts(11_026_976, 247) - // Standard Error: 227 - .saturating_add(Weight::from_parts(3_390, 0).saturating_mul(n.into())) + // Minimum execution time: 8_259_000 picoseconds. + Weight::from_parts(9_579_511, 247) + // Standard Error: 89 + .saturating_add(Weight::from_parts(1_569, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -943,18 +943,20 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_260_000 picoseconds. - Weight::from_parts(3_618_857, 0) + // Minimum execution time: 3_296_000 picoseconds. + Weight::from_parts(3_670_971, 0) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `n` is `[0, 416]`. - fn take_storage(_n: u32, ) -> Weight { + fn take_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `376` // Estimated: `376` - // Minimum execution time: 13_287_000 picoseconds. - Weight::from_parts(14_651_006, 376) + // Minimum execution time: 11_660_000 picoseconds. + Weight::from_parts(12_798_428, 376) + // Standard Error: 94 + .saturating_add(Weight::from_parts(571, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -962,36 +964,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_585_000 picoseconds. - Weight::from_parts(1_672_000, 0) + // Minimum execution time: 1_652_000 picoseconds. + Weight::from_parts(1_749_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_924_000 picoseconds. - Weight::from_parts(2_064_000, 0) + // Minimum execution time: 1_986_000 picoseconds. + Weight::from_parts(2_063_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_568_000 picoseconds. - Weight::from_parts(1_656_000, 0) + // Minimum execution time: 1_596_000 picoseconds. + Weight::from_parts(1_702_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_762_000 picoseconds. - Weight::from_parts(1_905_000, 0) + // Minimum execution time: 1_786_000 picoseconds. + Weight::from_parts(1_893_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_186_000 picoseconds. - Weight::from_parts(1_263_000, 0) + // Minimum execution time: 1_252_000 picoseconds. + Weight::from_parts(1_339_000, 0) } /// The range of component `n` is `[0, 416]`. /// The range of component `o` is `[0, 416]`. @@ -999,48 +1001,48 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_295_000 picoseconds. - Weight::from_parts(2_718_034, 0) - // Standard Error: 19 - .saturating_add(Weight::from_parts(51, 0).saturating_mul(n.into())) - // Standard Error: 19 - .saturating_add(Weight::from_parts(220, 0).saturating_mul(o.into())) + // Minimum execution time: 2_331_000 picoseconds. + Weight::from_parts(2_642_531, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(234, 0).saturating_mul(n.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(356, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 416]`. - fn seal_clear_transient_storage(_n: u32, ) -> Weight { + fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_895_000 picoseconds. - Weight::from_parts(4_302_579, 0) + // Minimum execution time: 3_897_000 picoseconds. + Weight::from_parts(4_214_505, 0) + // Standard Error: 34 + .saturating_add(Weight::from_parts(192, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 416]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_994_000 picoseconds. - Weight::from_parts(2_298_129, 0) - // Standard Error: 36 - .saturating_add(Weight::from_parts(149, 0).saturating_mul(n.into())) + // Minimum execution time: 2_008_000 picoseconds. + Weight::from_parts(2_260_857, 0) + // Standard Error: 19 + .saturating_add(Weight::from_parts(334, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 416]`. fn seal_contains_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_251_000 picoseconds. - Weight::from_parts(3_627_761, 0) + // Minimum execution time: 3_387_000 picoseconds. + Weight::from_parts(3_823_784, 0) } /// The range of component `n` is `[0, 416]`. - fn seal_take_transient_storage(n: u32, ) -> Weight { + fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_042_000 picoseconds. - Weight::from_parts(4_418_798, 0) - // Standard Error: 29 - .saturating_add(Weight::from_parts(60, 0).saturating_mul(n.into())) + // Minimum execution time: 4_105_000 picoseconds. + Weight::from_parts(4_590_927, 0) } /// Storage: `Revive::OriginalAccount` (r:1 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) @@ -1057,16 +1059,16 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 1048576]`. fn seal_call(t: u32, d: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1969` - // Estimated: `5434` - // Minimum execution time: 93_866_000 picoseconds. - Weight::from_parts(77_283_811, 5434) - // Standard Error: 171_206 - .saturating_add(Weight::from_parts(18_282_578, 0).saturating_mul(t.into())) - // Standard Error: 171_206 - .saturating_add(Weight::from_parts(23_290_286, 0).saturating_mul(d.into())) + // Measured: `2129` + // Estimated: `5594` + // Minimum execution time: 96_945_000 picoseconds. + Weight::from_parts(82_723_058, 5594) + // Standard Error: 197_185 + .saturating_add(Weight::from_parts(17_112_972, 0).saturating_mul(t.into())) + // Standard Error: 197_185 + .saturating_add(Weight::from_parts(23_554_105, 0).saturating_mul(d.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) @@ -1079,17 +1081,17 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 130972]`. fn seal_call_precompile(d: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `436 + d * (250 ±0)` - // Estimated: `2075 + d * (2076 ±0)` - // Minimum execution time: 26_692_000 picoseconds. - Weight::from_parts(15_618_453, 2075) - // Standard Error: 37_997 - .saturating_add(Weight::from_parts(12_721_428, 0).saturating_mul(d.into())) + // Measured: `436 + d * (212 ±0)` + // Estimated: `2056 + d * (2056 ±0)` + // Minimum execution time: 26_713_000 picoseconds. + Weight::from_parts(15_979_369, 2056) + // Standard Error: 53_691 + .saturating_add(Weight::from_parts(11_856_790, 0).saturating_mul(d.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(321, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(326, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(d.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(d.into()))) - .saturating_add(Weight::from_parts(0, 2076).saturating_mul(d.into())) + .saturating_add(Weight::from_parts(0, 2056).saturating_mul(d.into())) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) @@ -1101,8 +1103,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1434` // Estimated: `4899` - // Minimum execution time: 34_108_000 picoseconds. - Weight::from_parts(35_130_000, 4899) + // Minimum execution time: 35_156_000 picoseconds. + Weight::from_parts(36_008_000, 4899) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1118,18 +1120,20 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 131072]`. fn seal_instantiate(t: u32, d: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1464` - // Estimated: `4937` - // Minimum execution time: 154_945_000 picoseconds. - Weight::from_parts(108_508_437, 4937) - // Standard Error: 551_551 - .saturating_add(Weight::from_parts(20_945_320, 0).saturating_mul(t.into())) - // Standard Error: 551_551 - .saturating_add(Weight::from_parts(30_650_720, 0).saturating_mul(d.into())) - // Standard Error: 6 - .saturating_add(Weight::from_parts(3_953, 0).saturating_mul(i.into())) + // Measured: `1484` + // Estimated: `4921 + d * (31 ±1) + t * (31 ±1)` + // Minimum execution time: 158_623_000 picoseconds. + Weight::from_parts(119_635_514, 4921) + // Standard Error: 509_096 + .saturating_add(Weight::from_parts(17_183_470, 0).saturating_mul(t.into())) + // Standard Error: 509_096 + .saturating_add(Weight::from_parts(27_676_190, 0).saturating_mul(d.into())) + // Standard Error: 5 + .saturating_add(Weight::from_parts(3_929, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 31).saturating_mul(d.into())) + .saturating_add(Weight::from_parts(0, 31).saturating_mul(t.into())) } /// Storage: `Revive::AccountInfoOf` (r:1 w:1) /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) @@ -1146,16 +1150,16 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[10240, 49152]`. fn evm_instantiate(t: u32, d: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `870` - // Estimated: `6810` - // Minimum execution time: 347_011_000 picoseconds. - Weight::from_parts(270_222_660, 6810) - // Standard Error: 629_049 - .saturating_add(Weight::from_parts(17_216_349, 0).saturating_mul(t.into())) - // Standard Error: 629_049 - .saturating_add(Weight::from_parts(26_158_441, 0).saturating_mul(d.into())) - // Standard Error: 24 - .saturating_add(Weight::from_parts(3_843, 0).saturating_mul(i.into())) + // Measured: `869` + // Estimated: `6829` + // Minimum execution time: 375_311_000 picoseconds. + Weight::from_parts(238_984_394, 6829) + // Standard Error: 687_857 + .saturating_add(Weight::from_parts(21_371_046, 0).saturating_mul(t.into())) + // Standard Error: 687_857 + .saturating_add(Weight::from_parts(28_395_391, 0).saturating_mul(d.into())) + // Standard Error: 27 + .saturating_add(Weight::from_parts(8_472, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -1164,125 +1168,125 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_343_000 picoseconds. - Weight::from_parts(10_611_030, 0) + // Minimum execution time: 1_346_000 picoseconds. + Weight::from_parts(9_536_684, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(1_242, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_239, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn identity(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 830_000 picoseconds. - Weight::from_parts(819_424, 0) + // Minimum execution time: 799_000 picoseconds. + Weight::from_parts(759_415, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(112, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn ripemd_160(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_484_000 picoseconds. - Weight::from_parts(1_587_000, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(3_763, 0).saturating_mul(n.into())) + // Minimum execution time: 1_377_000 picoseconds. + Weight::from_parts(6_797_388, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(3_733, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_156_000 picoseconds. - Weight::from_parts(14_579_377, 0) + // Minimum execution time: 1_129_000 picoseconds. + Weight::from_parts(14_992_965, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(3_531, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_543, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_917_000 picoseconds. - Weight::from_parts(12_777_727, 0) + // Minimum execution time: 1_873_000 picoseconds. + Weight::from_parts(13_180_808, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(1_400, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_398, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_855_000 picoseconds. - Weight::from_parts(15_746_302, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_397, 0).saturating_mul(n.into())) + // Minimum execution time: 1_854_000 picoseconds. + Weight::from_parts(12_306_060, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_400, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048321]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 45_079_000 picoseconds. - Weight::from_parts(83_414_280, 0) + // Minimum execution time: 47_104_000 picoseconds. + Weight::from_parts(80_861_678, 0) // Standard Error: 4 - .saturating_add(Weight::from_parts(4_826, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(4_830, 0).saturating_mul(n.into())) } fn ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_987_000 picoseconds. - Weight::from_parts(48_111_000, 0) + // Minimum execution time: 46_698_000 picoseconds. + Weight::from_parts(47_673_000, 0) } fn p256_verify() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_787_561_000 picoseconds. - Weight::from_parts(1_798_471_000, 0) + // Minimum execution time: 1_791_033_000 picoseconds. + Weight::from_parts(1_801_147_000, 0) } fn bn128_add() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 15_130_000 picoseconds. - Weight::from_parts(16_755_000, 0) + // Minimum execution time: 14_836_000 picoseconds. + Weight::from_parts(16_004_000, 0) } fn bn128_mul() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 988_607_000 picoseconds. - Weight::from_parts(991_668_000, 0) + // Minimum execution time: 979_455_000 picoseconds. + Weight::from_parts(988_686_000, 0) } /// The range of component `n` is `[0, 20]`. fn bn128_pairing(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 915_000 picoseconds. - Weight::from_parts(4_888_094_516, 0) - // Standard Error: 10_384_426 - .saturating_add(Weight::from_parts(5_943_011_353, 0).saturating_mul(n.into())) + // Minimum execution time: 938_000 picoseconds. + Weight::from_parts(4_848_308_436, 0) + // Standard Error: 10_255_035 + .saturating_add(Weight::from_parts(5_920_112_189, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1200]`. fn blake2f(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_043_000 picoseconds. - Weight::from_parts(1_345_971, 0) - // Standard Error: 33 - .saturating_add(Weight::from_parts(28_987, 0).saturating_mul(n.into())) + // Minimum execution time: 1_070_000 picoseconds. + Weight::from_parts(1_284_385, 0) + // Standard Error: 54 + .saturating_add(Weight::from_parts(28_398, 0).saturating_mul(n.into())) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_002_000 picoseconds. - Weight::from_parts(13_201_000, 0) + // Minimum execution time: 13_180_000 picoseconds. + Weight::from_parts(13_387_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:2 w:2) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) @@ -1297,10 +1301,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `424 + r * (434 ±0)` // Estimated: `6364 + r * (2162 ±0)` - // Minimum execution time: 14_885_000 picoseconds. - Weight::from_parts(16_051_320, 6364) - // Standard Error: 57_553 - .saturating_add(Weight::from_parts(46_501_379, 0).saturating_mul(r.into())) + // Minimum execution time: 14_799_000 picoseconds. + Weight::from_parts(15_922_167, 6364) + // Standard Error: 52_549 + .saturating_add(Weight::from_parts(47_258_332, 0).saturating_mul(r.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(r.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -1312,30 +1316,30 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 558_000 picoseconds. - Weight::from_parts(624_652, 0) - // Standard Error: 66 - .saturating_add(Weight::from_parts(14_757, 0).saturating_mul(r.into())) + // Minimum execution time: 550_000 picoseconds. + Weight::from_parts(1_050_932, 0) + // Standard Error: 21 + .saturating_add(Weight::from_parts(15_280, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 10000]`. fn instr(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_194_000 picoseconds. - Weight::from_parts(53_119_928, 0) - // Standard Error: 232 - .saturating_add(Weight::from_parts(129_578, 0).saturating_mul(r.into())) + // Minimum execution time: 12_061_000 picoseconds. + Weight::from_parts(62_740_927, 0) + // Standard Error: 361 + .saturating_add(Weight::from_parts(123_285, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 10000]`. fn instr_empty_loop(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_177_000 picoseconds. - Weight::from_parts(1_872_649, 0) - // Standard Error: 57 - .saturating_add(Weight::from_parts(74_048, 0).saturating_mul(r.into())) + // Minimum execution time: 3_326_000 picoseconds. + Weight::from_parts(3_151_099, 0) + // Standard Error: 50 + .saturating_add(Weight::from_parts(74_055, 0).saturating_mul(r.into())) } /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1344,10 +1348,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `527 + n * (1 ±0)` // Estimated: `3992 + n * (1 ±0)` - // Minimum execution time: 14_619_000 picoseconds. - Weight::from_parts(14_803_675, 3992) - // Standard Error: 5 - .saturating_add(Weight::from_parts(747, 0).saturating_mul(n.into())) + // Minimum execution time: 14_656_000 picoseconds. + Weight::from_parts(14_686_037, 3992) + // Standard Error: 4 + .saturating_add(Weight::from_parts(724, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1359,8 +1363,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `382` // Estimated: `6322` - // Minimum execution time: 12_485_000 picoseconds. - Weight::from_parts(12_931_000, 6322) + // Minimum execution time: 12_331_000 picoseconds. + Weight::from_parts(12_868_000, 6322) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -1374,8 +1378,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `505` // Estimated: `6866` - // Minimum execution time: 64_237_000 picoseconds. - Weight::from_parts(66_071_000, 6866) + // Minimum execution time: 63_102_000 picoseconds. + Weight::from_parts(65_300_000, 6866) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -1396,10 +1400,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3012 + n * (97 ±0)` // Estimated: `6303 + n * (104 ±0)` - // Minimum execution time: 28_116_000 picoseconds. - Weight::from_parts(55_446_109, 6303) - // Standard Error: 4_382 - .saturating_add(Weight::from_parts(514_927, 0).saturating_mul(n.into())) + // Minimum execution time: 26_639_000 picoseconds. + Weight::from_parts(56_482_010, 6303) + // Standard Error: 4_704 + .saturating_add(Weight::from_parts(522_203, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) .saturating_add(Weight::from_parts(0, 104).saturating_mul(n.into())) @@ -1421,10 +1425,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3577 + d * (3 ±0)` // Estimated: `7036 + d * (3 ±0)` - // Minimum execution time: 59_544_000 picoseconds. - Weight::from_parts(61_174_055, 7036) - // Standard Error: 129 - .saturating_add(Weight::from_parts(12_909, 0).saturating_mul(d.into())) + // Minimum execution time: 59_432_000 picoseconds. + Weight::from_parts(61_955_716, 7036) + // Standard Error: 157 + .saturating_add(Weight::from_parts(12_327, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) .saturating_add(Weight::from_parts(0, 3).saturating_mul(d.into())) @@ -1448,10 +1452,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1546` // Estimated: `5011` - // Minimum execution time: 45_140_000 picoseconds. - Weight::from_parts(46_928_768, 5011) - // Standard Error: 1_998 - .saturating_add(Weight::from_parts(3_639, 0).saturating_mul(e.into())) + // Minimum execution time: 44_652_000 picoseconds. + Weight::from_parts(46_489_107, 5011) + // Standard Error: 1_089 + .saturating_add(Weight::from_parts(4_596, 0).saturating_mul(e.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -1474,10 +1478,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1546` // Estimated: `5011` - // Minimum execution time: 44_893_000 picoseconds. - Weight::from_parts(46_639_493, 5011) + // Minimum execution time: 44_400_000 picoseconds. + Weight::from_parts(46_629_995, 5011) // Standard Error: 6 - .saturating_add(Weight::from_parts(31, 0).saturating_mul(d.into())) + .saturating_add(Weight::from_parts(21, 0).saturating_mul(d.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -1491,8 +1495,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `213` // Estimated: `1698` - // Minimum execution time: 3_212_000 picoseconds. - Weight::from_parts(3_435_000, 1698) + // Minimum execution time: 3_325_000 picoseconds. + Weight::from_parts(3_509_000, 1698) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1502,10 +1506,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `491 + k * (69 ±0)` // Estimated: `481 + k * (70 ±0)` - // Minimum execution time: 14_823_000 picoseconds. - Weight::from_parts(14_976_000, 481) - // Standard Error: 1_014 - .saturating_add(Weight::from_parts(1_198_198, 0).saturating_mul(k.into())) + // Minimum execution time: 14_389_000 picoseconds. + Weight::from_parts(15_127_000, 481) + // Standard Error: 1_039 + .saturating_add(Weight::from_parts(1_209_966, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1529,10 +1533,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1265 + c * (1 ±0)` // Estimated: `7200 + c * (1 ±0)` - // Minimum execution time: 95_906_000 picoseconds. - Weight::from_parts(137_008_318, 7200) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_477, 0).saturating_mul(c.into())) + // Minimum execution time: 100_357_000 picoseconds. + Weight::from_parts(143_652_444, 7200) + // Standard Error: 12 + .saturating_add(Weight::from_parts(1_441, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) @@ -1554,10 +1558,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1205` // Estimated: `7144` - // Minimum execution time: 89_753_000 picoseconds. - Weight::from_parts(93_826_418, 7144) - // Standard Error: 17 - .saturating_add(Weight::from_parts(113, 0).saturating_mul(c.into())) + // Minimum execution time: 92_748_000 picoseconds. + Weight::from_parts(97_747_165, 7144) + // Standard Error: 24 + .saturating_add(Weight::from_parts(46, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1574,12 +1578,14 @@ impl WeightInfo for () { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `b` is `[0, 1]`. - fn basic_block_compilation(_b: u32, ) -> Weight { + fn basic_block_compilation(b: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `4609` // Estimated: `10549` - // Minimum execution time: 140_658_000 picoseconds. - Weight::from_parts(145_253_853, 10549) + // Minimum execution time: 144_014_000 picoseconds. + Weight::from_parts(149_812_683, 10549) + // Standard Error: 724_905 + .saturating_add(Weight::from_parts(1_749_116, 0).saturating_mul(b.into())) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1603,12 +1609,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `994` // Estimated: `6924` - // Minimum execution time: 761_511_000 picoseconds. - Weight::from_parts(84_218_529, 6924) - // Standard Error: 36 - .saturating_add(Weight::from_parts(20_410, 0).saturating_mul(c.into())) - // Standard Error: 28 - .saturating_add(Weight::from_parts(4_874, 0).saturating_mul(i.into())) + // Minimum execution time: 773_080_000 picoseconds. + Weight::from_parts(70_290_148, 6924) + // Standard Error: 37 + .saturating_add(Weight::from_parts(20_365, 0).saturating_mul(c.into())) + // Standard Error: 29 + .saturating_add(Weight::from_parts(5_006, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1637,14 +1643,14 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `994` // Estimated: `6934` - // Minimum execution time: 370_631_000 picoseconds. - Weight::from_parts(241_842_187, 6934) - // Standard Error: 56 - .saturating_add(Weight::from_parts(16_174, 0).saturating_mul(c.into())) - // Standard Error: 43 - .saturating_add(Weight::from_parts(679, 0).saturating_mul(i.into())) - // Standard Error: 3_661_614 - .saturating_add(Weight::from_parts(12_103_675, 0).saturating_mul(d.into())) + // Minimum execution time: 405_718_000 picoseconds. + Weight::from_parts(270_737_149, 6934) + // Standard Error: 44 + .saturating_add(Weight::from_parts(15_826, 0).saturating_mul(c.into())) + // Standard Error: 34 + .saturating_add(Weight::from_parts(660, 0).saturating_mul(i.into())) + // Standard Error: 2_902_398 + .saturating_add(Weight::from_parts(8_450_702, 0).saturating_mul(d.into())) .saturating_add(RocksDbWeight::get().reads(10_u64)) .saturating_add(RocksDbWeight::get().writes(10_u64)) } @@ -1652,8 +1658,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_071_000 picoseconds. - Weight::from_parts(3_232_000, 0) + // Minimum execution time: 3_011_000 picoseconds. + Weight::from_parts(3_274_000, 0) } /// Storage: `Revive::AccountInfoOf` (r:2 w:1) /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) @@ -1673,11 +1679,11 @@ impl WeightInfo for () { fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `1735` - // Estimated: `7659` - // Minimum execution time: 186_506_000 picoseconds. - Weight::from_parts(190_843_676, 7659) + // Estimated: `7665` + // Minimum execution time: 187_515_000 picoseconds. + Weight::from_parts(194_934_584, 7665) // Standard Error: 11 - .saturating_add(Weight::from_parts(4_180, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_151, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1695,10 +1701,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1958` - // Estimated: `7898` - // Minimum execution time: 99_804_000 picoseconds. - Weight::from_parts(101_727_000, 7898) + // Measured: `1947` + // Estimated: `7887` + // Minimum execution time: 104_210_000 picoseconds. + Weight::from_parts(110_220_000, 7887) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1721,12 +1727,12 @@ impl WeightInfo for () { /// The range of component `d` is `[0, 1]`. fn eth_call(d: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1958` - // Estimated: `7898` - // Minimum execution time: 162_250_000 picoseconds. - Weight::from_parts(168_112_555, 7898) - // Standard Error: 549_643 - .saturating_add(Weight::from_parts(4_435_744, 0).saturating_mul(d.into())) + // Measured: `1947` + // Estimated: `7887` + // Minimum execution time: 179_230_000 picoseconds. + Weight::from_parts(189_040_259, 7887) + // Standard Error: 869_758 + .saturating_add(Weight::from_parts(4_172_840, 0).saturating_mul(d.into())) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1743,10 +1749,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3823` - // Minimum execution time: 29_050_000 picoseconds. - Weight::from_parts(23_929_217, 3823) - // Standard Error: 13 - .saturating_add(Weight::from_parts(6_243, 0).saturating_mul(c.into())) + // Minimum execution time: 28_233_000 picoseconds. + Weight::from_parts(22_905_261, 3823) + // Standard Error: 12 + .saturating_add(Weight::from_parts(6_274, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1763,10 +1769,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `392` // Estimated: `3857` - // Minimum execution time: 60_288_000 picoseconds. - Weight::from_parts(52_801_907, 3857) - // Standard Error: 19 - .saturating_add(Weight::from_parts(14_346, 0).saturating_mul(c.into())) + // Minimum execution time: 59_732_000 picoseconds. + Weight::from_parts(53_002_061, 3857) + // Standard Error: 17 + .saturating_add(Weight::from_parts(14_107, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1780,8 +1786,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `524` // Estimated: `3989` - // Minimum execution time: 53_434_000 picoseconds. - Weight::from_parts(55_152_000, 3989) + // Minimum execution time: 53_247_000 picoseconds. + Weight::from_parts(54_239_000, 3989) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1797,10 +1803,10 @@ impl WeightInfo for () { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `867` - // Estimated: `6807` - // Minimum execution time: 66_946_000 picoseconds. - Weight::from_parts(69_226_000, 6807) + // Measured: `833` + // Estimated: `6773` + // Minimum execution time: 67_638_000 picoseconds. + Weight::from_parts(69_822_000, 6773) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1814,8 +1820,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `623` // Estimated: `4088` - // Minimum execution time: 60_387_000 picoseconds. - Weight::from_parts(62_012_000, 4088) + // Minimum execution time: 60_308_000 picoseconds. + Weight::from_parts(61_865_000, 4088) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1827,8 +1833,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `93` // Estimated: `3558` - // Minimum execution time: 40_203_000 picoseconds. - Weight::from_parts(40_786_000, 3558) + // Minimum execution time: 40_748_000 picoseconds. + Weight::from_parts(41_916_000, 3558) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1842,8 +1848,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `381` // Estimated: `3846` - // Minimum execution time: 19_128_000 picoseconds. - Weight::from_parts(19_977_000, 3846) + // Minimum execution time: 19_441_000 picoseconds. + Weight::from_parts(19_775_000, 3846) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1851,24 +1857,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_560_000 picoseconds. - Weight::from_parts(9_060_136, 0) - // Standard Error: 289 - .saturating_add(Weight::from_parts(177_740, 0).saturating_mul(r.into())) + // Minimum execution time: 8_031_000 picoseconds. + Weight::from_parts(9_266_076, 0) + // Standard Error: 205 + .saturating_add(Weight::from_parts(186_444, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 335_000 picoseconds. - Weight::from_parts(382_000, 0) + // Minimum execution time: 377_000 picoseconds. + Weight::from_parts(403_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 310_000 picoseconds. - Weight::from_parts(343_000, 0) + // Minimum execution time: 351_000 picoseconds. + Weight::from_parts(395_000, 0) } /// Storage: `Revive::OriginalAccount` (r:1 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) @@ -1876,8 +1882,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `623` // Estimated: `4088` - // Minimum execution time: 11_432_000 picoseconds. - Weight::from_parts(12_236_000, 4088) + // Minimum execution time: 11_287_000 picoseconds. + Weight::from_parts(12_022_000, 4088) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) @@ -1886,16 +1892,16 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 9_959_000 picoseconds. - Weight::from_parts(10_217_000, 3938) + // Minimum execution time: 9_933_000 picoseconds. + Weight::from_parts(10_470_000, 3938) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `436` // Estimated: `0` - // Minimum execution time: 9_727_000 picoseconds. - Weight::from_parts(10_186_000, 0) + // Minimum execution time: 9_614_000 picoseconds. + Weight::from_parts(10_109_000, 0) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) @@ -1905,51 +1911,51 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `545` // Estimated: `4010` - // Minimum execution time: 13_769_000 picoseconds. - Weight::from_parts(14_300_000, 4010) + // Minimum execution time: 13_599_000 picoseconds. + Weight::from_parts(14_148_000, 4010) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_199_000 picoseconds. - Weight::from_parts(1_310_000, 0) + // Minimum execution time: 1_161_000 picoseconds. + Weight::from_parts(1_257_000, 0) } fn caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_110_000 picoseconds. - Weight::from_parts(1_253_000, 0) + // Minimum execution time: 1_061_000 picoseconds. + Weight::from_parts(1_188_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 301_000 picoseconds. - Weight::from_parts(345_000, 0) + // Minimum execution time: 303_000 picoseconds. + Weight::from_parts(349_000, 0) } fn weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_115_000 picoseconds. - Weight::from_parts(1_232_000, 0) + // Minimum execution time: 1_143_000 picoseconds. + Weight::from_parts(1_283_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: - // Measured: `140` + // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_868_000 picoseconds. - Weight::from_parts(6_235_000, 0) + // Minimum execution time: 1_827_000 picoseconds. + Weight::from_parts(1_929_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `538` + // Measured: `610` // Estimated: `0` - // Minimum execution time: 12_767_000 picoseconds. - Weight::from_parts(13_413_000, 0) + // Minimum execution time: 13_541_000 picoseconds. + Weight::from_parts(14_240_000, 0) } /// Storage: `Revive::OriginalAccount` (r:1 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) @@ -1961,8 +1967,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `884` // Estimated: `4349` - // Minimum execution time: 20_635_000 picoseconds. - Weight::from_parts(21_382_000, 4349) + // Minimum execution time: 20_559_000 picoseconds. + Weight::from_parts(21_367_000, 4349) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1972,10 +1978,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `304 + n * (1 ±0)` // Estimated: `3769 + n * (1 ±0)` - // Minimum execution time: 6_022_000 picoseconds. - Weight::from_parts(8_663_240, 3769) - // Standard Error: 13 - .saturating_add(Weight::from_parts(530, 0).saturating_mul(n.into())) + // Minimum execution time: 5_923_000 picoseconds. + Weight::from_parts(6_753_149, 3769) + // Standard Error: 5 + .saturating_add(Weight::from_parts(523, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1986,67 +1992,67 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_996_000 picoseconds. - Weight::from_parts(2_269_324, 0) + // Minimum execution time: 2_101_000 picoseconds. + Weight::from_parts(2_465_888, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(488, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(472, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 281_000 picoseconds. - Weight::from_parts(326_000, 0) + // Minimum execution time: 293_000 picoseconds. + Weight::from_parts(319_000, 0) } fn minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_294_000 picoseconds. - Weight::from_parts(1_440_000, 0) + // Minimum execution time: 1_322_000 picoseconds. + Weight::from_parts(1_476_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 294_000 picoseconds. - Weight::from_parts(329_000, 0) + // Minimum execution time: 287_000 picoseconds. + Weight::from_parts(319_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(314_000, 0) + Weight::from_parts(330_000, 0) } fn seal_gas_limit() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_754_000 picoseconds. - Weight::from_parts(1_890_000, 0) + // Minimum execution time: 293_000 picoseconds. + Weight::from_parts(339_000, 0) } fn seal_gas_price() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 956_000 picoseconds. - Weight::from_parts(1_071_000, 0) + // Minimum execution time: 986_000 picoseconds. + Weight::from_parts(1_085_000, 0) } fn seal_base_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_110_000 picoseconds. - Weight::from_parts(1_235_000, 0) + // Minimum execution time: 974_000 picoseconds. + Weight::from_parts(1_057_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 286_000 picoseconds. - Weight::from_parts(335_000, 0) + // Minimum execution time: 285_000 picoseconds. + Weight::from_parts(330_000, 0) } /// Storage: `Session::Validators` (r:1 w:0) /// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -2054,8 +2060,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `141` // Estimated: `1626` - // Minimum execution time: 22_075_000 picoseconds. - Weight::from_parts(22_522_000, 1626) + // Minimum execution time: 21_803_000 picoseconds. + Weight::from_parts(22_360_000, 1626) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::BlockHash` (r:1 w:0) @@ -2064,41 +2070,41 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `318` // Estimated: `3783` - // Minimum execution time: 7_711_000 picoseconds. - Weight::from_parts(8_210_000, 3783) + // Minimum execution time: 5_906_000 picoseconds. + Weight::from_parts(6_201_000, 3783) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 294_000 picoseconds. - Weight::from_parts(334_000, 0) + // Minimum execution time: 307_000 picoseconds. + Weight::from_parts(347_000, 0) } /// The range of component `n` is `[0, 1048572]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 440_000 picoseconds. - Weight::from_parts(356_536, 0) + // Minimum execution time: 453_000 picoseconds. + Weight::from_parts(489_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(203, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 277_000 picoseconds. - Weight::from_parts(310_000, 0) + // Minimum execution time: 289_000 picoseconds. + Weight::from_parts(343_000, 0) } /// The range of component `n` is `[0, 1048576]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 273_000 picoseconds. - Weight::from_parts(166_951, 0) + // Minimum execution time: 280_000 picoseconds. + Weight::from_parts(496_576, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) } @@ -2107,20 +2113,20 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 295_000 picoseconds. - Weight::from_parts(558_480, 0) + // Minimum execution time: 305_000 picoseconds. + Weight::from_parts(529_465, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(199, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) } /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 831_000 picoseconds. - Weight::from_parts(976_751, 0) - // Standard Error: 8_914 - .saturating_add(Weight::from_parts(158_748, 0).saturating_mul(r.into())) + // Minimum execution time: 927_000 picoseconds. + Weight::from_parts(1_059_312, 0) + // Standard Error: 6_858 + .saturating_add(Weight::from_parts(13_287, 0).saturating_mul(r.into())) } /// Storage: `Revive::DeletionQueueCounter` (r:1 w:1) /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) @@ -2138,10 +2144,10 @@ impl WeightInfo for () { /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) fn seal_terminate_logic() -> Weight { // Proof Size summary in bytes: - // Measured: `978` - // Estimated: `6918` - // Minimum execution time: 116_713_000 picoseconds. - Weight::from_parts(119_806_000, 6918) + // Measured: `1050` + // Estimated: `6990` + // Minimum execution time: 118_234_000 picoseconds. + Weight::from_parts(122_191_000, 6990) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(8_u64)) } @@ -2151,10 +2157,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_940_000 picoseconds. - Weight::from_parts(5_263_000, 0) + // Minimum execution time: 5_221_000 picoseconds. + Weight::from_parts(5_319_000, 0) // Standard Error: 4 - .saturating_add(Weight::from_parts(1_190, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_209, 0).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -2162,8 +2168,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `648` // Estimated: `648` - // Minimum execution time: 9_019_000 picoseconds. - Weight::from_parts(9_631_000, 648) + // Minimum execution time: 7_453_000 picoseconds. + Weight::from_parts(7_862_000, 648) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -2172,8 +2178,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10658` // Estimated: `10658` - // Minimum execution time: 41_438_000 picoseconds. - Weight::from_parts(42_549_000, 10658) + // Minimum execution time: 41_255_000 picoseconds. + Weight::from_parts(42_397_000, 10658) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -2182,8 +2188,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `648` // Estimated: `648` - // Minimum execution time: 10_402_000 picoseconds. - Weight::from_parts(11_014_000, 648) + // Minimum execution time: 8_727_000 picoseconds. + Weight::from_parts(9_104_000, 648) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -2193,8 +2199,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10658` // Estimated: `10658` - // Minimum execution time: 43_813_000 picoseconds. - Weight::from_parts(45_432_000, 10658) + // Minimum execution time: 43_313_000 picoseconds. + Weight::from_parts(44_570_000, 10658) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -2206,12 +2212,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_316_000 picoseconds. - Weight::from_parts(11_278_881, 247) - // Standard Error: 127 - .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) - // Standard Error: 127 - .saturating_add(Weight::from_parts(2_288, 0).saturating_mul(o.into())) + // Minimum execution time: 9_291_000 picoseconds. + Weight::from_parts(10_116_310, 247) + // Standard Error: 56 + .saturating_add(Weight::from_parts(562, 0).saturating_mul(n.into())) + // Standard Error: 56 + .saturating_add(Weight::from_parts(766, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -2219,14 +2225,12 @@ impl WeightInfo for () { /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `n` is `[0, 416]`. - fn clear_storage(n: u32, ) -> Weight { + fn clear_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `376` // Estimated: `376` - // Minimum execution time: 13_112_000 picoseconds. - Weight::from_parts(14_049_292, 376) - // Standard Error: 80 - .saturating_add(Weight::from_parts(382, 0).saturating_mul(n.into())) + // Minimum execution time: 11_317_000 picoseconds. + Weight::from_parts(12_313_550, 376) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -2237,10 +2241,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_429_000 picoseconds. - Weight::from_parts(11_026_976, 247) - // Standard Error: 227 - .saturating_add(Weight::from_parts(3_390, 0).saturating_mul(n.into())) + // Minimum execution time: 8_259_000 picoseconds. + Weight::from_parts(9_579_511, 247) + // Standard Error: 89 + .saturating_add(Weight::from_parts(1_569, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -2251,18 +2255,20 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_260_000 picoseconds. - Weight::from_parts(3_618_857, 0) + // Minimum execution time: 3_296_000 picoseconds. + Weight::from_parts(3_670_971, 0) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `n` is `[0, 416]`. - fn take_storage(_n: u32, ) -> Weight { + fn take_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `376` // Estimated: `376` - // Minimum execution time: 13_287_000 picoseconds. - Weight::from_parts(14_651_006, 376) + // Minimum execution time: 11_660_000 picoseconds. + Weight::from_parts(12_798_428, 376) + // Standard Error: 94 + .saturating_add(Weight::from_parts(571, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -2270,36 +2276,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_585_000 picoseconds. - Weight::from_parts(1_672_000, 0) + // Minimum execution time: 1_652_000 picoseconds. + Weight::from_parts(1_749_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_924_000 picoseconds. - Weight::from_parts(2_064_000, 0) + // Minimum execution time: 1_986_000 picoseconds. + Weight::from_parts(2_063_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_568_000 picoseconds. - Weight::from_parts(1_656_000, 0) + // Minimum execution time: 1_596_000 picoseconds. + Weight::from_parts(1_702_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_762_000 picoseconds. - Weight::from_parts(1_905_000, 0) + // Minimum execution time: 1_786_000 picoseconds. + Weight::from_parts(1_893_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_186_000 picoseconds. - Weight::from_parts(1_263_000, 0) + // Minimum execution time: 1_252_000 picoseconds. + Weight::from_parts(1_339_000, 0) } /// The range of component `n` is `[0, 416]`. /// The range of component `o` is `[0, 416]`. @@ -2307,48 +2313,48 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_295_000 picoseconds. - Weight::from_parts(2_718_034, 0) - // Standard Error: 19 - .saturating_add(Weight::from_parts(51, 0).saturating_mul(n.into())) - // Standard Error: 19 - .saturating_add(Weight::from_parts(220, 0).saturating_mul(o.into())) + // Minimum execution time: 2_331_000 picoseconds. + Weight::from_parts(2_642_531, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(234, 0).saturating_mul(n.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(356, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 416]`. - fn seal_clear_transient_storage(_n: u32, ) -> Weight { + fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_895_000 picoseconds. - Weight::from_parts(4_302_579, 0) + // Minimum execution time: 3_897_000 picoseconds. + Weight::from_parts(4_214_505, 0) + // Standard Error: 34 + .saturating_add(Weight::from_parts(192, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 416]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_994_000 picoseconds. - Weight::from_parts(2_298_129, 0) - // Standard Error: 36 - .saturating_add(Weight::from_parts(149, 0).saturating_mul(n.into())) + // Minimum execution time: 2_008_000 picoseconds. + Weight::from_parts(2_260_857, 0) + // Standard Error: 19 + .saturating_add(Weight::from_parts(334, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 416]`. fn seal_contains_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_251_000 picoseconds. - Weight::from_parts(3_627_761, 0) + // Minimum execution time: 3_387_000 picoseconds. + Weight::from_parts(3_823_784, 0) } /// The range of component `n` is `[0, 416]`. - fn seal_take_transient_storage(n: u32, ) -> Weight { + fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_042_000 picoseconds. - Weight::from_parts(4_418_798, 0) - // Standard Error: 29 - .saturating_add(Weight::from_parts(60, 0).saturating_mul(n.into())) + // Minimum execution time: 4_105_000 picoseconds. + Weight::from_parts(4_590_927, 0) } /// Storage: `Revive::OriginalAccount` (r:1 w:0) /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `Measured`) @@ -2365,16 +2371,16 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 1048576]`. fn seal_call(t: u32, d: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1969` - // Estimated: `5434` - // Minimum execution time: 93_866_000 picoseconds. - Weight::from_parts(77_283_811, 5434) - // Standard Error: 171_206 - .saturating_add(Weight::from_parts(18_282_578, 0).saturating_mul(t.into())) - // Standard Error: 171_206 - .saturating_add(Weight::from_parts(23_290_286, 0).saturating_mul(d.into())) + // Measured: `2129` + // Estimated: `5594` + // Minimum execution time: 96_945_000 picoseconds. + Weight::from_parts(82_723_058, 5594) + // Standard Error: 197_185 + .saturating_add(Weight::from_parts(17_112_972, 0).saturating_mul(t.into())) + // Standard Error: 197_185 + .saturating_add(Weight::from_parts(23_554_105, 0).saturating_mul(d.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) @@ -2387,17 +2393,17 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 130972]`. fn seal_call_precompile(d: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `436 + d * (250 ±0)` - // Estimated: `2075 + d * (2076 ±0)` - // Minimum execution time: 26_692_000 picoseconds. - Weight::from_parts(15_618_453, 2075) - // Standard Error: 37_997 - .saturating_add(Weight::from_parts(12_721_428, 0).saturating_mul(d.into())) + // Measured: `436 + d * (212 ±0)` + // Estimated: `2056 + d * (2056 ±0)` + // Minimum execution time: 26_713_000 picoseconds. + Weight::from_parts(15_979_369, 2056) + // Standard Error: 53_691 + .saturating_add(Weight::from_parts(11_856_790, 0).saturating_mul(d.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(321, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(326, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(d.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(d.into()))) - .saturating_add(Weight::from_parts(0, 2076).saturating_mul(d.into())) + .saturating_add(Weight::from_parts(0, 2056).saturating_mul(d.into())) } /// Storage: `Revive::AccountInfoOf` (r:1 w:0) /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) @@ -2409,8 +2415,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1434` // Estimated: `4899` - // Minimum execution time: 34_108_000 picoseconds. - Weight::from_parts(35_130_000, 4899) + // Minimum execution time: 35_156_000 picoseconds. + Weight::from_parts(36_008_000, 4899) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -2426,18 +2432,20 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 131072]`. fn seal_instantiate(t: u32, d: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1464` - // Estimated: `4937` - // Minimum execution time: 154_945_000 picoseconds. - Weight::from_parts(108_508_437, 4937) - // Standard Error: 551_551 - .saturating_add(Weight::from_parts(20_945_320, 0).saturating_mul(t.into())) - // Standard Error: 551_551 - .saturating_add(Weight::from_parts(30_650_720, 0).saturating_mul(d.into())) - // Standard Error: 6 - .saturating_add(Weight::from_parts(3_953, 0).saturating_mul(i.into())) + // Measured: `1484` + // Estimated: `4921 + d * (31 ±1) + t * (31 ±1)` + // Minimum execution time: 158_623_000 picoseconds. + Weight::from_parts(119_635_514, 4921) + // Standard Error: 509_096 + .saturating_add(Weight::from_parts(17_183_470, 0).saturating_mul(t.into())) + // Standard Error: 509_096 + .saturating_add(Weight::from_parts(27_676_190, 0).saturating_mul(d.into())) + // Standard Error: 5 + .saturating_add(Weight::from_parts(3_929, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 31).saturating_mul(d.into())) + .saturating_add(Weight::from_parts(0, 31).saturating_mul(t.into())) } /// Storage: `Revive::AccountInfoOf` (r:1 w:1) /// Proof: `Revive::AccountInfoOf` (`max_values`: None, `max_size`: Some(247), added: 2722, mode: `Measured`) @@ -2454,16 +2462,16 @@ impl WeightInfo for () { /// The range of component `i` is `[10240, 49152]`. fn evm_instantiate(t: u32, d: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `870` - // Estimated: `6810` - // Minimum execution time: 347_011_000 picoseconds. - Weight::from_parts(270_222_660, 6810) - // Standard Error: 629_049 - .saturating_add(Weight::from_parts(17_216_349, 0).saturating_mul(t.into())) - // Standard Error: 629_049 - .saturating_add(Weight::from_parts(26_158_441, 0).saturating_mul(d.into())) - // Standard Error: 24 - .saturating_add(Weight::from_parts(3_843, 0).saturating_mul(i.into())) + // Measured: `869` + // Estimated: `6829` + // Minimum execution time: 375_311_000 picoseconds. + Weight::from_parts(238_984_394, 6829) + // Standard Error: 687_857 + .saturating_add(Weight::from_parts(21_371_046, 0).saturating_mul(t.into())) + // Standard Error: 687_857 + .saturating_add(Weight::from_parts(28_395_391, 0).saturating_mul(d.into())) + // Standard Error: 27 + .saturating_add(Weight::from_parts(8_472, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -2472,125 +2480,125 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_343_000 picoseconds. - Weight::from_parts(10_611_030, 0) + // Minimum execution time: 1_346_000 picoseconds. + Weight::from_parts(9_536_684, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(1_242, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_239, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn identity(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 830_000 picoseconds. - Weight::from_parts(819_424, 0) + // Minimum execution time: 799_000 picoseconds. + Weight::from_parts(759_415, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(112, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn ripemd_160(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_484_000 picoseconds. - Weight::from_parts(1_587_000, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(3_763, 0).saturating_mul(n.into())) + // Minimum execution time: 1_377_000 picoseconds. + Weight::from_parts(6_797_388, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(3_733, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_156_000 picoseconds. - Weight::from_parts(14_579_377, 0) + // Minimum execution time: 1_129_000 picoseconds. + Weight::from_parts(14_992_965, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(3_531, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_543, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_917_000 picoseconds. - Weight::from_parts(12_777_727, 0) + // Minimum execution time: 1_873_000 picoseconds. + Weight::from_parts(13_180_808, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(1_400, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_398, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048576]`. fn hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_855_000 picoseconds. - Weight::from_parts(15_746_302, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_397, 0).saturating_mul(n.into())) + // Minimum execution time: 1_854_000 picoseconds. + Weight::from_parts(12_306_060, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_400, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1048321]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 45_079_000 picoseconds. - Weight::from_parts(83_414_280, 0) + // Minimum execution time: 47_104_000 picoseconds. + Weight::from_parts(80_861_678, 0) // Standard Error: 4 - .saturating_add(Weight::from_parts(4_826, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(4_830, 0).saturating_mul(n.into())) } fn ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_987_000 picoseconds. - Weight::from_parts(48_111_000, 0) + // Minimum execution time: 46_698_000 picoseconds. + Weight::from_parts(47_673_000, 0) } fn p256_verify() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_787_561_000 picoseconds. - Weight::from_parts(1_798_471_000, 0) + // Minimum execution time: 1_791_033_000 picoseconds. + Weight::from_parts(1_801_147_000, 0) } fn bn128_add() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 15_130_000 picoseconds. - Weight::from_parts(16_755_000, 0) + // Minimum execution time: 14_836_000 picoseconds. + Weight::from_parts(16_004_000, 0) } fn bn128_mul() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 988_607_000 picoseconds. - Weight::from_parts(991_668_000, 0) + // Minimum execution time: 979_455_000 picoseconds. + Weight::from_parts(988_686_000, 0) } /// The range of component `n` is `[0, 20]`. fn bn128_pairing(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 915_000 picoseconds. - Weight::from_parts(4_888_094_516, 0) - // Standard Error: 10_384_426 - .saturating_add(Weight::from_parts(5_943_011_353, 0).saturating_mul(n.into())) + // Minimum execution time: 938_000 picoseconds. + Weight::from_parts(4_848_308_436, 0) + // Standard Error: 10_255_035 + .saturating_add(Weight::from_parts(5_920_112_189, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 1200]`. fn blake2f(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_043_000 picoseconds. - Weight::from_parts(1_345_971, 0) - // Standard Error: 33 - .saturating_add(Weight::from_parts(28_987, 0).saturating_mul(n.into())) + // Minimum execution time: 1_070_000 picoseconds. + Weight::from_parts(1_284_385, 0) + // Standard Error: 54 + .saturating_add(Weight::from_parts(28_398, 0).saturating_mul(n.into())) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_002_000 picoseconds. - Weight::from_parts(13_201_000, 0) + // Minimum execution time: 13_180_000 picoseconds. + Weight::from_parts(13_387_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:2 w:2) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(97), added: 2572, mode: `Measured`) @@ -2605,10 +2613,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `424 + r * (434 ±0)` // Estimated: `6364 + r * (2162 ±0)` - // Minimum execution time: 14_885_000 picoseconds. - Weight::from_parts(16_051_320, 6364) - // Standard Error: 57_553 - .saturating_add(Weight::from_parts(46_501_379, 0).saturating_mul(r.into())) + // Minimum execution time: 14_799_000 picoseconds. + Weight::from_parts(15_922_167, 6364) + // Standard Error: 52_549 + .saturating_add(Weight::from_parts(47_258_332, 0).saturating_mul(r.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(r.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -2620,30 +2628,30 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 558_000 picoseconds. - Weight::from_parts(624_652, 0) - // Standard Error: 66 - .saturating_add(Weight::from_parts(14_757, 0).saturating_mul(r.into())) + // Minimum execution time: 550_000 picoseconds. + Weight::from_parts(1_050_932, 0) + // Standard Error: 21 + .saturating_add(Weight::from_parts(15_280, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 10000]`. fn instr(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_194_000 picoseconds. - Weight::from_parts(53_119_928, 0) - // Standard Error: 232 - .saturating_add(Weight::from_parts(129_578, 0).saturating_mul(r.into())) + // Minimum execution time: 12_061_000 picoseconds. + Weight::from_parts(62_740_927, 0) + // Standard Error: 361 + .saturating_add(Weight::from_parts(123_285, 0).saturating_mul(r.into())) } /// The range of component `r` is `[0, 10000]`. fn instr_empty_loop(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_177_000 picoseconds. - Weight::from_parts(1_872_649, 0) - // Standard Error: 57 - .saturating_add(Weight::from_parts(74_048, 0).saturating_mul(r.into())) + // Minimum execution time: 3_326_000 picoseconds. + Weight::from_parts(3_151_099, 0) + // Standard Error: 50 + .saturating_add(Weight::from_parts(74_055, 0).saturating_mul(r.into())) } /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -2652,10 +2660,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `527 + n * (1 ±0)` // Estimated: `3992 + n * (1 ±0)` - // Minimum execution time: 14_619_000 picoseconds. - Weight::from_parts(14_803_675, 3992) - // Standard Error: 5 - .saturating_add(Weight::from_parts(747, 0).saturating_mul(n.into())) + // Minimum execution time: 14_656_000 picoseconds. + Weight::from_parts(14_686_037, 3992) + // Standard Error: 4 + .saturating_add(Weight::from_parts(724, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -2667,8 +2675,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `382` // Estimated: `6322` - // Minimum execution time: 12_485_000 picoseconds. - Weight::from_parts(12_931_000, 6322) + // Minimum execution time: 12_331_000 picoseconds. + Weight::from_parts(12_868_000, 6322) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -2682,8 +2690,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `505` // Estimated: `6866` - // Minimum execution time: 64_237_000 picoseconds. - Weight::from_parts(66_071_000, 6866) + // Minimum execution time: 63_102_000 picoseconds. + Weight::from_parts(65_300_000, 6866) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -2704,10 +2712,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3012 + n * (97 ±0)` // Estimated: `6303 + n * (104 ±0)` - // Minimum execution time: 28_116_000 picoseconds. - Weight::from_parts(55_446_109, 6303) - // Standard Error: 4_382 - .saturating_add(Weight::from_parts(514_927, 0).saturating_mul(n.into())) + // Minimum execution time: 26_639_000 picoseconds. + Weight::from_parts(56_482_010, 6303) + // Standard Error: 4_704 + .saturating_add(Weight::from_parts(522_203, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) .saturating_add(Weight::from_parts(0, 104).saturating_mul(n.into())) @@ -2729,10 +2737,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3577 + d * (3 ±0)` // Estimated: `7036 + d * (3 ±0)` - // Minimum execution time: 59_544_000 picoseconds. - Weight::from_parts(61_174_055, 7036) - // Standard Error: 129 - .saturating_add(Weight::from_parts(12_909, 0).saturating_mul(d.into())) + // Minimum execution time: 59_432_000 picoseconds. + Weight::from_parts(61_955_716, 7036) + // Standard Error: 157 + .saturating_add(Weight::from_parts(12_327, 0).saturating_mul(d.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) .saturating_add(Weight::from_parts(0, 3).saturating_mul(d.into())) @@ -2756,10 +2764,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1546` // Estimated: `5011` - // Minimum execution time: 45_140_000 picoseconds. - Weight::from_parts(46_928_768, 5011) - // Standard Error: 1_998 - .saturating_add(Weight::from_parts(3_639, 0).saturating_mul(e.into())) + // Minimum execution time: 44_652_000 picoseconds. + Weight::from_parts(46_489_107, 5011) + // Standard Error: 1_089 + .saturating_add(Weight::from_parts(4_596, 0).saturating_mul(e.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -2782,10 +2790,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1546` // Estimated: `5011` - // Minimum execution time: 44_893_000 picoseconds. - Weight::from_parts(46_639_493, 5011) + // Minimum execution time: 44_400_000 picoseconds. + Weight::from_parts(46_629_995, 5011) // Standard Error: 6 - .saturating_add(Weight::from_parts(31, 0).saturating_mul(d.into())) + .saturating_add(Weight::from_parts(21, 0).saturating_mul(d.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } From 0f55b11e95e1d709402c8738cbae688060e7a8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 25 Nov 2025 04:24:29 -0300 Subject: [PATCH 48/69] Implement PR feedback --- .../revive/fixtures/contracts/Caller.sol | 15 +- .../fixtures/contracts/CatchConstructor.sol | 2 +- .../revive/fixtures/contracts/Deposit.sol | 8 +- substrate/frame/revive/src/evm/call.rs | 273 +++++++++--------- substrate/frame/revive/src/evm/runtime.rs | 9 +- substrate/frame/revive/src/evm/tracing.rs | 3 +- .../revive/src/evm/tracing/call_tracing.rs | 10 +- substrate/frame/revive/src/exec.rs | 2 + substrate/frame/revive/src/lib.rs | 7 +- substrate/frame/revive/src/metering/math.rs | 4 +- substrate/frame/revive/src/metering/weight.rs | 4 +- .../frame/revive/src/tests/sol/contract.rs | 5 +- 12 files changed, 178 insertions(+), 164 deletions(-) diff --git a/substrate/frame/revive/fixtures/contracts/Caller.sol b/substrate/frame/revive/fixtures/contracts/Caller.sol index dca1e6861c5e9..c2e1417ed7c19 100644 --- a/substrate/frame/revive/fixtures/contracts/Caller.sol +++ b/substrate/frame/revive/fixtures/contracts/Caller.sol @@ -11,6 +11,13 @@ contract ChildRevert { contract Caller { uint256 public data; + enum CallType { + Call, + StaticCall, + DelegateCall + } + + function normal(address _callee, uint64 _value, bytes memory _data, uint64 _gas) external returns (bool success, bytes memory output) @@ -68,17 +75,17 @@ contract Caller { } } - function callPartialGas(address _callee, bytes memory _data, uint64 _gasDivisor, uint8 _callType) + function callPartialGas(address _callee, bytes memory _data, uint64 _gasDivisor, CallType _callType) external returns (bool success) { uint256 gas = gasleft() / _gasDivisor; bytes memory output; - if (_callType == 0) { + if (_callType == CallType.Call) { (success, output) = _callee.call{gas: gas }(_data); - } else if (_callType == 1) { + } else if (_callType == CallType.StaticCall) { (success, output) = _callee.staticcall{gas: gas }(_data); - } else if (_callType == 2) { + } else if (_callType == CallType.DelegateCall) { (success, output) = _callee.delegatecall{gas: gas }(_data); } else { revert("unknown call type"); diff --git a/substrate/frame/revive/fixtures/contracts/CatchConstructor.sol b/substrate/frame/revive/fixtures/contracts/CatchConstructor.sol index 31fa78c3cf44c..8287cf146923e 100644 --- a/substrate/frame/revive/fixtures/contracts/CatchConstructor.sol +++ b/substrate/frame/revive/fixtures/contracts/CatchConstructor.sol @@ -7,7 +7,7 @@ contract CatchConstructorFoo { constructor(address _owner) { require(_owner != address(0), "invalid address"); - assert(_owner != 0x0000000000000000000000000000000000000001); + assert(_owner != address(1)); owner = _owner; } diff --git a/substrate/frame/revive/fixtures/contracts/Deposit.sol b/substrate/frame/revive/fixtures/contracts/Deposit.sol index 47f2c1db0a55f..feb66d46af6b9 100644 --- a/substrate/frame/revive/fixtures/contracts/Deposit.sol +++ b/substrate/frame/revive/fixtures/contracts/Deposit.sol @@ -5,8 +5,10 @@ contract Deposit { uint256 a; uint256 b; + address immutable storagePrecompile = address(0x901); + function clearStorageSlot(uint256 slot) internal { - address storagePrecompile = 0x0000000000000000000000000000000000000901; + address storagePrecompile = storagePrecompile; bytes memory key = abi.encodePacked(bytes32(slot)); (bool _success, ) = storagePrecompile.delegatecall( abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, key) @@ -19,7 +21,7 @@ contract Deposit { } function c() external { - address targetAddress = 0x0000000000000000000000000000000000000901; + address targetAddress = storagePrecompile; a = 2; b = 3; @@ -35,7 +37,7 @@ contract Deposit { function d() external { this.x(); - address targetAddress = 0x0000000000000000000000000000000000000901; + address targetAddress = storagePrecompile; bytes32 key = bytes32(bytes1(0x01)) >> 248; bytes memory keyBytes = abi.encodePacked(key); diff --git a/substrate/frame/revive/src/evm/call.rs b/substrate/frame/revive/src/evm/call.rs index 02864f37bdd80..28c0df6e1f99d 100644 --- a/substrate/frame/revive/src/evm/call.rs +++ b/substrate/frame/revive/src/evm/call.rs @@ -59,180 +59,183 @@ pub enum CreateCallMode { DryRun, } -/// Decode `tx` into a dispatchable call. -/// -/// signed_transaction is Some(..) for extrinsic execution (when called from -/// `try_into_checked_extrinsic`) and it is `None` for dry running (when called from -/// `dry_run_eth_transact`) -pub fn create_call( - tx: GenericTransaction, - mode: CreateCallMode, -) -> Result, InvalidTransaction> -where - T: Config, - CallOf: SetWeightLimit, -{ - let is_dry_run = matches!(mode, CreateCallMode::DryRun); - let base_fee = >::evm_base_fee(); - - let Some(gas) = tx.gas else { - log::debug!(target: LOG_TARGET, "No gas provided"); - return Err(InvalidTransaction::Call); - }; - - // Currently, effective_gas_price will always be the same as base_fee - // Because all callers of `create_call` will prepare `tx` that way. Some of the subsequent - // logic will not work correctly anymore if we change that assumption. - let Some(effective_gas_price) = tx.gas_price 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); - } +impl GenericTransaction { + /// Decode `tx` into a dispatchable call. + pub fn into_call(self, mode: CreateCallMode) -> Result, InvalidTransaction> + where + T: Config, + CallOf: SetWeightLimit, + { + let is_dry_run = matches!(mode, CreateCallMode::DryRun); + let base_fee = >::evm_base_fee(); + + let Some(gas) = self.gas else { + log::debug!(target: LOG_TARGET, "No gas provided"); + return Err(InvalidTransaction::Call); + }; - 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); - } + // Currently, effective_gas_price will always be the same as base_fee + // Because all callers of `into_call` will prepare `tx` that way. Some of the subsequent + // logic will not work correctly anymore if we change that assumption. + let Some(effective_gas_price) = self.gas_price else { + log::debug!(target: LOG_TARGET, "No gas_price provided."); + return Err(InvalidTransaction::Payment); + }; - let (encoded_len, transaction_encoded) = - if let CreateCallMode::ExtrinsicExecution(encoded_len, transaction_encoded) = mode { - (encoded_len, transaction_encoded) - } else { - // For dry runs, we need to ensure that the RLP encoding length is at least the length - // of the encoding of the actual transaction submitted later - let mut maximized_tx = tx.clone(); - maximized_tx.gas = Some(U256::MAX); - maximized_tx.gas_price = Some(U256::MAX); - maximized_tx.max_priority_fee_per_gas = Some(U256::MAX); - - let unsigned_tx = maximized_tx.try_into_unsigned().map_err(|_| { - log::debug!(target: LOG_TARGET, "Invalid transaction type."); - InvalidTransaction::Call - })?; - let transaction_encoded = unsigned_tx.dummy_signed_payload(); + let chain_id = self.chain_id.unwrap_or_default(); - let eth_transact_call = - crate::Call::::eth_transact { payload: transaction_encoded.clone() }; - (::FeeInfo::encoded_len(eth_transact_call.into()), transaction_encoded) - }; + if chain_id != ::ChainId::get().into() { + log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}"); + return Err(InvalidTransaction::Call); + } - let value = tx.value.unwrap_or_default(); - let data = tx.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 mut call = if let Some(dest) = tx.to { - if dest == RUNTIME_PALLETS_ADDR { - let call = - CallOf::::decode_all_with_depth_limit(MAX_EXTRINSIC_DEPTH, &mut &data[..]) - .map_err(|_| { + let (encoded_len, transaction_encoded) = + if let CreateCallMode::ExtrinsicExecution(encoded_len, transaction_encoded) = mode { + (encoded_len, transaction_encoded) + } else { + // For dry runs, we need to ensure that the RLP encoding length is at least the + // length of the encoding of the actual transaction submitted later + let mut maximized_tx = self.clone(); + maximized_tx.gas = Some(U256::MAX); + maximized_tx.gas_price = Some(U256::MAX); + maximized_tx.max_priority_fee_per_gas = Some(U256::MAX); + + let unsigned_tx = maximized_tx.try_into_unsigned().map_err(|_| { + log::debug!(target: LOG_TARGET, "Invalid transaction type."); + InvalidTransaction::Call + })?; + let transaction_encoded = unsigned_tx.dummy_signed_payload(); + + let eth_transact_call = + crate::Call::::eth_transact { payload: transaction_encoded.clone() }; + (::FeeInfo::encoded_len(eth_transact_call.into()), transaction_encoded) + }; + + let value = self.value.unwrap_or_default(); + let data = self.input.to_vec(); + + let mut call = if let Some(dest) = self.to { + if dest == RUNTIME_PALLETS_ADDR { + let call = + CallOf::::decode_all_with_depth_limit(MAX_EXTRINSIC_DEPTH, &mut &data[..]) + .map_err(|_| { log::debug!(target: LOG_TARGET, "Failed to decode data as Call"); InvalidTransaction::Call })?; - if !value.is_zero() { - log::debug!(target: LOG_TARGET, "Runtime pallets address cannot be called with value"); - return Err(InvalidTransaction::Call) + if !value.is_zero() { + log::debug!(target: LOG_TARGET, "Runtime pallets address cannot be called with value"); + return Err(InvalidTransaction::Call) + } + + crate::Call::eth_substrate_call:: { call: Box::new(call), transaction_encoded } + .into() + } else { + let call = crate::Call::eth_call:: { + dest, + value, + weight_limit: Zero::zero(), + eth_gas_limit: gas, + data, + transaction_encoded, + effective_gas_price, + encoded_len, + } + .into(); + call } - - crate::Call::eth_substrate_call:: { call: Box::new(call), transaction_encoded } - .into() } else { - let call = crate::Call::eth_call:: { - dest, + let (code, data) = if data.starts_with(&polkavm_common::program::BLOB_MAGIC) { + let Some((code, data)) = extract_code_and_data(&data) else { + log::debug!(target: LOG_TARGET, "Failed to extract polkavm code & data"); + return Err(InvalidTransaction::Call); + }; + (code, data) + } else { + (data, Default::default()) + }; + + let call = crate::Call::eth_instantiate_with_code:: { value, weight_limit: Zero::zero(), eth_gas_limit: gas, + code, data, transaction_encoded, effective_gas_price, encoded_len, } .into(); + call - } - } else { - let (code, data) = if data.starts_with(&polkavm_common::program::BLOB_MAGIC) { - let Some((code, data)) = extract_code_and_data(&data) else { - log::debug!(target: LOG_TARGET, "Failed to extract polkavm code & data"); - return Err(InvalidTransaction::Call); - }; - (code, data) - } else { - (data, Default::default()) }; - let call = crate::Call::eth_instantiate_with_code:: { - value, - weight_limit: Zero::zero(), - eth_gas_limit: gas, - code, - data, - transaction_encoded, - effective_gas_price, - encoded_len, - } - .into(); - - call - }; + // the fee as signed off by the eth wallet. we cannot consume more. + let eth_fee = + effective_gas_price.saturating_mul(gas) / ::NativeToEthRatio::get(); - // the fee as signed off by the eth wallet. we cannot consume more. - let eth_fee = effective_gas_price.saturating_mul(gas) / ::NativeToEthRatio::get(); - - let weight_limit = { - let fixed_fee = ::FeeInfo::fixed_fee(encoded_len as u32); - let info = ::FeeInfo::dispatch_info(&call); + let weight_limit = { + let fixed_fee = ::FeeInfo::fixed_fee(encoded_len as u32); + let info = ::FeeInfo::dispatch_info(&call); - let remaining_fee = { - let adjusted = eth_fee.checked_sub(fixed_fee.into()).ok_or_else(|| { + let remaining_fee = { + let adjusted = eth_fee.checked_sub(fixed_fee.into()).ok_or_else(|| { log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover base and len fee. eth_fee={eth_fee:?} fixed_fee={fixed_fee:?}"); InvalidTransaction::Payment })?; - let unadjusted = compute_max_integer_quotient( - ::FeeInfo::next_fee_multiplier(), - >::saturated_from(adjusted), - ); + let unadjusted = compute_max_integer_quotient( + ::FeeInfo::next_fee_multiplier(), + >::saturated_from(adjusted), + ); - unadjusted - }; - let remaining_fee_weight = ::FeeInfo::fee_to_weight(remaining_fee); - let weight_limit = remaining_fee_weight + unadjusted + }; + let remaining_fee_weight = ::FeeInfo::fee_to_weight(remaining_fee); + let weight_limit = remaining_fee_weight .checked_sub(&info.total_weight()).ok_or_else(|| { log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover the weight ({:?}) of the extrinsic. remaining_fee_weight: {remaining_fee_weight:?}", info.total_weight(),); InvalidTransaction::Payment })?; - call.set_weight_limit(weight_limit); - - if !is_dry_run { - let max_weight = >::evm_max_extrinsic_weight(); - let info = ::FeeInfo::dispatch_info(&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 - } else { - weight_limit - } - }; + call.set_weight_limit(weight_limit); + + if !is_dry_run { + let max_weight = >::evm_max_extrinsic_weight(); + let info = ::FeeInfo::dispatch_info(&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 + } else { + weight_limit + } + }; - // the overall fee of the extrinsic including the gas limit - let tx_fee = ::FeeInfo::tx_fee(encoded_len, &call); + // the overall fee of the extrinsic including the gas limit + let tx_fee = ::FeeInfo::tx_fee(encoded_len, &call); - // the leftover we make available to the deposit collection system - let storage_deposit = eth_fee.checked_sub(tx_fee.into()).ok_or_else(|| { + // the leftover we make available to the deposit collection system + let storage_deposit = eth_fee.checked_sub(tx_fee.into()).ok_or_else(|| { log::error!(target: LOG_TARGET, "The eth_fee={eth_fee:?} is smaller than the tx_fee={tx_fee:?}. This is a bug."); InvalidTransaction::Payment })?.saturated_into(); - Ok(CallInfo { call, weight_limit, encoded_len, tx_fee, storage_deposit, eth_gas_limit: gas }) + Ok(CallInfo { + call, + weight_limit, + encoded_len, + tx_fee, + storage_deposit, + eth_gas_limit: gas, + }) + } } diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index b9421bfa7fca4..c359b04d45764 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -18,7 +18,6 @@ use crate::{ evm::{ api::{GenericTransaction, TransactionSigned}, - create_call, fees::InfoT, CreateCallMode, }, @@ -315,10 +314,10 @@ pub trait EthExtra { })?; log::debug!(target: LOG_TARGET, "Decoded Ethereum transaction with signer: {signer_addr:?} nonce: {nonce:?}"); - let call_info = create_call::( - tx, - CreateCallMode::ExtrinsicExecution(encoded_len as u32, payload.to_vec()), - )?; + let call_info = tx.into_call::(CreateCallMode::ExtrinsicExecution( + encoded_len as u32, + payload.to_vec(), + ))?; let storage_credit = ::Currency::withdraw( &signer, call_info.storage_deposit, diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs index 17c8382c57548..0fe9dbfe076fd 100644 --- a/substrate/frame/revive/src/evm/tracing.rs +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -19,7 +19,6 @@ use crate::{ tracing::Tracing, Config, }; -use sp_core::U256; mod call_tracing; pub use call_tracing::*; @@ -31,7 +30,7 @@ pub use prestate_tracing::*; #[derive(derive_more::From, Debug)] pub enum Tracer { /// A tracer that traces calls. - CallTracer(CallTracer), + CallTracer(CallTracer), /// A tracer that traces the prestate. PrestateTracer(PrestateTracer), } diff --git a/substrate/frame/revive/src/evm/tracing/call_tracing.rs b/substrate/frame/revive/src/evm/tracing/call_tracing.rs index c33af025b7301..4e6b8f6a6b183 100644 --- a/substrate/frame/revive/src/evm/tracing/call_tracing.rs +++ b/substrate/frame/revive/src/evm/tracing/call_tracing.rs @@ -25,9 +25,9 @@ use sp_core::{H160, H256, U256}; /// A Tracer that reports logs and nested call traces transactions. #[derive(Default, Debug, Clone, PartialEq)] -pub struct CallTracer { +pub struct CallTracer { /// Store all in-progress CallTrace instances. - traces: Vec>, + traces: Vec>, /// Stack of indices to the current active traces. current_stack: Vec, /// The code and salt used to instantiate the next contract. @@ -36,19 +36,19 @@ pub struct CallTracer { config: CallTracerConfig, } -impl CallTracer { +impl CallTracer { /// Create a new [`CallTracer`] instance. pub fn new(config: CallTracerConfig) -> Self { Self { traces: Vec::new(), code_with_salt: None, current_stack: Vec::new(), config } } /// Collect the traces and return them. - pub fn collect_trace(mut self) -> Option> { + pub fn collect_trace(mut self) -> Option { self.traces.pop() } } -impl Tracing for CallTracer { +impl Tracing for CallTracer { fn instantiate_code(&mut self, code: &Code, salt: Option<&[u8; 32]>) { self.code_with_salt = Some((code.clone(), salt.is_some())); } diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index b6e02881b78d0..083dfe067901a 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -325,6 +325,7 @@ pub trait PrecompileExt: sealing::Sealed { /// Charges the weight meter with the given token or halts execution if not enough weight is /// left. + #[inline] fn charge_or_halt>( &mut self, token: Tok, @@ -2234,6 +2235,7 @@ where &self.top_frame().frame_meter } + #[inline] fn gas_meter_mut(&mut self) -> &mut FrameMeter { &mut self.top_frame_mut().frame_meter } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index ce70aea8d4160..b560c74007d47 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -48,7 +48,7 @@ pub mod weights; use crate::{ evm::{ - block_hash::EthereumBlockBuilderIR, block_storage, create_call, fees::InfoT as FeeInfo, + block_hash::EthereumBlockBuilderIR, block_storage, fees::InfoT as FeeInfo, runtime::SetWeightLimit, CallTracer, CreateCallMode, GenericTransaction, PrestateTracer, Trace, Tracer, TracerType, TYPE_EIP1559, }, @@ -1759,7 +1759,7 @@ impl Pallet { tx.chain_id = Some(T::ChainId::get().into()); } - // create_call expects tx.gas_price to be the effective gas price + // tx.into_call expects tx.gas_price to be the effective gas price tx.gas_price = Some(effective_gas_price); tx.max_priority_fee_per_gas = Some(0.into()); if tx.max_fee_per_gas.is_none() { @@ -1782,7 +1782,8 @@ impl Pallet { // we need to parse the weight from the transaction so that it is run // using the exact weight limit passed by the eth wallet - let mut call_info = create_call::(tx, CreateCallMode::DryRun) + let mut call_info = tx + .into_call::(CreateCallMode::DryRun) .map_err(|err| EthTransactError::Message(format!("Invalid call: {err:?}")))?; // the dry-run might leave out certain fields diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index 8177a9296ca67..f0b76436f9676 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -136,7 +136,7 @@ pub mod substrate_execution { let stipend = if *add_stipend { let weight_stipend = determine_call_stipend::(); if weight_left.any_lt(weight_stipend) { - Err(>::OutOfGas)? + return Err(>::OutOfGas.into()) } weight_limit.saturating_accrue(weight_stipend); @@ -365,7 +365,7 @@ pub mod ethereum_execution { let (gas_limit, stipend) = if *add_stipend { let weight_stipend = determine_call_stipend::(); if weight_left.any_lt(weight_stipend) { - Err(>::OutOfGas)? + return Err(>::OutOfGas.into()) } ( diff --git a/substrate/frame/revive/src/metering/weight.rs b/substrate/frame/revive/src/metering/weight.rs index 568b073fdc3e5..a931c7480bfa9 100644 --- a/substrate/frame/revive/src/metering/weight.rs +++ b/substrate/frame/revive/src/metering/weight.rs @@ -195,7 +195,7 @@ impl WeightMeter { // any action let new_consumed = self.weight_consumed.saturating_add(amount); if new_consumed.any_gt(self.effective_weight_limit) { - Err(>::OutOfGas)?; + return Err(>::OutOfGas.into()) } self.weight_consumed = new_consumed; @@ -237,7 +237,7 @@ impl WeightMeter { self.weight_consumed.saturating_accrue(weight_consumed); if self.weight_consumed.any_gt(self.effective_weight_limit) { self.weight_consumed = self.effective_weight_limit; - Err(>::OutOfGas)?; + return Err(>::OutOfGas.into()) } Ok(()) diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs index 206f599c3c8ae..070aa1bc48c77 100644 --- a/substrate/frame/revive/src/tests/sol/contract.rs +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -654,7 +654,8 @@ fn subcall_effectively_limited_substrate_tx(caller_type: FixtureType, callee_typ collection_config.collect_deposit_from_hold = Some(Default::default()); let configs = [no_collection_config, collection_config]; - let call_types = [0u8, 1, 2]; + let call_types = + [Caller::CallType::Call, Caller::CallType::StaticCall, Caller::CallType::DelegateCall]; struct Case { deposit_limit: BalanceOf, @@ -720,7 +721,7 @@ fn subcall_effectively_limited_substrate_tx(caller_type: FixtureType, callee_typ test_cases.iter().cartesian_product(&configs).cartesian_product(call_types) { // the storage stuff won't work on static or delegate call - if case.is_store_call && call_type != 0 { + if case.is_store_call && !matches!(call_type, Caller::CallType::Call) { continue } From 116ba4f6d7596a70c912585d02f44647a57f5d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:36:45 -0300 Subject: [PATCH 49/69] Implement review feedback --- .../revive/fixtures/contracts/Deposit.sol | 28 ++++----- substrate/frame/revive/src/evm/fees.rs | 62 +++++++++++++++---- substrate/frame/revive/src/exec.rs | 30 +++++---- substrate/frame/revive/src/exec/mock_ext.rs | 4 -- substrate/frame/revive/src/lib.rs | 32 +++++----- substrate/frame/revive/src/limits.rs | 6 -- substrate/frame/revive/src/metering/gas.rs | 5 +- substrate/frame/revive/src/metering/math.rs | 36 ++++++----- substrate/frame/revive/src/metering/mod.rs | 2 +- .../frame/revive/src/metering/storage.rs | 16 ++--- substrate/frame/revive/src/metering/tests.rs | 8 +-- substrate/frame/revive/src/tests/pvm.rs | 4 +- substrate/frame/revive/src/tests/stipends.rs | 8 +-- .../src/vm/evm/instructions/contract.rs | 6 +- 14 files changed, 140 insertions(+), 107 deletions(-) diff --git a/substrate/frame/revive/fixtures/contracts/Deposit.sol b/substrate/frame/revive/fixtures/contracts/Deposit.sol index feb66d46af6b9..32daf3ef4bc3d 100644 --- a/substrate/frame/revive/fixtures/contracts/Deposit.sol +++ b/substrate/frame/revive/fixtures/contracts/Deposit.sol @@ -8,64 +8,58 @@ contract Deposit { address immutable storagePrecompile = address(0x901); function clearStorageSlot(uint256 slot) internal { - address storagePrecompile = storagePrecompile; bytes memory key = abi.encodePacked(bytes32(slot)); (bool _success, ) = storagePrecompile.delegatecall( abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, key) ); } - function clear() external { + function clearAll() external { clearStorageSlot(0); clearStorageSlot(1); } - function c() external { - address targetAddress = storagePrecompile; - + function setAndClear() external { a = 2; b = 3; bytes32 key = bytes32(bytes1(0x01)) >> 248; bytes memory keyBytes = abi.encodePacked(key); - (bool success, ) = targetAddress.delegatecall( + (bool success, ) = storagePrecompile.delegatecall( abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, keyBytes) ); } - function d() external { - this.x(); + function callSetAndClear() external { + this.setVars(); - address targetAddress = storagePrecompile; bytes32 key = bytes32(bytes1(0x01)) >> 248; bytes memory keyBytes = abi.encodePacked(key); - (bool success, ) = targetAddress.delegatecall( + (bool success, ) = storagePrecompile.delegatecall( abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, keyBytes) ); } - function e() external { + function setAndCallClear() external { a = 2; b = 3; - this.y(); + this.clear(); } - function x() external { + function setVars() external { a = 2; b = 3; } - function y() external { - address targetAddress = 0x0000000000000000000000000000000000000901; - + function clear() external { bytes32 key = bytes32(bytes1(0x01)) >> 248; bytes memory keyBytes = abi.encodePacked(key); - (bool success, ) = targetAddress.delegatecall( + (bool success, ) = storagePrecompile.delegatecall( abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, keyBytes) ); } diff --git a/substrate/frame/revive/src/evm/fees.rs b/substrate/frame/revive/src/evm/fees.rs index ab8f91a4b1692..cb568d9ececb8 100644 --- a/substrate/frame/revive/src/evm/fees.rs +++ b/substrate/frame/revive/src/evm/fees.rs @@ -400,7 +400,7 @@ mod seal { /// /// We can take advantage of the function `FixedU128::checked_rounding_div`, which, given two fixed /// point numbers `a` and `b`, just computes `a.0 * FixedU128::DIV / b.0`. It also allows to specify -/// the rounding mode `SignedRounding::Major`, which means the that result of the division is +/// the rounding mode `SignedRounding::Major`, which means that the result of the division is /// rounded up. pub fn compute_max_integer_quotient( multiplier: FixedU128, @@ -436,18 +436,56 @@ pub fn compute_max_integer_pair_quotient( (result1, result2) } -#[test] -fn compute_max_quotient_works() { - let product1 = 8625031518u64; - let product2 = 2597808837u64; +#[cfg(test)] +mod tests { + use super::*; + use proptest::proptest; - let multiplier = FixedU128::from_rational(4_000_000_000_000, 10 * 1024 * 1024); + #[test] + fn compute_max_quotient_works() { + let product1 = 8625031518u64; + let product2 = 2597808837u64; - assert_eq!(compute_max_integer_quotient(multiplier, product1), 22610); - assert_eq!(compute_max_integer_quotient(multiplier, product2), 6810); + let multiplier = FixedU128::from_rational(4_000_000_000_000, 10 * 1024 * 1024); - // This shows that just dividing by the multiplier does not give the correct result, neither - // when rounding up, nor when rounding down - assert_eq!(multiplier.reciprocal().unwrap().saturating_mul_int(product1), 22610); - assert_eq!(multiplier.reciprocal().unwrap().saturating_mul_int(product2), 6809); + assert_eq!(compute_max_integer_quotient(multiplier, product1), 22610); + assert_eq!(compute_max_integer_quotient(multiplier, product2), 6810); + + // This shows that just dividing by the multiplier does not give the correct result, neither + // when rounding up, nor when rounding down + assert_eq!(multiplier.reciprocal().unwrap().saturating_mul_int(product1), 22610); + assert_eq!(multiplier.reciprocal().unwrap().saturating_mul_int(product2), 6809); + } + + #[test] + fn proptest_max_quotient_works() { + proptest!(|(numerator: u128, denominator: u128, product: u128)| { + let multiplier = FixedU128::from_rational(numerator.saturating_add(1), denominator.saturating_add(1)); + let max_quotient = compute_max_integer_quotient(multiplier, product); + + assert!(multiplier.saturating_mul_int(max_quotient) <= product); + if max_quotient < u128::MAX { + assert!(multiplier.saturating_mul_int(max_quotient + 1) > product); + } + }); + } + + #[test] + fn proptest_max_pair_quotient_works() { + proptest!(|(numerator1: u128, denominator1: u128, numerator2: u128, denominator2: u128, product: u128)| { + let multiplier1 = FixedU128::from_rational(numerator1.saturating_add(1), denominator1.saturating_add(1)); + let multiplier2 = FixedU128::from_rational(numerator2.saturating_add(1), denominator2.saturating_add(1)); + let (max_quotient1, max_quotient2) = compute_max_integer_pair_quotient((multiplier1, multiplier2), product); + + assert!(multiplier1.saturating_mul_int(max_quotient1) <= product); + if max_quotient1 < u128::MAX { + assert!(multiplier1.saturating_mul_int(max_quotient1 + 1) > product); + } + + assert!(multiplier2.saturating_mul_int(max_quotient2) <= product); + if max_quotient2 < u128::MAX { + assert!(multiplier2.saturating_mul_int(max_quotient2 + 1) > product); + } + }); + } } diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 083dfe067901a..e6feada8a58f0 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -128,11 +128,16 @@ impl Key { pub enum ReentrancyProtection { /// Don't activate reentrancy protection AllowReentry, - // Activate strict reentrancy protection. The direct callee and none of its own recursive - // callees must be the calling contract. + /// Activate strict reentrancy protection. The direct callee and none of its own recursive + /// callees must be the calling contract. Strict, - // Activate reentrancy protection where the direct callee can be the same contract as the - // caller but none of the recursive callees of the callee must be the caller + /// Activate reentrancy protection where the direct callee can be the same contract as the + /// caller but none of the recursive callees of the callee must be the caller. + /// + /// This is used for calls that transfer value but restrict gas so that the callee only has a + /// stipend gas amount. In Ethereum that is not sufficient for the callee to make another call. + /// However, due to gas scale differences that guarantee does not automatically hold in revive + /// and we enforce it explicitly here. AllowNext, } @@ -438,9 +443,6 @@ pub trait PrecompileExt: sealing::Sealed { /// Returns the chain id. fn chain_id(&self) -> u64; - /// Returns the maximum allowed size of a storage item. - fn max_value_size(&self) -> u32; - /// Get an immutable reference to the nested resource meter of the frame. fn gas_meter(&self) -> &FrameMeter; @@ -1249,7 +1251,7 @@ where } self.transient_storage.start_transaction(); - let is_first_frame = self.frames.len() == 0; + let is_first_frame = self.frames.is_empty(); let do_transaction = || -> ExecResult { let caller = self.caller(); @@ -1272,7 +1274,7 @@ where let origin = &self.origin.account_id()?; if !frame_system::Pallet::::account_exists(&account_id) { - let ed: ::Balance = >::min_balance(); + let ed = >::min_balance(); frame.frame_meter.charge_deposit(&StorageDeposit::Charge(ed))?; >::charge_deposit(None, origin, account_id, ed, self.exec_config) .map_err(|_| >::StorageDepositNotEnoughFunds)?; @@ -1437,6 +1439,9 @@ where Ok((success, output)) => { if_tracing(|tracer| { let frame_meter = &top_frame!(self).frame_meter; + + // we treat the initial frame meter differently to address + // https://github.com/paritytech/polkadot-sdk/issues/8362 let gas_consumed = if is_first_frame { frame_meter.total_consumed_gas().into() } else { @@ -1456,6 +1461,9 @@ where Err(error) => { if_tracing(|tracer| { let frame_meter = &top_frame!(self).frame_meter; + + // we treat the initial frame meter differently to address + // https://github.com/paritytech/polkadot-sdk/issues/8362 let gas_consumed = if is_first_frame { frame_meter.total_consumed_gas().into() } else { @@ -2227,10 +2235,6 @@ where ::ChainId::get() } - fn max_value_size(&self) -> u32 { - limits::PAYLOAD_BYTES - } - fn gas_meter(&self) -> &FrameMeter { &self.top_frame().frame_meter } diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index edb2f60a4fa3d..d2c4f41b14af4 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -161,10 +161,6 @@ impl PrecompileExt for MockExt { panic!("MockExt::chain_id") } - fn max_value_size(&self) -> u32 { - panic!("MockExt::max_value_size") - } - fn gas_meter(&self) -> &FrameMeter { &self.frame_meter } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 8be8cc1f2b0ff..9949d0f84ce36 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1761,6 +1761,8 @@ impl Pallet { // tx.into_call expects tx.gas_price to be the effective gas price tx.gas_price = Some(effective_gas_price); + // we don't support priority fee for now as the tipping system in pallet-transaction-payment + // works differently and the total tip needs to be known pre dispatch tx.max_priority_fee_per_gas = Some(0.into()); if tx.max_fee_per_gas.is_none() { tx.max_fee_per_gas = Some(effective_gas_price); @@ -1819,6 +1821,14 @@ impl Pallet { } }; + let transaction_limits = TransactionLimits::EthereumGas { + eth_gas_limit: call_info.eth_gas_limit.saturated_into(), + // no need to limit weight here, we will check later whether it exceeds + // evm_max_extrinsic_weight + maybe_weight_limit: None, + eth_tx_info: EthTxInfo::new(call_info.encoded_len, base_weight), + }; + // Dry run the call let mut dry_run = match to { // A contract call. @@ -1846,13 +1856,7 @@ impl Pallet { OriginFor::::signed(origin), dest, value, - TransactionLimits::EthereumGas { - eth_gas_limit: call_info.eth_gas_limit.saturated_into(), - // no need to limit weight here, we will check later whether it exceeds - // evm_max_extrinsic_weight - maybe_weight_limit: None, - eth_tx_info: EthTxInfo::new(call_info.encoded_len, base_weight), - }, + transaction_limits, input.clone(), exec_config, ); @@ -1892,13 +1896,7 @@ impl Pallet { let result = crate::Pallet::::bare_instantiate( OriginFor::::signed(origin), value, - TransactionLimits::EthereumGas { - eth_gas_limit: call_info.eth_gas_limit.saturated_into(), - // no need to limit weight here, we will check later whether it exceeds - // evm_max_extrinsic_weight - maybe_weight_limit: None, - eth_tx_info: EthTxInfo::new(call_info.encoded_len, base_weight), - }, + transaction_limits, Code::Upload(code.clone()), data.clone(), None, @@ -2070,6 +2068,12 @@ impl Pallet { /// Get the block gas limit. pub fn evm_block_gas_limit() -> U256 { + // We just return `u64::MAX` because the gas cost of a transaction can get very large when + // the transaction executes many storage deposits (in theory a contract can behave like a + // factory, procedurally create code and make contract creation calls to store that as + // code). It is too brittle to estimate a maximally possible amount here. + // On the other hand, the data type `u64` seems to be the "common denominator" as the + // typical data type tools and Ethereum implementations use to represent gas amounts. u64::MAX.into() } diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 75c8358f8d8e7..68108846b5856 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -48,9 +48,6 @@ pub const CALL_STACK_DEPTH: u32 = 25; /// We set it to the same limit that ethereum has. It is unlikely to change. pub const NUM_EVENT_TOPICS: u32 = 4; -/// Maximum size of events (including topics) and storage values. -pub const PAYLOAD_BYTES: u32 = 416; - /// Maximum size of of the transaction payload /// /// Maximum code size during instantiation taken into account plus some overhead. @@ -96,9 +93,6 @@ pub const EVM_MEMORY_BYTES: u32 = 1024 * 1024; /// EVM interpreter stack limit. pub const EVM_STACK_LIMIT: u32 = 1024; -/// The call stipend gas amount defined in the EVM -pub const SOLIDITY_CALL_STIPEND: u32 = 2300; - /// Limits that are only enforced on code upload. /// /// # Note diff --git a/substrate/frame/revive/src/metering/gas.rs b/substrate/frame/revive/src/metering/gas.rs index a95a41479d19f..15b55affdc1fc 100644 --- a/substrate/frame/revive/src/metering/gas.rs +++ b/substrate/frame/revive/src/metering/gas.rs @@ -19,7 +19,10 @@ use crate::{evm::fees::InfoT, BalanceOf, Config, StorageDeposit}; use frame_support::DebugNoBound; use sp_runtime::{FixedPointNumber, Saturating}; -/// The type for negative and positive gas amounts +/// The type for negative and positive gas amounts. +/// +/// We need to be able to represent negative amounts as they occur when storage deposit refunds are +/// involved. /// /// The structure of this type resembles `StorageDeposit` but the enum variants have a more obvious /// name to avoid confusion and errors diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index f0b76436f9676..c9cad56205fd9 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -20,11 +20,12 @@ use super::{ FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, State, StorageDeposit, TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, }; -use crate::{limits::SOLIDITY_CALL_STIPEND, metering::weight::Token, vm::evm::EVMGas, SignedGas}; +use crate::{metering::weight::Token, vm::evm::EVMGas, SignedGas}; use core::marker::PhantomData; +use revm::interpreter::gas::CALL_STIPEND; fn determine_call_stipend() -> Weight { - let gas = EVMGas(SOLIDITY_CALL_STIPEND.into()); + let gas = EVMGas(CALL_STIPEND); >::weight(&gas) } @@ -83,14 +84,17 @@ pub mod substrate_execution { let weight_left = meter .weight .weight_limit - .expect("Weight limits are always defined for WeightAndDeposit; qed") + .expect( + "Weight limits are always defined for `ResourceMeter` in Substrate \ + execution mode (i.e., when its `transaction_limits` are `WeightAndDeposit`); qed", + ) .checked_sub(&self_consumed_weight) .ok_or(>::OutOfGas)?; - let deposit_limit = meter - .deposit - .limit - .expect("Deposit limits are always defined for WeightAndDeposit; qed"); + let deposit_limit = meter.deposit.limit.expect( + "Deposit limits are always defined for `ResourceMeter` in Substrate \ + execution mode (i.e., when its `transaction_limits` are `WeightAndDeposit`); qed", + ); let deposit_left = self_consumed_deposit .available(&deposit_limit) .ok_or(>::StorageDepositLimitExhausted)?; @@ -170,7 +174,7 @@ pub mod substrate_execution { /// Compute the remaining ethereum-gas-equivalent for a Substrate-style transaction. /// /// Converts the remaining weight and deposit into their gas-equivalents (via `FeeInfo`) and - /// returns the sum. Returns `None` if either component there is not enough as left + /// returns the sum. Returns `None` if either component does not have enough left. pub fn gas_left(meter: &ResourceMeter) -> Option> { match (weight_left(meter), deposit_left(meter)) { (Some(weight_left), Some(deposit_left)) => { @@ -191,10 +195,10 @@ pub mod substrate_execution { /// /// Subtracts the weight already consumed in the current frame from the configured limit. pub fn weight_left(meter: &ResourceMeter) -> Option { - let weight_limit = meter - .weight - .weight_limit - .expect("Weight limits are always defined for WeightAndDeposit; qed"); + let weight_limit = meter.weight.weight_limit.expect( + "Weight limits are always defined for `ResourceMeter` in Substrate \ + execution mode (i.e., when its `transaction_limits` are `WeightAndDeposit`); qed", + ); weight_limit.checked_sub(&meter.weight.weight_consumed()) } @@ -203,10 +207,10 @@ pub mod substrate_execution { /// Subtracts the storage deposit already consumed in the current frame from the configured /// limit. pub fn deposit_left(meter: &ResourceMeter) -> Option> { - let deposit_limit = meter - .deposit - .limit - .expect("Deposit limits are always defined for WeightAndDeposit; qed"); + let deposit_limit = meter.deposit.limit.expect( + "Deposit limits are always defined for `ResourceMeter` in Substrate \ + execution mode (i.e., when its `transaction_limits` are `WeightAndDeposit`); qed", + ); meter.deposit.consumed().available(&deposit_limit) } diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 93e15d0e905c4..e13f4152bf2cf 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -397,7 +397,7 @@ impl ResourceMeter { } /// Get total weight required - /// This is the maxmimum amount of weight consumption that occurred during execution so far + /// This is the maximum amount of weight consumption that occurred during execution so far /// This is relevant because consumed weight can decrease in case it is asjusted a posteriori /// for some operations pub fn weight_required(&self) -> Weight { diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index bcaa452dcd0cc..a081e455e3aeb 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -81,7 +81,7 @@ pub enum ReservingExt {} #[derive(DefaultNoBound, RuntimeDebugNoBound)] pub struct RawMeter { /// The limit of how much balance this meter is allowed to consume. - pub limit: Option>, + pub(crate) limit: Option>, /// The amount of balance that was used in this meter and all of its already absorbed children. total_deposit: DepositOf, /// The amount of storage changes that were recorded in this meter alone. @@ -293,8 +293,13 @@ where contract: &T::AccountId, info: Option<&mut ContractInfo>, ) { - // No need to recalculate max_charged for `absorbed` here. With `info` we can now calculate - // the correct `own_contribution` of `absorbed` but that can only be less + // We are now at the position to calculate the actual final net charge of `absorbed` as we + // now have the contract information `info`. Before that we only took net charges related to + // the contract storage into account but ignored net refunds. + // However, with this complete information there is no need to recalculate `max_charged` for + // `absorbed` here before we absorb it because the actual final net charge will not be more + // than the net charge we observed before (as we only ignored net refunds but not net + // charges). self.max_charged = self .max_charged .max(self.consumed().saturating_add(&absorbed.max_charged()).charge_or_zero()); @@ -328,8 +333,6 @@ where /// /// - `absorbed`: The child storage meter pub fn absorb_only_max_charged(&mut self, absorbed: RawMeter) { - // No need to recalculate max_charged for `absorbed` here. With `info` we can now calculate - // the correct `own_contribution` of `absorbed` but that can only be less self.max_charged = self .max_charged .max(self.consumed().saturating_add(&absorbed.max_charged()).charge_or_zero()); @@ -421,9 +424,6 @@ where /// The total amount of deposit that should change hands as result of the execution /// that this meter was passed into. This will also perform all the charges accumulated /// in the whole contract stack. - /// - /// This drops the root meter in order to make sure it is only called when the whole - /// execution did finish. pub fn execute_postponed_deposits( &mut self, origin: &Origin, diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs index aa0879b9715ae..53293b8424399 100644 --- a/substrate/frame/revive/src/metering/tests.rs +++ b/substrate/frame/revive/src/metering/tests.rs @@ -54,7 +54,7 @@ fn max_consumed_deposit_integration(fixture_type: FixtureType) { let Contract { addr: caller_addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let result = builder::bare_call(caller_addr).data(Deposit::dCall {}.abi_encode()).build(); + let result = builder::bare_call(caller_addr).data(Deposit::callSetAndClearCall {}.abi_encode()).build(); assert_eq!(result.storage_deposit, StorageDeposit::Charge(66)); assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); @@ -73,14 +73,14 @@ fn max_consumed_deposit_integration_refunds_subframes(fixture_type: FixtureType) let Contract { addr: caller_addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let result = builder::bare_call(caller_addr).data(Deposit::cCall {}.abi_encode()).build(); + let result = builder::bare_call(caller_addr).data(Deposit::setAndClearCall {}.abi_encode()).build(); assert_eq!(result.storage_deposit, StorageDeposit::Charge(66)); assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); - builder::bare_call(caller_addr).data(Deposit::clearCall {}.abi_encode()).build(); + builder::bare_call(caller_addr).data(Deposit::clearAllCall {}.abi_encode()).build(); - let result = builder::bare_call(caller_addr).data(Deposit::eCall {}.abi_encode()).build(); + let result = builder::bare_call(caller_addr).data(Deposit::setAndCallClearCall {}.abi_encode()).build(); assert_eq!(result.storage_deposit, StorageDeposit::Charge(66)); assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index fb5af0376a954..8546e8b3c9b98 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -333,7 +333,7 @@ fn deposit_event_max_value_limit() { // Call contract with allowed event size. assert_ok!(builder::call(addr) .weight_limit(WEIGHT_LIMIT.set_ref_time(WEIGHT_LIMIT.ref_time() * 2)) // we are copying a huge buffer, - .data(limits::PAYLOAD_BYTES.encode()) + .data(limits::EVENT_BYTES.encode()) .build()); // Call contract with too large a evene size @@ -502,7 +502,7 @@ fn storage_max_value_limit() { // Call contract with allowed storage value. assert_ok!(builder::call(addr) .weight_limit(WEIGHT_LIMIT.set_ref_time(WEIGHT_LIMIT.ref_time() * 2)) // we are copying a huge buffer - .data(limits::PAYLOAD_BYTES.encode()) + .data(limits::STORAGE_BYTES.encode()) .build()); // Call contract with too large a storage value. diff --git a/substrate/frame/revive/src/tests/stipends.rs b/substrate/frame/revive/src/tests/stipends.rs index d4ba5a188405f..a803230332166 100644 --- a/substrate/frame/revive/src/tests/stipends.rs +++ b/substrate/frame/revive/src/tests/stipends.rs @@ -25,10 +25,9 @@ use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{ compile_module_with_type, ComplexReceiver, FixtureType, SimpleReceiver, StipendSender, }; +use sp_core::H160; use test_case::test_case; -pub use sp_core::H160; - enum Receiver { Contract(&'static str), EOA(H160), @@ -104,10 +103,7 @@ fn evm_call_stipends_work_for_transfers(test_case: TestCase) { let balance_before = Pallet::::evm_balance(&receiver_addr); - let amount = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::( - 1_000_000_000_000, - 0, - )); + let amount = Pallet::::convert_native_to_evm(1_000_000_000_000); let result = builder::bare_call(stipend_sender_address) .data(StipendSender::sendViaTransferCall { to: receiver_addr.0.into() }.abi_encode()) diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index d7f268c7429c9..ab4962a520872 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -32,7 +32,7 @@ use core::{ cmp::min, ops::{ControlFlow, Range}, }; -use revm::interpreter::interpreter_action::CallScheme; +use revm::interpreter::{gas::CALL_STIPEND, interpreter_action::CallScheme}; /// Implements the CREATE/CREATE2 instruction. /// @@ -190,12 +190,12 @@ fn run_call<'a, E: Ext>( value: U256, return_memory_range: Range, ) -> ControlFlow { - // We use SOLIDITY_CALL_STIPEND to detect the typical gas limit solc defines as a call stipend + // We use ALL_STIPEND to detect the typical gas limit solc defines as a call stipend // This is just a heuristic let add_stipend = !value.is_zero() || gas_limit .try_into() - .is_ok_and(|limit: u32| limit == crate::limits::SOLIDITY_CALL_STIPEND); + .is_ok_and(|limit: u64| limit == CALL_STIPEND); let call_result = match scheme { CallScheme::Call | CallScheme::StaticCall => interpreter.ext.call( From 2dba778d50469029045d226ec73fe7a2dd81fc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 25 Nov 2025 18:13:40 -0300 Subject: [PATCH 50/69] Simplify return type of try_upload_code --- substrate/frame/revive/src/lib.rs | 15 +++++++++------ substrate/frame/revive/src/tests/sol.rs | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 9949d0f84ce36..0ab3275502ddb 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -1662,7 +1662,7 @@ impl Pallet { let executable = match code { Code::Upload(code) if code.starts_with(&polkavm_common::program::BLOB_MAGIC) => { let upload_account = T::UploadOrigin::ensure_origin(origin)?; - let (executable, ..) = Self::try_upload_code( + let executable = Self::try_upload_code( upload_account, code, BytecodeType::Pvm, @@ -2132,14 +2132,17 @@ impl Pallet { deposit_limit: storage_deposit_limit, })?; - let (module, deposit) = Self::try_upload_code( + let module = Self::try_upload_code( origin, code, bytecode_type, &mut meter, &ExecConfig::new_substrate_tx(), )?; - Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) + Ok(CodeUploadReturnValue { + code_hash: *module.code_hash(), + deposit: meter.deposit_consumed().charge_or_zero(), + }) } /// Query storage of a specified contract under a specified key. @@ -2282,13 +2285,13 @@ impl Pallet { code_type: BytecodeType, meter: &mut TransactionMeter, exec_config: &ExecConfig, - ) -> Result<(ContractBlob, BalanceOf), DispatchError> { + ) -> Result, DispatchError> { let mut module = match code_type { BytecodeType::Pvm => ContractBlob::from_pvm_code(code, origin)?, BytecodeType::Evm => ContractBlob::from_evm_runtime_code(code, origin)?, }; - let deposit = module.store_code(exec_config, meter)?; - Ok((module, deposit)) + module.store_code(exec_config, meter)?; + Ok(module) } /// Run the supplied function `f` if no other instance of this pallet is on the stack. diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 81fbc3374498d..a62f7d9b30e53 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -231,7 +231,7 @@ fn upload_evm_runtime_code_works() { let deployer_addr = ALICE_ADDR; let _ = Pallet::::set_evm_balance(&deployer_addr, 1_000_000_000.into()); - let (uploaded_blob, _) = Pallet::::try_upload_code( + let uploaded_blob = Pallet::::try_upload_code( deployer, runtime_code.clone(), crate::vm::BytecodeType::Evm, From 2c1acdf406fa277dccf9ecd46ca4e3e86b639215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 26 Nov 2025 03:20:28 -0300 Subject: [PATCH 51/69] Make some members crate public --- substrate/frame/revive/src/metering/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index a081e455e3aeb..401cb09513344 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -98,7 +98,7 @@ pub struct RawMeter { /// True if this is the root meter. /// /// Sometimes we cannot know at compile time. - pub is_root: bool, + pub(crate) is_root: bool, /// Type parameter only used in impls. _phantom: PhantomData<(E, S)>, } From aa31d6eb8948191e515a96d9ba4c8030d76bedfa Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 26 Nov 2025 11:31:01 +0100 Subject: [PATCH 52/69] simplify test --- .../revive/fixtures/contracts/Stipends.sol | 144 +++++++-- substrate/frame/revive/src/tests/stipends.rs | 295 +++--------------- 2 files changed, 163 insertions(+), 276 deletions(-) diff --git a/substrate/frame/revive/fixtures/contracts/Stipends.sol b/substrate/frame/revive/fixtures/contracts/Stipends.sol index 553268e8ea4a5..8205b3dc77970 100644 --- a/substrate/frame/revive/fixtures/contracts/Stipends.sol +++ b/substrate/frame/revive/fixtures/contracts/Stipends.sol @@ -6,42 +6,19 @@ pragma solidity ^0.8.19; * @dev Sender contract: provides three transfer methods */ contract StipendSender { - event TransferSuccess(string method, address to, uint256 amount, uint256 gasUsed); - event TransferFailed(string method, address to, uint256 amount, string reason); - // Method 1: transfer (2300 gas stipend) function sendViaTransfer(address payable to) external payable { - uint256 gasBefore = gasleft(); to.transfer(msg.value); - uint256 gasUsed = gasBefore - gasleft(); - emit TransferSuccess("transfer", to, msg.value, gasUsed); } // Method 2: send (2300 gas stipend, returns bool) function sendViaSend(address payable to) external payable returns (bool) { - uint256 gasBefore = gasleft(); - bool success = to.send(msg.value); - uint256 gasUsed = gasBefore - gasleft(); - - if (success) { - emit TransferSuccess("send", to, msg.value, gasUsed); - } else { - emit TransferFailed("send", to, msg.value, "send returned false"); - } - return success; + return to.send(msg.value); } // Method 3: call (forwards all gas) function sendViaCall(address payable to) external payable returns (bool) { - uint256 gasBefore = gasleft(); (bool success, ) = to.call{value: msg.value}(""); - uint256 gasUsed = gasBefore - gasleft(); - - if (success) { - emit TransferSuccess("call", to, msg.value, gasUsed); - } else { - emit TransferFailed("call", to, msg.value, "call returned false"); - } return success; } @@ -62,7 +39,7 @@ contract DoNothingReceiver { /** * @title SimpleReceiver - * @dev Receiver contract 2: only emits events + * @dev Receiver contract 2: emits events */ contract SimpleReceiver { event Received(address from, uint256 amount); @@ -92,4 +69,121 @@ contract ComplexReceiver { function getBalance() external view returns (uint256) { return address(this).balance; } +} + +/** + * @title StipendTest + * @dev Test contract that verifies stipend behavior for different receiver types + */ +contract StipendTest { + DoNothingReceiver doNothingReceiver; + SimpleReceiver simpleReceiver; + ComplexReceiver complexReceiver; + address payable eoa; + + constructor() { + doNothingReceiver = new DoNothingReceiver(); + simpleReceiver = new SimpleReceiver(); + complexReceiver = new ComplexReceiver(); + eoa = payable(address(0x1234567890123456789012345678901234567890)); + } + + // Helper function to attempt transfer (so we can use try-catch) + function attemptTransfer(address payable to, uint256 amount) external { + to.transfer(amount); + } + + // Test transfer method (2300 gas stipend) + function testTransfer() public payable { + uint256 amount = msg.value / 4; + + // EOA should succeed + uint256 balanceBefore = eoa.balance; + eoa.transfer(amount); + require(eoa.balance == balanceBefore + amount, "EOA transfer failed"); + + // DoNothingReceiver should succeed (empty receive) + balanceBefore = address(doNothingReceiver).balance; + payable(address(doNothingReceiver)).transfer(amount); + require(address(doNothingReceiver).balance == balanceBefore + amount, "DoNothingReceiver transfer failed"); + + // SimpleReceiver should succeed + balanceBefore = address(simpleReceiver).balance; + payable(address(simpleReceiver)).transfer(amount); + require(address(simpleReceiver).balance == balanceBefore + amount, "SimpleReceiver transfer failed"); + + // ComplexReceiver should fail (not enough gas for SSTORE) + balanceBefore = address(complexReceiver).balance; + bool failed = false; + try this.attemptTransfer(payable(address(complexReceiver)), amount) { + // Should not succeed + failed = false; + } catch { + failed = true; + } + require(failed, "ComplexReceiver transfer should have failed"); + require(address(complexReceiver).balance == balanceBefore, "ComplexReceiver balance changed on failed transfer"); + } + + // Test send method (2300 gas stipend, returns bool) + function testSend() public payable { + uint256 amount = msg.value / 4; + + // EOA should succeed + uint256 balanceBefore = eoa.balance; + bool success = eoa.send(amount); + require(success, "EOA send failed"); + require(eoa.balance == balanceBefore + amount, "EOA balance not updated"); + + // DoNothingReceiver should succeed (empty receive) + balanceBefore = address(doNothingReceiver).balance; + success = payable(address(doNothingReceiver)).send(amount); + require(success, "DoNothingReceiver send failed"); + require(address(doNothingReceiver).balance == balanceBefore + amount, "DoNothingReceiver balance not updated"); + + // SimpleReceiver should succeed + balanceBefore = address(simpleReceiver).balance; + success = payable(address(simpleReceiver)).send(amount); + require(success, "SimpleReceiver send failed"); + require(address(simpleReceiver).balance == balanceBefore + amount, "SimpleReceiver balance not updated"); + + // ComplexReceiver should fail (not enough gas for SSTORE) + balanceBefore = address(complexReceiver).balance; + success = payable(address(complexReceiver)).send(amount); + require(!success, "ComplexReceiver send should have failed"); + require(address(complexReceiver).balance == balanceBefore, "ComplexReceiver balance changed on failed send"); + } + + // Test call method (forwards all gas) + function testCall() public payable { + uint256 amount = msg.value / 4; + + // EOA should succeed + uint256 balanceBefore = eoa.balance; + (bool success, ) = eoa.call{value: amount}(""); + require(success, "EOA call failed"); + require(eoa.balance == balanceBefore + amount, "EOA balance not updated"); + + // DoNothingReceiver should succeed (empty receive) + balanceBefore = address(doNothingReceiver).balance; + (success, ) = payable(address(doNothingReceiver)).call{value: amount}(""); + require(success, "DoNothingReceiver call failed"); + require(address(doNothingReceiver).balance == balanceBefore + amount, "DoNothingReceiver balance not updated"); + + // SimpleReceiver should succeed + balanceBefore = address(simpleReceiver).balance; + (success, ) = payable(address(simpleReceiver)).call{value: amount}(""); + require(success, "SimpleReceiver call failed"); + require(address(simpleReceiver).balance == balanceBefore + amount, "SimpleReceiver balance not updated"); + + // ComplexReceiver should succeed (enough gas for SSTORE with call) + balanceBefore = address(complexReceiver).balance; + uint256 counterBefore = complexReceiver.counter(); + (success, ) = payable(address(complexReceiver)).call{value: amount}(""); + require(success, "ComplexReceiver call failed"); + require(address(complexReceiver).balance == balanceBefore + amount, "ComplexReceiver balance not updated"); + require(complexReceiver.counter() == counterBefore + 1, "ComplexReceiver counter not incremented"); + } + + receive() external payable {} } \ No newline at end of file diff --git a/substrate/frame/revive/src/tests/stipends.rs b/substrate/frame/revive/src/tests/stipends.rs index a803230332166..8159d177342cf 100644 --- a/substrate/frame/revive/src/tests/stipends.rs +++ b/substrate/frame/revive/src/tests/stipends.rs @@ -16,283 +16,76 @@ // limitations under the License. use crate::{ - test_utils::{builder::Contract, ALICE, BOB_ADDR}, - tests::{builder, ExtBuilder, RuntimeEvent, Test}, - BalanceWithDust, Code, Config, Pallet, System, + test_utils::builder::Contract, + tests::{builder, ExtBuilder, Test}, + Code, Config, }; -use alloy_core::sol_types::{SolCall, SolEvent}; +use alloy_core::sol_types::SolCall; use frame_support::traits::fungible::Mutate; -use pallet_revive_fixtures::{ - compile_module_with_type, ComplexReceiver, FixtureType, SimpleReceiver, StipendSender, -}; -use sp_core::H160; -use test_case::test_case; - -enum Receiver { - Contract(&'static str), - EOA(H160), -} - -#[derive(Clone, Copy)] -enum TestCase { - Bob, - DoNothingReceiver, - SimpleReceiver, - ComplexReceiver, -} - -impl TestCase { - fn receiver(&self) -> Receiver { - match self { - &TestCase::Bob => Receiver::EOA(BOB_ADDR), - &TestCase::DoNothingReceiver => Receiver::Contract("DoNothingReceiver"), - &TestCase::SimpleReceiver => Receiver::Contract("SimpleReceiver"), - &TestCase::ComplexReceiver => Receiver::Contract("ComplexReceiver"), - } - } -} - -fn get_contract_events() -> Vec<(H160, Vec, Vec<[u8; 32]>)> { - let events = System::::events(); - events - .into_iter() - .filter_map(|e| match e.event { - RuntimeEvent::Contracts(crate::Event::ContractEmitted { contract, data, topics }) => - Some(( - contract, - data, - topics.into_iter().map(|t| t.to_fixed_bytes()).collect::>(), - )), - _ => None, - }) - .collect() -} - -#[test_case(TestCase::Bob; "EOA")] -#[test_case(TestCase::DoNothingReceiver; "DoNothingReceiver")] -#[test_case(TestCase::SimpleReceiver; "SimpleReceiver")] -#[test_case(TestCase::ComplexReceiver; "ComplexReceiver")] -fn evm_call_stipends_work_for_transfers(test_case: TestCase) { - let (expect_receive_event, expect_success) = match test_case { - TestCase::Bob => (false, true), - TestCase::DoNothingReceiver => (false, true), - TestCase::SimpleReceiver => (true, true), - TestCase::ComplexReceiver => (false, false), - }; +use pallet_revive_fixtures::{compile_module_with_type, FixtureType, StipendTest}; - let (code, _) = compile_module_with_type("StipendSender", FixtureType::Solc).unwrap(); +#[test] +fn evm_call_stipends_work_for_transfers() { + let (code, _) = compile_module_with_type("StipendTest", FixtureType::Solc).unwrap(); ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 10_000_000_000_000); + let _ = ::Currency::set_balance( + &crate::test_utils::ALICE, + 10_000_000_000_000, + ); - let Contract { addr: stipend_sender_address, .. } = + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let receiver_addr = match test_case.receiver() { - Receiver::Contract(name) => { - let (receiver_code, _) = compile_module_with_type(name, FixtureType::Solc).unwrap(); - let Contract { addr: receiver_addr, .. } = - builder::bare_instantiate(Code::Upload(receiver_code)) - .build_and_unwrap_contract(); + let result = builder::bare_call(addr) + .data(StipendTest::testTransferCall {}.abi_encode()) + .evm_value(1_000_000_u128.into()) + .build(); - receiver_addr - }, - - Receiver::EOA(address) => address, - }; - - let balance_before = Pallet::::evm_balance(&receiver_addr); - - let amount = Pallet::::convert_native_to_evm(1_000_000_000_000); - - let result = builder::bare_call(stipend_sender_address) - .data(StipendSender::sendViaTransferCall { to: receiver_addr.0.into() }.abi_encode()) - .evm_value(amount) - .build_and_unwrap_result(); - - let balance_after = Pallet::::evm_balance(&receiver_addr); - - let mut contract_events = get_contract_events().into_iter(); - - if expect_receive_event { - let (_contract, data, topics) = contract_events.next().unwrap(); - let decoded_event = - SimpleReceiver::Received::decode_raw_log(topics, data.as_slice()).unwrap(); - assert_eq!(decoded_event.from, stipend_sender_address.0); - assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); - } - - if expect_success { - assert!(!result.did_revert()); - assert_eq!(amount, balance_after.saturating_sub(balance_before)); - - let (_contract, data, topics) = contract_events.next().unwrap(); - let decoded_event = - StipendSender::TransferSuccess::decode_raw_log(topics, data.as_slice()).unwrap(); - assert_eq!(decoded_event.method, "transfer"); - assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); - assert_eq!(decoded_event.to, receiver_addr.0); - } else { - assert!(result.did_revert()); - assert_eq!(balance_after, balance_before); - } + assert!(!result.result.unwrap().did_revert()); }); } -#[test_case(TestCase::Bob; "EOA")] -#[test_case(TestCase::DoNothingReceiver; "DoNothingReceiver")] -#[test_case(TestCase::SimpleReceiver; "SimpleReceiver")] -#[test_case(TestCase::ComplexReceiver; "ComplexReceiver")] -fn evm_call_stipends_work_for_sends(test_case: TestCase) { - let (expect_receive_event, expect_success) = match test_case { - TestCase::Bob => (false, true), - TestCase::DoNothingReceiver => (false, true), - TestCase::SimpleReceiver => (true, true), - TestCase::ComplexReceiver => (false, false), - }; - - let (code, _) = compile_module_with_type("StipendSender", FixtureType::Solc).unwrap(); +#[test] +fn evm_call_stipends_work_for_sends() { + let (code, _) = compile_module_with_type("StipendTest", FixtureType::Solc).unwrap(); ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 10_000_000_000_000); + let _ = ::Currency::set_balance( + &crate::test_utils::ALICE, + 10_000_000_000_000, + ); - let Contract { addr: stipend_sender_address, .. } = + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let receiver_addr = match test_case.receiver() { - Receiver::Contract(name) => { - let (receiver_code, _) = compile_module_with_type(name, FixtureType::Solc).unwrap(); - let Contract { addr: receiver_addr, .. } = - builder::bare_instantiate(Code::Upload(receiver_code)) - .build_and_unwrap_contract(); + let result = builder::bare_call(addr) + .data(StipendTest::testSendCall {}.abi_encode()) + .evm_value(1_000_000_u128.into()) + .build(); - receiver_addr - }, - - Receiver::EOA(address) => address, - }; - - let balance_before = Pallet::::evm_balance(&receiver_addr); - - let amount = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::( - 1_000_000_000_000, - 0, - )); - - let result = builder::bare_call(stipend_sender_address) - .data(StipendSender::sendViaSendCall { to: receiver_addr.0.into() }.abi_encode()) - .evm_value(amount) - .build_and_unwrap_result(); - - let balance_after = Pallet::::evm_balance(&receiver_addr); - - let mut contract_events = get_contract_events().into_iter(); - - if expect_receive_event { - let (_contract, data, topics) = contract_events.next().unwrap(); - let decoded_event = - SimpleReceiver::Received::decode_raw_log(topics, data.as_slice()).unwrap(); - assert_eq!(decoded_event.from, stipend_sender_address.0); - assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); - } - - if expect_success { - assert!(!result.did_revert()); - assert_eq!(amount, balance_after.saturating_sub(balance_before)); - - let (_contract, data, topics) = contract_events.next().unwrap(); - let decoded_event = - StipendSender::TransferSuccess::decode_raw_log(topics, data.as_slice()).unwrap(); - assert_eq!(decoded_event.method, "send"); - assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); - assert_eq!(decoded_event.to, receiver_addr.0); - } else { - assert!(!result.did_revert()); - assert_eq!(balance_after, balance_before); - - let (_contract, data, topics) = contract_events.next().unwrap(); - let decoded_event = - StipendSender::TransferFailed::decode_raw_log(topics, data.as_slice()).unwrap(); - assert_eq!(decoded_event.method, "send"); - assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); - assert_eq!(decoded_event.to, receiver_addr.0); - } + assert!(!result.result.unwrap().did_revert()); }); } -#[test_case(TestCase::Bob; "EOA")] -#[test_case(TestCase::DoNothingReceiver; "DoNothingReceiver")] -#[test_case(TestCase::SimpleReceiver; "SimpleReceiver")] -#[test_case(TestCase::ComplexReceiver; "ComplexReceiver")] -fn evm_call_stipends_work_for_calls(test_case: TestCase) { - let (expect_receive_event, expect_simple_receive_event) = match test_case { - TestCase::Bob => (false, true), - TestCase::DoNothingReceiver => (false, true), - TestCase::SimpleReceiver => (true, true), - TestCase::ComplexReceiver => (true, false), - }; - - let (code, _) = compile_module_with_type("StipendSender", FixtureType::Solc).unwrap(); +#[test] +fn evm_call_stipends_work_for_calls() { + let (code, _) = compile_module_with_type("StipendTest", FixtureType::Solc).unwrap(); ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 10_000_000_000_000); + let _ = ::Currency::set_balance( + &crate::test_utils::ALICE, + 10_000_000_000_000, + ); - let Contract { addr: stipend_sender_address, .. } = + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let receiver_addr = match test_case.receiver() { - Receiver::Contract(name) => { - let (receiver_code, _) = compile_module_with_type(name, FixtureType::Solc).unwrap(); - let Contract { addr: receiver_addr, .. } = - builder::bare_instantiate(Code::Upload(receiver_code)) - .build_and_unwrap_contract(); - - receiver_addr - }, - - Receiver::EOA(address) => address, - }; - - let balance_before = Pallet::::evm_balance(&receiver_addr); - - let amount = Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::( - 1_000_000_000_000, - 0, - )); - - let result = builder::bare_call(stipend_sender_address) - .data(StipendSender::sendViaCallCall { to: receiver_addr.0.into() }.abi_encode()) - .evm_value(amount) - .build_and_unwrap_result(); - - let balance_after = Pallet::::evm_balance(&receiver_addr); - - let mut contract_events = get_contract_events().into_iter(); - - if expect_receive_event { - let (_contract, data, topics) = contract_events.next().unwrap(); - if expect_simple_receive_event { - let decoded_event = - SimpleReceiver::Received::decode_raw_log(topics, data.as_slice()).unwrap(); - assert_eq!(decoded_event.from, stipend_sender_address.0); - assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); - } else { - let decoded_event = - ComplexReceiver::Received::decode_raw_log(topics, data.as_slice()).unwrap(); - assert_eq!(decoded_event.from, stipend_sender_address.0); - assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); - assert_eq!(decoded_event.newCounter.try_into(), Ok(1)); - } - } - - assert!(!result.did_revert()); - assert_eq!(amount, balance_after.saturating_sub(balance_before)); + let result = builder::bare_call(addr) + .data(StipendTest::testCallCall {}.abi_encode()) + .evm_value(1_000_000_u128.into()) + .build(); - let (_contract, data, topics) = contract_events.next().unwrap(); - let decoded_event = - StipendSender::TransferSuccess::decode_raw_log(topics, data.as_slice()).unwrap(); - assert_eq!(decoded_event.method, "call"); - assert_eq!(decoded_event.amount.as_le_slice(), amount.to_little_endian()); - assert_eq!(decoded_event.to, receiver_addr.0); + assert!(!result.result.unwrap().did_revert()); }); } From c8ced0ef7241943964d7a110d4c203e783f1b541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 26 Nov 2025 21:27:40 -0300 Subject: [PATCH 53/69] Deprecate gas_meter API --- substrate/frame/revive/proc-macro/src/lib.rs | 6 ++--- substrate/frame/revive/src/benchmarking.rs | 4 +-- substrate/frame/revive/src/exec.rs | 27 ++++++++++++++++--- substrate/frame/revive/src/exec/mock_ext.rs | 8 ++++++ .../revive/src/precompiles/builtin/blake2f.rs | 2 +- .../revive/src/precompiles/builtin/bn128.rs | 8 +++--- .../src/precompiles/builtin/ecrecover.rs | 2 +- .../src/precompiles/builtin/identity.rs | 2 +- .../revive/src/precompiles/builtin/modexp.rs | 8 +++--- .../src/precompiles/builtin/p256_verify.rs | 2 +- .../src/precompiles/builtin/ripemd160.rs | 2 +- .../revive/src/precompiles/builtin/sha256.rs | 2 +- .../revive/src/precompiles/builtin/storage.rs | 14 +++++----- .../revive/src/precompiles/builtin/system.rs | 22 +++++++-------- .../frame/revive/src/tests/precompiles.rs | 2 +- .../src/vm/evm/instructions/contract.rs | 8 +++--- .../evm/instructions/contract/call_helpers.rs | 15 +++++------ .../revive/src/vm/evm/instructions/control.rs | 2 +- .../revive/src/vm/evm/instructions/host.rs | 2 +- .../revive/src/vm/evm/instructions/system.rs | 2 +- substrate/frame/revive/src/vm/pvm.rs | 6 ++--- substrate/frame/revive/src/vm/pvm/env.rs | 4 +-- 22 files changed, 87 insertions(+), 63 deletions(-) diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index c00625bfaa11e..b67e15a0103df 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -424,7 +424,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { quote! { // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); - ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.gas_meter().weight_consumed()); + ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.frame_meter().weight_consumed()); result } }; @@ -444,7 +444,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { quote! { // Write gas from polkavm into pallet-revive before entering the host function. self.ext - .gas_meter_mut() + .frame_meter_mut() .sync_from_executor(memory.gas()) .map_err(TrapReason::from)?; @@ -461,7 +461,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { })(); // Write gas from pallet-revive into polkavm after leaving the host function. - let gas = self.ext.gas_meter_mut().sync_to_executor(); + let gas = self.ext.frame_meter_mut().sync_to_executor(); memory.set_gas(gas.into()); result } diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index fe5fc1ae83489..01cf9bbece526 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -783,7 +783,7 @@ mod benchmarks { let mut call_setup = CallSetup::::default(); let (mut ext, _) = call_setup.ext(); - let weight_left_before = ext.gas_meter().weight_left().unwrap(); + let weight_left_before = ext.frame_meter().weight_left().unwrap(); let result; #[block] { @@ -793,7 +793,7 @@ mod benchmarks { input_bytes, ); } - let weight_left_after = ext.gas_meter().weight_left().unwrap(); + let weight_left_after = ext.frame_meter().weight_left().unwrap(); assert_ne!(weight_left_after.ref_time(), 0); assert!(weight_left_before.ref_time() > weight_left_after.ref_time()); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index e3794bf367c3b..0ee818eb5492b 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -319,13 +319,13 @@ pub trait PrecompileExt: sealing::Sealed { /// Charges the weight meter with the given weight. fn charge(&mut self, weight: Weight) -> Result { - self.gas_meter_mut().charge_weight_token(RuntimeCosts::Precompile(weight)) + self.frame_meter_mut().charge_weight_token(RuntimeCosts::Precompile(weight)) } /// Reconcile an earlier gas charge with the actual weight consumed. /// This updates the current weight meter to reflect the real cost of the token. fn adjust_gas(&mut self, charged: ChargedAmount, actual_weight: Weight) { - self.gas_meter_mut() + self.frame_meter_mut() .adjust_weight(charged, RuntimeCosts::Precompile(actual_weight)); } @@ -336,7 +336,7 @@ pub trait PrecompileExt: sealing::Sealed { &mut self, token: Tok, ) -> ControlFlow { - self.gas_meter_mut().charge_or_halt(token) + self.frame_meter_mut().charge_or_halt(token) } /// Call (possibly transferring some amount of funds) into the specified account. @@ -445,11 +445,21 @@ pub trait PrecompileExt: sealing::Sealed { fn chain_id(&self) -> u64; /// Get an immutable reference to the nested resource meter of the frame. + #[deprecated(note = "Renamed to `frame_meter`; this alias will be removed in future versions")] fn gas_meter(&self) -> &FrameMeter; /// Get a mutable reference to the nested resource meter of the frame. + #[deprecated( + note = "Renamed to `frame_meter_mut`; this alias will be removed in future versions" + )] fn gas_meter_mut(&mut self) -> &mut FrameMeter; + /// Get an immutable reference to the nested resource meter of the frame. + fn frame_meter(&self) -> &FrameMeter; + + /// Get a mutable reference to the nested resource meter of the frame. + fn frame_meter_mut(&mut self) -> &mut FrameMeter; + /// Recovers ECDSA compressed public key based on signature and message hash. fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; @@ -2004,7 +2014,7 @@ where E::from_evm_init_code(initcode, sender.clone())? }, Code::Existing(hash) => { - let executable = E::from_storage(*hash, self.gas_meter_mut())?; + let executable = E::from_storage(*hash, self.frame_meter_mut())?; ensure!(executable.code_info().is_pvm(), >::EvmConstructedFromHash); executable }, @@ -2331,6 +2341,15 @@ where &mut self.top_frame_mut().frame_meter } + fn frame_meter(&self) -> &FrameMeter { + &self.top_frame().frame_meter + } + + #[inline] + fn frame_meter_mut(&mut self) -> &mut FrameMeter { + &mut self.top_frame_mut().frame_meter + } + fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()> { secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ()) } diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index d2c4f41b14af4..bfe8b4cd159e8 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -169,6 +169,14 @@ impl PrecompileExt for MockExt { &mut self.frame_meter } + fn frame_meter(&self) -> &FrameMeter { + &self.frame_meter + } + + fn frame_meter_mut(&mut self) -> &mut FrameMeter { + &mut self.frame_meter + } + fn ecdsa_recover( &self, _signature: &[u8; 65], diff --git a/substrate/frame/revive/src/precompiles/builtin/blake2f.rs b/substrate/frame/revive/src/precompiles/builtin/blake2f.rs index d2ca9f207f32c..9fd2d67b1581c 100644 --- a/substrate/frame/revive/src/precompiles/builtin/blake2f.rs +++ b/substrate/frame/revive/src/precompiles/builtin/blake2f.rs @@ -46,7 +46,7 @@ impl PrimitivePrecompile for Blake2F { rounds_buf.copy_from_slice(&input[0..4]); let rounds: u32 = u32::from_be_bytes(rounds_buf); - env.gas_meter_mut().charge_weight_token(RuntimeCosts::Blake2F(rounds))?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::Blake2F(rounds))?; // we use from_le_bytes below to effectively swap byte order to LE if architecture is BE diff --git a/substrate/frame/revive/src/precompiles/builtin/bn128.rs b/substrate/frame/revive/src/precompiles/builtin/bn128.rs index 345711ee3c5b3..b091800c799da 100644 --- a/substrate/frame/revive/src/precompiles/builtin/bn128.rs +++ b/substrate/frame/revive/src/precompiles/builtin/bn128.rs @@ -38,7 +38,7 @@ impl PrimitivePrecompile for Bn128Add { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::Bn128Add)?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::Bn128Add)?; let p1 = read_point(&input, 0)?; let p2 = read_point(&input, 64)?; @@ -66,7 +66,7 @@ impl PrimitivePrecompile for Bn128Mul { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::Bn128Mul)?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::Bn128Mul)?; let p = read_point(&input, 0)?; let fr = read_fr(&input, 64)?; @@ -99,12 +99,12 @@ impl PrimitivePrecompile for Bn128Pairing { } let ret_val = if input.is_empty() { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::Bn128Pairing(0))?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::Bn128Pairing(0))?; U256::one() } else { // (a, b_a, b_b - each 64-byte affine coordinates) let elements = input.len() / 192; - env.gas_meter_mut() + env.frame_meter_mut() .charge_weight_token(RuntimeCosts::Bn128Pairing(elements as u32))?; let mut vals = Vec::new(); diff --git a/substrate/frame/revive/src/precompiles/builtin/ecrecover.rs b/substrate/frame/revive/src/precompiles/builtin/ecrecover.rs index 0a6ab9b6c3dfb..6d13b84d01f8d 100644 --- a/substrate/frame/revive/src/precompiles/builtin/ecrecover.rs +++ b/substrate/frame/revive/src/precompiles/builtin/ecrecover.rs @@ -35,7 +35,7 @@ impl PrimitivePrecompile for EcRecover { i: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::EcdsaRecovery)?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::EcdsaRecovery)?; let mut input = [0u8; 128]; let len = i.len().min(128); input[..len].copy_from_slice(&i[..len]); diff --git a/substrate/frame/revive/src/precompiles/builtin/identity.rs b/substrate/frame/revive/src/precompiles/builtin/identity.rs index 01dc2e376e4e8..899e8b200fcd4 100644 --- a/substrate/frame/revive/src/precompiles/builtin/identity.rs +++ b/substrate/frame/revive/src/precompiles/builtin/identity.rs @@ -35,7 +35,7 @@ impl PrimitivePrecompile for Identity { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut() + env.frame_meter_mut() .charge_weight_token(RuntimeCosts::Identity(input.len() as _))?; Ok(input) } diff --git a/substrate/frame/revive/src/precompiles/builtin/modexp.rs b/substrate/frame/revive/src/precompiles/builtin/modexp.rs index fa3a36043c2f1..ec80008aaeda7 100644 --- a/substrate/frame/revive/src/precompiles/builtin/modexp.rs +++ b/substrate/frame/revive/src/precompiles/builtin/modexp.rs @@ -99,7 +99,7 @@ impl PrimitivePrecompile for Modexp { // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to // handle empty base first. let r = if base_len == 0 && mod_len == 0 { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::Modexp(MIN_GAS_COST))?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::Modexp(MIN_GAS_COST))?; BigUint::zero() } else { @@ -125,7 +125,7 @@ impl PrimitivePrecompile for Modexp { modulus.is_even(), ); - env.gas_meter_mut().charge_weight_token(RuntimeCosts::Modexp(gas_cost))?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::Modexp(gas_cost))?; if modulus.is_zero() || modulus.is_one() { BigUint::zero() @@ -389,9 +389,9 @@ mod tests { let mut call_setup = CallSetup::::default(); let (mut ext, _) = call_setup.ext(); - let before = ext.gas_meter().weight_consumed(); + let before = ext.frame_meter().weight_consumed(); >::call(&>::MATCHER.base_address(), input, &mut ext).unwrap(); - let after = ext.gas_meter().weight_consumed(); + let after = ext.frame_meter().weight_consumed(); // 7104 * 20 gas used when ran in geth (x20) assert_eq!(after - before, Token::::weight(&RuntimeCosts::Modexp(7104 * 20))); diff --git a/substrate/frame/revive/src/precompiles/builtin/p256_verify.rs b/substrate/frame/revive/src/precompiles/builtin/p256_verify.rs index 7a0550663d377..ae08ab28c9b44 100644 --- a/substrate/frame/revive/src/precompiles/builtin/p256_verify.rs +++ b/substrate/frame/revive/src/precompiles/builtin/p256_verify.rs @@ -51,7 +51,7 @@ impl PrimitivePrecompile for P256Verify { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::P256Verify)?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::P256Verify)?; if revm::precompile::secp256r1::verify_impl(&input).is_some() { Ok(U256::one().to_big_endian().to_vec()) diff --git a/substrate/frame/revive/src/precompiles/builtin/ripemd160.rs b/substrate/frame/revive/src/precompiles/builtin/ripemd160.rs index 0b1916e351a5f..288a8159fecf6 100644 --- a/substrate/frame/revive/src/precompiles/builtin/ripemd160.rs +++ b/substrate/frame/revive/src/precompiles/builtin/ripemd160.rs @@ -36,7 +36,7 @@ impl PrimitivePrecompile for Ripemd160 { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut() + env.frame_meter_mut() .charge_weight_token(RuntimeCosts::Ripemd160(input.len() as _))?; let mut ret = [0u8; 32]; ret[12..32].copy_from_slice(&ripemd::Ripemd160::digest(input)); diff --git a/substrate/frame/revive/src/precompiles/builtin/sha256.rs b/substrate/frame/revive/src/precompiles/builtin/sha256.rs index 4f8b9aa07919d..e977cb96894b5 100644 --- a/substrate/frame/revive/src/precompiles/builtin/sha256.rs +++ b/substrate/frame/revive/src/precompiles/builtin/sha256.rs @@ -35,7 +35,7 @@ impl PrimitivePrecompile for Sha256 { input: Vec, env: &mut impl Ext, ) -> Result, Error> { - env.gas_meter_mut() + env.frame_meter_mut() .charge_weight_token(RuntimeCosts::HashSha256(input.len() as _))?; let data = sp_io::hashing::sha2_256(&input).to_vec(); Ok(data) diff --git a/substrate/frame/revive/src/precompiles/builtin/storage.rs b/substrate/frame/revive/src/precompiles/builtin/storage.rs index a2590031f1cf5..6049fcdcec0c8 100644 --- a/substrate/frame/revive/src/precompiles/builtin/storage.rs +++ b/substrate/frame/revive/src/precompiles/builtin/storage.rs @@ -68,7 +68,7 @@ impl BuiltinPrecompile for Storage { RuntimeCosts::ClearStorage(len) } }; - let charged = env.gas_meter_mut().charge_weight_token(costs(max_size))?; + let charged = env.frame_meter_mut().charge_weight_token(costs(max_size))?; let key = decode_key(key.as_bytes_ref(), *isFixedKey) .map_err(|_| Error::Revert("failed decoding key".into()))?; let outcome = if transient { @@ -80,7 +80,7 @@ impl BuiltinPrecompile for Storage { }; let contained_key = outcome != WriteOutcome::New; let ret = (contained_key, outcome.old_len()); - env.gas_meter_mut().adjust_weight(charged, costs(outcome.old_len())); + env.frame_meter_mut().adjust_weight(charged, costs(outcome.old_len())); Ok(ret.abi_encode()) }, IStorageCalls::containsStorage(IStorage::containsStorageCall { @@ -97,7 +97,7 @@ impl BuiltinPrecompile for Storage { RuntimeCosts::ContainsStorage(len) } }; - let charged = env.gas_meter_mut().charge_weight_token(costs(max_size))?; + let charged = env.frame_meter_mut().charge_weight_token(costs(max_size))?; let key = decode_key(key.as_bytes_ref(), *isFixedKey) .map_err(|_| Error::Revert("failed decoding key".into()))?; let outcome = if transient { @@ -107,7 +107,7 @@ impl BuiltinPrecompile for Storage { }; let value_len = outcome.unwrap_or(0); let ret = (outcome.is_some(), value_len); - env.gas_meter_mut().adjust_weight(charged, costs(value_len)); + env.frame_meter_mut().adjust_weight(charged, costs(value_len)); Ok(ret.abi_encode()) }, IStorageCalls::takeStorage(IStorage::takeStorageCall { flags, key, isFixedKey }) => { @@ -120,7 +120,7 @@ impl BuiltinPrecompile for Storage { RuntimeCosts::TakeStorage(len) } }; - let charged = env.gas_meter_mut().charge_weight_token(costs(max_size))?; + let charged = env.frame_meter_mut().charge_weight_token(costs(max_size))?; let key = decode_key(key.as_bytes_ref(), *isFixedKey) .map_err(|_| Error::Revert("failed decoding key".into()))?; let outcome = if transient { @@ -130,10 +130,10 @@ impl BuiltinPrecompile for Storage { }; if let crate::storage::WriteOutcome::Taken(value) = outcome { - env.gas_meter_mut().adjust_weight(charged, costs(value.len() as u32)); + env.frame_meter_mut().adjust_weight(charged, costs(value.len() as u32)); Ok(value.abi_encode()) } else { - env.gas_meter_mut().adjust_weight(charged, costs(0)); + env.frame_meter_mut().adjust_weight(charged, costs(0)); Ok(Vec::::new().abi_encode()) } }, diff --git a/substrate/frame/revive/src/precompiles/builtin/system.rs b/substrate/frame/revive/src/precompiles/builtin/system.rs index bca89298e51f8..e465fcbfadf12 100644 --- a/substrate/frame/revive/src/precompiles/builtin/system.rs +++ b/substrate/frame/revive/src/precompiles/builtin/system.rs @@ -47,54 +47,54 @@ impl BuiltinPrecompile for System { ISystemCalls::terminate(_) if env.is_read_only() => Err(crate::Error::::StateChangeDenied.into()), ISystemCalls::hashBlake256(ISystem::hashBlake256Call { input }) => { - env.gas_meter_mut() + env.frame_meter_mut() .charge_weight_token(RuntimeCosts::HashBlake256(input.len() as u32))?; let output = sp_io::hashing::blake2_256(input.as_bytes_ref()); Ok(output.abi_encode()) }, ISystemCalls::hashBlake128(ISystem::hashBlake128Call { input }) => { - env.gas_meter_mut() + env.frame_meter_mut() .charge_weight_token(RuntimeCosts::HashBlake128(input.len() as u32))?; let output = sp_io::hashing::blake2_128(input.as_bytes_ref()); Ok(output.abi_encode()) }, ISystemCalls::toAccountId(ISystem::toAccountIdCall { input }) => { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::ToAccountId)?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::ToAccountId)?; let account_id = env.to_account_id(&H160::from_slice(input.as_slice())); Ok(account_id.encode().abi_encode()) }, ISystemCalls::callerIsOrigin(ISystem::callerIsOriginCall {}) => { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::CallerIsOrigin)?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::CallerIsOrigin)?; let is_origin = env.caller_is_origin(true); Ok(is_origin.abi_encode()) }, ISystemCalls::callerIsRoot(ISystem::callerIsRootCall {}) => { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::CallerIsRoot)?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::CallerIsRoot)?; let is_root = env.caller_is_root(true); Ok(is_root.abi_encode()) }, ISystemCalls::ownCodeHash(ISystem::ownCodeHashCall {}) => { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::OwnCodeHash)?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::OwnCodeHash)?; let caller = env.caller(); let addr = T::AddressMapper::to_address(caller.account_id()?); let output = env.code_hash(&addr.into()).0.abi_encode(); Ok(output) }, ISystemCalls::minimumBalance(ISystem::minimumBalanceCall {}) => { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::MinimumBalance)?; + env.frame_meter_mut().charge_weight_token(RuntimeCosts::MinimumBalance)?; let minimum_balance = env.minimum_balance(); Ok(minimum_balance.to_big_endian().abi_encode()) }, ISystemCalls::weightLeft(ISystem::weightLeftCall {}) => { - env.gas_meter_mut().charge_weight_token(RuntimeCosts::WeightLeft)?; - let ref_time = env.gas_meter().weight_left().unwrap_or_default().ref_time(); - let proof_size = env.gas_meter().weight_left().unwrap_or_default().proof_size(); + env.frame_meter_mut().charge_weight_token(RuntimeCosts::WeightLeft)?; + let ref_time = env.frame_meter().weight_left().unwrap_or_default().ref_time(); + let proof_size = env.frame_meter().weight_left().unwrap_or_default().proof_size(); let res = (ref_time, proof_size); Ok(res.abi_encode()) }, ISystemCalls::terminate(ISystem::terminateCall { beneficiary }) => { // no need to adjust gas because this always deletes code - env.gas_meter_mut() + env.frame_meter_mut() .charge_weight_token(RuntimeCosts::Terminate { code_removed: true })?; let h160 = H160::from_slice(beneficiary.as_slice()); env.terminate_caller(&h160).map_err(Error::try_to_revert::)?; diff --git a/substrate/frame/revive/src/tests/precompiles.rs b/substrate/frame/revive/src/tests/precompiles.rs index a6039c1a9987e..06e15d406780d 100644 --- a/substrate/frame/revive/src/tests/precompiles.rs +++ b/substrate/frame/revive/src/tests/precompiles.rs @@ -90,7 +90,7 @@ impl Precompile for NoInfo { INoInfoCalls::errors(INoInfo::errorsCall {}) => Err(Error::Error(DispatchError::Other("precompile failed").into())), INoInfoCalls::consumeMaxGas(INoInfo::consumeMaxGasCall {}) => { - env.gas_meter_mut().charge_weight_token(MaxGasToken)?; + env.frame_meter_mut().charge_weight_token(MaxGasToken)?; Ok(Vec::new()) }, INoInfoCalls::callRuntime(INoInfo::callRuntimeCall { call }) => { diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index ab4962a520872..b3ba3228a29cd 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -192,10 +192,8 @@ fn run_call<'a, E: Ext>( ) -> ControlFlow { // We use ALL_STIPEND to detect the typical gas limit solc defines as a call stipend // This is just a heuristic - let add_stipend = !value.is_zero() || - gas_limit - .try_into() - .is_ok_and(|limit: u64| limit == CALL_STIPEND); + let add_stipend = + !value.is_zero() || gas_limit.try_into().is_ok_and(|limit: u64| limit == CALL_STIPEND); let call_result = match scheme { CallScheme::Call | CallScheme::StaticCall => interpreter.ext.call( @@ -231,7 +229,7 @@ fn run_call<'a, E: Ext>( // success or revert interpreter .ext - .gas_meter_mut() + .frame_meter_mut() .charge_or_halt(RuntimeCosts::CopyToContract(target_len as u32))?; let return_value = interpreter.ext.last_frame_output(); diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs index 4b6cfbc990f72..436e5eed649c0 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs @@ -68,19 +68,18 @@ pub fn charge_call_gas<'a, E: Ext>( match precompile { Some(precompile) => { // Base cost depending on contract info - interpreter - .ext - .gas_meter_mut() - .charge_or_halt(if precompile.has_contract_info() { + interpreter.ext.frame_meter_mut().charge_or_halt( + if precompile.has_contract_info() { RuntimeCosts::PrecompileWithInfoBase } else { RuntimeCosts::PrecompileBase - })?; + }, + )?; // Cost for decoding input interpreter .ext - .gas_meter_mut() + .frame_meter_mut() .charge_or_halt(RuntimeCosts::PrecompileDecode(input_len as u32))?; }, None => { @@ -93,14 +92,14 @@ pub fn charge_call_gas<'a, E: Ext>( interpreter .ext - .gas_meter_mut() + .frame_meter_mut() .charge_or_halt(RuntimeCosts::CopyFromContract(input_len as u32))?; }, }; if !value.is_zero() { interpreter .ext - .gas_meter_mut() + .frame_meter_mut() .charge_or_halt(RuntimeCosts::CallTransferSurcharge { dust_transfer: Pallet::::has_dust(value), })?; diff --git a/substrate/frame/revive/src/vm/evm/instructions/control.rs b/substrate/frame/revive/src/vm/evm/instructions/control.rs index 83c66f0d9847e..658d564294a8f 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/control.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/control.rs @@ -127,7 +127,7 @@ pub fn stop(_interpreter: &mut Interpreter) -> ControlFlow { /// Invalid opcode. This opcode halts the execution. pub fn invalid(interpreter: &mut Interpreter) -> ControlFlow { - interpreter.ext.gas_meter_mut().consume_all_weight(); + interpreter.ext.frame_meter_mut().consume_all_weight(); ControlFlow::Break(Error::::InvalidInstruction.into()) } diff --git a/substrate/frame/revive/src/vm/evm/instructions/host.rs b/substrate/frame/revive/src/vm/evm/instructions/host.rs index 82205da3a7140..c7073738489af 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/host.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/host.rs @@ -152,7 +152,7 @@ fn store_helper<'ext, E: Ext>( return ControlFlow::Break(Error::::ContractTrapped.into()); }; - interpreter.ext.gas_meter_mut().adjust_weight( + interpreter.ext.frame_meter_mut().adjust_weight( charged_amount, adjust_cost(value_to_store.unwrap_or_default().len() as u32, write_outcome.old_len()), ); diff --git a/substrate/frame/revive/src/vm/evm/instructions/system.rs b/substrate/frame/revive/src/vm/evm/instructions/system.rs index a112fc35b569e..92167a0248366 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/system.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/system.rs @@ -41,7 +41,7 @@ pub fn keccak256(interpreter: &mut Interpreter) -> ControlFlow let len = as_usize_or_halt::(*top)?; interpreter .ext - .gas_meter_mut() + .frame_meter_mut() .charge_or_halt(RuntimeCosts::HashKeccak256(len as u32))?; let hash = if len == 0 { diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index df61f946b2d1d..7ec2b3d09f28e 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -270,7 +270,7 @@ impl fmt::Display for TrapReason { /// a function won't work out. macro_rules! charge_gas { ($runtime:expr, $costs:expr) => {{ - $runtime.ext.gas_meter_mut().charge_weight_token($costs) + $runtime.ext.frame_meter_mut().charge_weight_token($costs) }}; } @@ -356,7 +356,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { /// This is when a maximum a priori amount was charged and then should be partially /// refunded to match the actual amount. fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) { - self.ext.gas_meter_mut().adjust_weight(charged, actual_costs); + self.ext.frame_meter_mut().adjust_weight(charged, actual_costs); } /// Write the given buffer and its length to the designated locations in sandbox memory and @@ -844,7 +844,7 @@ impl<'a, E: Ext> PreparedCall<'a, E> { break exec_result } }; - self.runtime.ext().gas_meter_mut().sync_from_executor(self.instance.gas())?; + self.runtime.ext().frame_meter_mut().sync_from_executor(self.instance.gas())?; exec_result } diff --git a/substrate/frame/revive/src/vm/pvm/env.rs b/substrate/frame/revive/src/vm/pvm/env.rs index fde470a4b0edb..60a7b8cce5c06 100644 --- a/substrate/frame/revive/src/vm/pvm/env.rs +++ b/substrate/frame/revive/src/vm/pvm/env.rs @@ -76,7 +76,7 @@ impl ContractBlob { .ok_or_else(|| >::CodeRejected)? .program_counter(); - let gas_limit_polkavm: polkavm::Gas = runtime.ext().gas_meter_mut().sync_to_executor(); + let gas_limit_polkavm: polkavm::Gas = runtime.ext().frame_meter_mut().sync_to_executor(); let mut instance = module.instantiate().map_err(|err| { log::debug!(target: LOG_TARGET, "failed to instantiate polkavm module: {err:?}"); @@ -845,7 +845,7 @@ pub mod env { /// See [`pallet_revive_uapi::HostFn::consume_all_gas`]. #[stable] fn consume_all_gas(&mut self, memory: &mut M) -> Result<(), TrapReason> { - self.ext.gas_meter_mut().consume_all_weight(); + self.ext.frame_meter_mut().consume_all_weight(); Err(TrapReason::Return(ReturnData { flags: ReturnFlags::REVERT.bits(), data: Default::default(), From c40e273e8b3062a80e262995cb4b4dcc7bb69160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 27 Nov 2025 03:35:46 -0300 Subject: [PATCH 54/69] Simplify deposit refund tests --- substrate/frame/assets/precompiles/src/lib.rs | 2 +- .../revive/fixtures/contracts/Deposit.sol | 61 +++++++++++++++---- substrate/frame/revive/src/exec.rs | 1 - substrate/frame/revive/src/metering/tests.rs | 41 +++++++++---- 4 files changed, 79 insertions(+), 26 deletions(-) diff --git a/substrate/frame/assets/precompiles/src/lib.rs b/substrate/frame/assets/precompiles/src/lib.rs index c39990013a2ec..19b24a4db3f08 100644 --- a/substrate/frame/assets/precompiles/src/lib.rs +++ b/substrate/frame/assets/precompiles/src/lib.rs @@ -167,7 +167,7 @@ where fn deposit_event(env: &mut impl Ext, event: IERC20Events) -> Result<(), Error> { let (topics, data) = event.into_log_data().split(); let topics = topics.into_iter().map(|v| H256(v.0)).collect::>(); - env.gas_meter_mut().charge_weight_token(RuntimeCosts::DepositEvent { + env.frame_meter_mut().charge_weight_token(RuntimeCosts::DepositEvent { num_topic: topics.len() as u32, len: topics.len() as u32, })?; diff --git a/substrate/frame/revive/fixtures/contracts/Deposit.sol b/substrate/frame/revive/fixtures/contracts/Deposit.sol index 32daf3ef4bc3d..21c95470d513d 100644 --- a/substrate/frame/revive/fixtures/contracts/Deposit.sol +++ b/substrate/frame/revive/fixtures/contracts/Deposit.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.20; -contract Deposit { +import "@revive/IStorage.sol"; + +contract DepositPrecompile { uint256 a; uint256 b; - address immutable storagePrecompile = address(0x901); - function clearStorageSlot(uint256 slot) internal { bytes memory key = abi.encodePacked(bytes32(slot)); - (bool _success, ) = storagePrecompile.delegatecall( - abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, key) + (bool _success, ) = STORAGE_ADDR.delegatecall( + abi.encodeWithSelector(IStorage.clearStorage.selector, 0, true, key) ); } @@ -26,8 +26,8 @@ contract Deposit { bytes32 key = bytes32(bytes1(0x01)) >> 248; bytes memory keyBytes = abi.encodePacked(key); - (bool success, ) = storagePrecompile.delegatecall( - abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, keyBytes) + (bool success, ) = STORAGE_ADDR.delegatecall( + abi.encodeWithSelector(IStorage.clearStorage.selector, 0, true, keyBytes) ); } @@ -37,8 +37,8 @@ contract Deposit { bytes32 key = bytes32(bytes1(0x01)) >> 248; bytes memory keyBytes = abi.encodePacked(key); - (bool success, ) = storagePrecompile.delegatecall( - abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, keyBytes) + (bool success, ) = STORAGE_ADDR.delegatecall( + abi.encodeWithSelector(IStorage.clearStorage.selector, 0, true, keyBytes) ); } @@ -59,8 +59,47 @@ contract Deposit { bytes32 key = bytes32(bytes1(0x01)) >> 248; bytes memory keyBytes = abi.encodePacked(key); - (bool success, ) = storagePrecompile.delegatecall( - abi.encodeWithSignature("clearStorage(uint32,bool,bytes)", 0, true, keyBytes) + (bool success, ) = STORAGE_ADDR.delegatecall( + abi.encodeWithSelector(IStorage.clearStorage.selector, 0, true, keyBytes) ); } +} + +contract DepositDirect { + uint256 a; + uint256 b; + + function clearAll() external { + a = 0; + b = 0; + } + + function setAndClear() external { + a = 2; + b = 3; + b = 0; + } + + function callSetAndClear() external { + this.setVars(); + + b = 0; + } + + function setAndCallClear() external { + a = 2; + b = 3; + + this.clear(); + } + + + function setVars() external { + a = 2; + b = 3; + } + + function clear() external { + b = 0; + } } \ No newline at end of file diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 0ee818eb5492b..8d19f935dd224 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1559,7 +1559,6 @@ where } } } else { - // TODO: iterate contracts_to_be_destroyed and destroy each contract if !persist { self.transaction_meter .absorb_weight_meter_only(mem::take(&mut self.first_frame.frame_meter)); diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs index 53293b8424399..4b72024a5d6fc 100644 --- a/substrate/frame/revive/src/metering/tests.rs +++ b/substrate/frame/revive/src/metering/tests.rs @@ -24,7 +24,7 @@ use crate::{ use alloy_core::sol_types::SolCall; use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{ - compile_module_with_type, CatchConstructorTest, Deposit, FixtureType, + compile_module_with_type, CatchConstructorTest, DepositPrecompile, FixtureType, }; use sp_runtime::{FixedU128, Weight}; use test_case::test_case; @@ -43,10 +43,12 @@ enum Charge { D(i64), } -#[test_case(FixtureType::Solc ; "solc")] -#[test_case(FixtureType::Resolc ; "resolc")] -fn max_consumed_deposit_integration(fixture_type: FixtureType) { - let (code, _) = compile_module_with_type("Deposit", fixture_type).unwrap(); +#[test_case(FixtureType::Solc , "DepositPrecompile" ; "solc precompiles")] +#[test_case(FixtureType::Resolc , "DepositPrecompile" ; "resolc precompiles")] +#[test_case(FixtureType::Solc , "DepositDirect" ; "solc direct")] +#[test_case(FixtureType::Resolc , "DepositDirect" ; "resolc direct")] +fn max_consumed_deposit_integration(fixture_type: FixtureType, fixture_name: &str) { + let (code, _) = compile_module_with_type(fixture_name, fixture_type).unwrap(); ExtBuilder::default().build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); @@ -54,7 +56,9 @@ fn max_consumed_deposit_integration(fixture_type: FixtureType) { let Contract { addr: caller_addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let result = builder::bare_call(caller_addr).data(Deposit::callSetAndClearCall {}.abi_encode()).build(); + let result = builder::bare_call(caller_addr) + .data(DepositPrecompile::callSetAndClearCall {}.abi_encode()) + .build(); assert_eq!(result.storage_deposit, StorageDeposit::Charge(66)); assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); @@ -62,10 +66,15 @@ fn max_consumed_deposit_integration(fixture_type: FixtureType) { } #[ignore = "TODO: Does not work yet, see https://github.com/paritytech/contract-issues/issues/213"] -#[test_case(FixtureType::Solc ; "solc")] -#[test_case(FixtureType::Resolc ; "resolc")] -fn max_consumed_deposit_integration_refunds_subframes(fixture_type: FixtureType) { - let (code, _) = compile_module_with_type("Deposit", fixture_type).unwrap(); +#[test_case(FixtureType::Solc , "DepositPrecompile" ; "solc precompiles")] +#[test_case(FixtureType::Resolc , "DepositPrecompile" ; "resolc precompiles")] +#[test_case(FixtureType::Solc , "DepositDirect" ; "solc direct")] +#[test_case(FixtureType::Resolc , "DepositDirect" ; "resolc direct")] +fn max_consumed_deposit_integration_refunds_subframes( + fixture_type: FixtureType, + fixture_name: &str, +) { + let (code, _) = compile_module_with_type(fixture_name, fixture_type).unwrap(); ExtBuilder::default().build().execute_with(|| { let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); @@ -73,14 +82,20 @@ fn max_consumed_deposit_integration_refunds_subframes(fixture_type: FixtureType) let Contract { addr: caller_addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - let result = builder::bare_call(caller_addr).data(Deposit::setAndClearCall {}.abi_encode()).build(); + let result = builder::bare_call(caller_addr) + .data(DepositPrecompile::setAndClearCall {}.abi_encode()) + .build(); assert_eq!(result.storage_deposit, StorageDeposit::Charge(66)); assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); - builder::bare_call(caller_addr).data(Deposit::clearAllCall {}.abi_encode()).build(); + builder::bare_call(caller_addr) + .data(DepositPrecompile::clearAllCall {}.abi_encode()) + .build(); - let result = builder::bare_call(caller_addr).data(Deposit::setAndCallClearCall {}.abi_encode()).build(); + let result = builder::bare_call(caller_addr) + .data(DepositPrecompile::setAndCallClearCall {}.abi_encode()) + .build(); assert_eq!(result.storage_deposit, StorageDeposit::Charge(66)); assert_eq!(result.max_storage_deposit, StorageDeposit::Charge(132)); From 7edd38f0ee411840e93a325e52fefd06aa45b5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 27 Nov 2025 03:50:08 -0300 Subject: [PATCH 55/69] Remove dead code --- .../revive/fixtures/contracts/Stipends.sol | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/substrate/frame/revive/fixtures/contracts/Stipends.sol b/substrate/frame/revive/fixtures/contracts/Stipends.sol index 8205b3dc77970..a5cf1fe3c9569 100644 --- a/substrate/frame/revive/fixtures/contracts/Stipends.sol +++ b/substrate/frame/revive/fixtures/contracts/Stipends.sol @@ -1,40 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -/** - * @title Sender - * @dev Sender contract: provides three transfer methods - */ -contract StipendSender { - // Method 1: transfer (2300 gas stipend) - function sendViaTransfer(address payable to) external payable { - to.transfer(msg.value); - } - - // Method 2: send (2300 gas stipend, returns bool) - function sendViaSend(address payable to) external payable returns (bool) { - return to.send(msg.value); - } - - // Method 3: call (forwards all gas) - function sendViaCall(address payable to) external payable returns (bool) { - (bool success, ) = to.call{value: msg.value}(""); - return success; - } - - receive() external payable {} -} - /** * @title DoNothingReceiver * @dev Receiver contract 1: empty receive(), does nothing */ contract DoNothingReceiver { receive() external payable {} - - function getBalance() external view returns (uint256) { - return address(this).balance; - } } /** @@ -47,10 +19,6 @@ contract SimpleReceiver { receive() external payable { emit Received(msg.sender, msg.value); } - - function getBalance() external view returns (uint256) { - return address(this).balance; - } } /** @@ -65,10 +33,6 @@ contract ComplexReceiver { counter += 1; emit Received(msg.sender, msg.value, counter); } - - function getBalance() external view returns (uint256) { - return address(this).balance; - } } /** From 9a412aea53a2e723d116272295e43852c963e03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 27 Nov 2025 04:02:39 -0300 Subject: [PATCH 56/69] Format --- substrate/frame/revive/src/tests/stipends.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/substrate/frame/revive/src/tests/stipends.rs b/substrate/frame/revive/src/tests/stipends.rs index 8159d177342cf..3204b66807e2a 100644 --- a/substrate/frame/revive/src/tests/stipends.rs +++ b/substrate/frame/revive/src/tests/stipends.rs @@ -29,10 +29,8 @@ fn evm_call_stipends_work_for_transfers() { let (code, _) = compile_module_with_type("StipendTest", FixtureType::Solc).unwrap(); ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance( - &crate::test_utils::ALICE, - 10_000_000_000_000, - ); + let _ = + ::Currency::set_balance(&crate::test_utils::ALICE, 10_000_000_000_000); let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); @@ -51,10 +49,8 @@ fn evm_call_stipends_work_for_sends() { let (code, _) = compile_module_with_type("StipendTest", FixtureType::Solc).unwrap(); ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance( - &crate::test_utils::ALICE, - 10_000_000_000_000, - ); + let _ = + ::Currency::set_balance(&crate::test_utils::ALICE, 10_000_000_000_000); let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); @@ -73,10 +69,8 @@ fn evm_call_stipends_work_for_calls() { let (code, _) = compile_module_with_type("StipendTest", FixtureType::Solc).unwrap(); ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance( - &crate::test_utils::ALICE, - 10_000_000_000_000, - ); + let _ = + ::Currency::set_balance(&crate::test_utils::ALICE, 10_000_000_000_000); let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); From 93bf186d8c5afb56671248fee734bc768619d9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 27 Nov 2025 04:21:44 -0300 Subject: [PATCH 57/69] Fix compiler errors --- substrate/frame/revive/src/exec.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 8d19f935dd224..01e39cef194f1 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -2496,9 +2496,16 @@ pub fn terminate_contract_for_benchmark( info: &ContractInfo, beneficiary: T::AccountId, ) -> Result<(), DispatchError> { - let mut meter = storage::meter::Meter::::new(BalanceOf::::max_value()); + use crate::TransactionLimits; + use num_traits::Bounded; + + let mut transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit { + weight_limit: Default::default(), + deposit_limit: BalanceOf::::max_value(), + }) + .unwrap(); Stack::>::do_terminate( - &mut meter, + &mut transaction_meter, &ExecConfig::new_substrate_tx(), contract, &Origin::from_account_id(origin), From bc26308275d6a46d52e0f4b6c4c86efa75b15d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 27 Nov 2025 04:42:51 -0300 Subject: [PATCH 58/69] Fix benchmark code --- substrate/frame/revive/src/benchmarking.rs | 29 ++++++++--- substrate/frame/revive/src/exec.rs | 59 +++++++++------------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index 01cf9bbece526..46ff22907aac6 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -24,7 +24,7 @@ use crate::{ block_hash::EthereumBlockBuilder, block_storage, TransactionLegacyUnsigned, TransactionSigned, TransactionUnsigned, }, - exec::{Key, PrecompileExt}, + exec::{Key, Origin as ExecOrigin, PrecompileExt}, limits, precompiles::{ self, @@ -1229,14 +1229,31 @@ mod benchmarks { T::Currency::set_balance(&instance.account_id, Pallet::::min_balance() * 10u32.into()); + let mut transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit { + weight_limit: Default::default(), + deposit_limit: BalanceOf::::max_value(), + }) + .unwrap(); + let exec_config = ExecConfig::new_substrate_tx(); + let contract_account = &instance.account_id; + let origin = &ExecOrigin::from_account_id(caller); + let beneficiary_clone = beneficiary.clone(); + let trie_id = instance.info()?.trie_id.clone(); + let code_hash = instance.info()?.code_hash; + let only_if_same_tx = false; + let result; #[block] { - result = crate::exec::terminate_contract_for_benchmark::( - caller, - &instance.account_id, - &instance.info()?, - beneficiary.clone(), + result = crate::exec::bench_do_terminate::( + &mut transaction_meter, + &exec_config, + contract_account, + &origin, + beneficiary_clone, + trie_id, + code_hash, + only_if_same_tx, ); } result.unwrap(); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 01e39cef194f1..34620c95bf30f 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -249,6 +249,18 @@ impl Default for CallResources { } } +/// Stored inside the `Stack` for each contract that is scheduled for termination. +struct TerminateArgs { + /// Where to send the free balance of the terminated contract. + beneficiary: T::AccountId, + /// The storage child trie of the contract that needs to be deleted. + trie_id: TrieId, + /// The code referenced by the contract. Will be deleted if refcount drops to zero. + code_hash: H256, + /// Triggered by the EVM opcode. + only_if_same_tx: bool, +} + /// Environment functions only available to host functions. pub trait Ext: PrecompileWithInfoExt { /// Execute code in the current frame. @@ -2490,44 +2502,23 @@ pub fn is_precompile>(address: &H160) -> bool { } #[cfg(feature = "runtime-benchmarks")] -pub fn terminate_contract_for_benchmark( - origin: T::AccountId, - contract: &T::AccountId, - info: &ContractInfo, +pub fn bench_do_terminate( + transaction_meter: &mut TransactionMeter, + exec_config: &ExecConfig, + contract_account: &T::AccountId, + origin: &Origin, beneficiary: T::AccountId, -) -> Result<(), DispatchError> { - use crate::TransactionLimits; - use num_traits::Bounded; - - let mut transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit { - weight_limit: Default::default(), - deposit_limit: BalanceOf::::max_value(), - }) - .unwrap(); - Stack::>::do_terminate( - &mut transaction_meter, - &ExecConfig::new_substrate_tx(), - contract, - &Origin::from_account_id(origin), - &TerminateArgs { - beneficiary, - trie_id: info.trie_id.clone(), - code_hash: info.code_hash, - only_if_same_tx: false, - }, - ) -} - -/// Stored inside the `Stack` for each contract that is scheduled for termination. -struct TerminateArgs { - /// Where to send the free balance of the terminated contract. - beneficiary: T::AccountId, - /// The storage child trie of the contract that needs to be deleted. trie_id: TrieId, - /// The code referenced by the contract. Will be deleted if refcount drops to zero. code_hash: H256, - /// Triggered by the EVM opcode. only_if_same_tx: bool, +) -> Result<(), DispatchError> { + Stack::>::do_terminate( + transaction_meter, + exec_config, + contract_account, + origin, + &TerminateArgs { beneficiary, trie_id, code_hash, only_if_same_tx }, + ) } mod sealing { From 6c80641a717bbe4374801e0b02ae1eeb6ba847c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:11:05 -0300 Subject: [PATCH 59/69] Add doc comment and integrity check --- substrate/frame/revive/src/lib.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index b3f27bf696383..70af538dc0472 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -350,6 +350,23 @@ pub mod pallet { #[pallet::constant] type DebugEnabled: Get; + /// This determines the relative scale of our gas price and gas estimates. + /// + /// By default, the gas price (in wei) is `FeeInfo::next_fee_multiplier()` multiplied by + /// `NativeToEthRatio`. `GasScale` allows to scale this value: the actual gas price is the + /// default gas price multiplied by `GasScale`. + /// + /// As a consequence, gas cost (gas estimates and actual gas usage during transaction) is + /// scaled down by the same factor. Thus, the total transaction cost is not affected by + /// `GasScale` – apart from rounding differences: the transaction cost is always a multiple + /// of the gas price and is derived by rounded up, so that with higher `GasScales` this can + /// lead to higher gas cost as the rounding difference would be larger. + /// + /// The main purpose of changing the `GasScale` is to tune the gas cost so that it is closer + /// to standard EVM gas cost and contracts will not run out of gas when tools or code + /// assume hard coded gas limits. + /// + /// Invariant: `GasScale` must no be 0 #[pallet::constant] #[pallet::no_default_bounds] type GasScale: Get>; @@ -900,6 +917,8 @@ pub mod pallet { fn integrity_test() { assert!(T::ChainId::get() > 0, "ChainId must be greater than 0"); + assert!(T::GasScale::get() > 0, "GasScale must no be 0"); + T::FeeInfo::integrity_test(); // The memory available in the block building runtime From ec0b8fb4c920fd2b90b0d03660cb9a862427a856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:13:00 -0300 Subject: [PATCH 60/69] Fix typo --- substrate/frame/revive/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 70af538dc0472..4073bf1f596a8 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -366,7 +366,7 @@ pub mod pallet { /// to standard EVM gas cost and contracts will not run out of gas when tools or code /// assume hard coded gas limits. /// - /// Invariant: `GasScale` must no be 0 + /// Requirement: `GasScale` must not be 0 #[pallet::constant] #[pallet::no_default_bounds] type GasScale: Get>; @@ -917,7 +917,7 @@ pub mod pallet { fn integrity_test() { assert!(T::ChainId::get() > 0, "ChainId must be greater than 0"); - assert!(T::GasScale::get() > 0, "GasScale must no be 0"); + assert!(T::GasScale::get() > 0.into(), "GasScale must no be 0"); T::FeeInfo::integrity_test(); From 54a9c43cf2f38dca688416e0faeffcee5cab084a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:33:00 -0300 Subject: [PATCH 61/69] Fix cargo check error --- substrate/frame/revive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 4073bf1f596a8..b8ecaf3286825 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -917,7 +917,7 @@ pub mod pallet { fn integrity_test() { assert!(T::ChainId::get() > 0, "ChainId must be greater than 0"); - assert!(T::GasScale::get() > 0.into(), "GasScale must no be 0"); + assert!(T::GasScale::get() > 0u32.into(), "GasScale must no be 0"); T::FeeInfo::integrity_test(); From 94f1a05d9a59485b8623c3efd7ad23fb8e8e0340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:15:10 -0300 Subject: [PATCH 62/69] Increase gas scale further on dev node --- substrate/frame/revive/dev-node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index 32276fa837809..d54a3c2b7bf7d 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -356,7 +356,7 @@ impl pallet_revive::Config for Runtime { type Time = Timestamp; type FeeInfo = FeeInfo; type DebugEnabled = ConstBool; - type GasScale = ConstU128<50000>; + type GasScale = ConstU128<500000>; } pallet_revive::impl_runtime_apis_plus_revive_traits!( From 72fde9322c3d4ddcb0244347756c49efc2f811ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:22:54 -0300 Subject: [PATCH 63/69] Implement PR feedback --- .../revive/fixtures/contracts/Deposit.sol | 11 +- substrate/frame/revive/rpc/src/lib.rs | 2 +- substrate/frame/revive/src/evm/fees.rs | 5 + substrate/frame/revive/src/exec.rs | 11 +- substrate/frame/revive/src/lib.rs | 6 +- substrate/frame/revive/src/metering/math.rs | 6 +- substrate/frame/revive/src/metering/mod.rs | 70 ++- .../frame/revive/src/metering/storage.rs | 529 +----------------- .../revive/src/metering/storage/tests.rs | 518 +++++++++++++++++ substrate/frame/revive/src/metering/weight.rs | 142 +---- .../frame/revive/src/metering/weight/tests.rs | 134 +++++ substrate/frame/revive/src/precompiles.rs | 2 +- .../revive/src/precompiles/builtin/modexp.rs | 2 +- substrate/frame/revive/src/storage.rs | 2 +- .../src/vm/evm/instructions/contract.rs | 21 +- .../revive/src/vm/evm/instructions/host.rs | 2 +- substrate/frame/revive/src/vm/mod.rs | 2 +- substrate/frame/revive/src/vm/pvm.rs | 2 +- .../frame/revive/src/vm/runtime_costs.rs | 4 +- 19 files changed, 747 insertions(+), 724 deletions(-) create mode 100644 substrate/frame/revive/src/metering/storage/tests.rs create mode 100644 substrate/frame/revive/src/metering/weight/tests.rs diff --git a/substrate/frame/revive/fixtures/contracts/Deposit.sol b/substrate/frame/revive/fixtures/contracts/Deposit.sol index 21c95470d513d..7443c86d23ca4 100644 --- a/substrate/frame/revive/fixtures/contracts/Deposit.sol +++ b/substrate/frame/revive/fixtures/contracts/Deposit.sol @@ -15,8 +15,15 @@ contract DepositPrecompile { } function clearAll() external { - clearStorageSlot(0); - clearStorageSlot(1); + uint slot; + assembly { + slot := a.slot + } + clearStorageSlot(slot); + assembly { + slot := b.slot + } + clearStorageSlot(slot); } function setAndClear() external { diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 4d153d59af92e..e154e77e974c9 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -179,7 +179,7 @@ impl EthRpcServer for EthRpcServerImpl { err })?; - log::debug!(target: LOG_TARGET, "send_raw_transaction with hash: {hash:?}"); + log::trace!(target: LOG_TARGET, "send_raw_transaction with hash: {hash:?}"); // Wait for the transaction to be included in a block if automine is enabled if let Some(mut receiver) = receiver { diff --git a/substrate/frame/revive/src/evm/fees.rs b/substrate/frame/revive/src/evm/fees.rs index cb568d9ececb8..4722d333d596d 100644 --- a/substrate/frame/revive/src/evm/fees.rs +++ b/substrate/frame/revive/src/evm/fees.rs @@ -378,6 +378,11 @@ mod seal { /// Determine the maximal integer `n` so that `multiplier.saturating_mul_int(n) <= product` /// +/// See the tests `compute_max_quotient_works` below for an example why simple division does not +/// give the correct result. This level of pedantry is required because otherwise we observed actual +/// cases where limits where calculated incorrectly and the transaction ran out of gas although it +/// used the correct gas estimate. +/// /// FixedU128 wraps a 128 bit unsigned integer `self.0` and it is interpreted to represent the real /// number self.0 / FixedU128::DIV, where FixedU128::DIV is 1_000_000_000_000_000_000. /// diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 34620c95bf30f..86dfe136d6922 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -19,11 +19,7 @@ use crate::{ address::{self, AddressMapper}, evm::{block_storage, transfer_with_dust}, limits, - metering::{ - storage, - weight::{ChargedAmount, Token}, - FrameMeter, ResourceMeter, State, TransactionMeter, - }, + metering::{ChargedAmount, Diff, FrameMeter, ResourceMeter, State, Token, TransactionMeter}, precompiles::{All as AllPrecompiles, Instance as PrecompileInstance, Precompiles}, primitives::{ExecConfig, ExecReturnValue, StorageDeposit}, runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, @@ -122,6 +118,7 @@ impl Key { } /// Level of reentrancy protection. +/// /// This needs to be specifed when a contract makes a message call. This way the calling contract /// can specify the level of re-entrancy protection while the callee (and it's recursive callees) is /// executing. @@ -550,7 +547,7 @@ pub trait PrecompileExt: sealing::Sealed { ) -> Result; /// Charges `diff` from the meter. - fn charge_storage(&mut self, diff: &storage::Diff) -> DispatchResult; + fn charge_storage(&mut self, diff: &Diff) -> DispatchResult; } /// Describes the different functions that can be exported by an [`Executable`]. @@ -2490,7 +2487,7 @@ where ) } - fn charge_storage(&mut self, diff: &storage::Diff) -> DispatchResult { + fn charge_storage(&mut self, diff: &Diff) -> DispatchResult { assert!(self.has_contract_info()); self.top_frame_mut().frame_meter.record_contract_storage_changes(diff) } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 3aa53eb7da31d..e1cd26bfe0da3 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -101,8 +101,8 @@ pub use crate::{ }, exec::{CallResources, DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin}, metering::{ - gas::SignedGas, weight::Token as WeightToken, EthTxInfo, FrameMeter, ResourceMeter, - TransactionLimits, TransactionMeter, + EthTxInfo, FrameMeter, ResourceMeter, Token as WeightToken, TransactionLimits, + TransactionMeter, }, pallet::{genesis, *}, storage::{AccountInfo, ContractInfo}, @@ -141,8 +141,6 @@ const LOG_TARGET: &str = "runtime::revive"; #[frame_support::pallet] pub mod pallet { - use crate::metering::EthTxInfo; - use super::*; use frame_support::{pallet_prelude::*, traits::FindAuthor}; use frame_system::pallet_prelude::*; diff --git a/substrate/frame/revive/src/metering/math.rs b/substrate/frame/revive/src/metering/math.rs index c9cad56205fd9..54f1df1922c83 100644 --- a/substrate/frame/revive/src/metering/math.rs +++ b/substrate/frame/revive/src/metering/math.rs @@ -17,10 +17,10 @@ use super::{ BalanceOf, CallResources, Config, DispatchError, Error, EthTxInfo, FixedPointNumber, FixedU128, - FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, State, StorageDeposit, - TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, + FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, SignedGas, State, + StorageDeposit, Token, TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero, }; -use crate::{metering::weight::Token, vm::evm::EVMGas, SignedGas}; +use crate::vm::evm::EVMGas; use core::marker::PhantomData; use revm::interpreter::gas::CALL_STIPEND; diff --git a/substrate/frame/revive/src/metering/mod.rs b/substrate/frame/revive/src/metering/mod.rs index 799d1a334cf20..1701bf3fdf1ef 100644 --- a/substrate/frame/revive/src/metering/mod.rs +++ b/substrate/frame/revive/src/metering/mod.rs @@ -15,25 +15,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod gas; -pub mod math; -pub mod storage; -pub mod weight; +mod gas; +mod math; +mod storage; +mod weight; #[cfg(test)] mod tests; use crate::{ evm::fees::InfoT, exec::CallResources, storage::ContractInfo, vm::evm::Halt, BalanceOf, Config, - Error, ExecConfig, ExecOrigin as Origin, SignedGas, StorageDeposit, LOG_TARGET, + Error, ExecConfig, ExecOrigin as Origin, StorageDeposit, LOG_TARGET, }; + +pub use gas::SignedGas; +pub use storage::Diff; +pub use weight::{ChargedAmount, Token}; + use frame_support::{DebugNoBound, DefaultNoBound}; use num_traits::Zero; use core::{fmt::Debug, marker::PhantomData, ops::ControlFlow}; use sp_runtime::{FixedPointNumber, Weight}; -use storage::{DepositOf, Diff, GenericMeter as GenericStorageMeter, Meter as RootStorageMeter}; -use weight::{ChargedAmount, Token, WeightMeter}; +use storage::{DepositOf, GenericMeter as GenericStorageMeter, Meter as RootStorageMeter}; +use weight::WeightMeter; use sp_runtime::{DispatchError, DispatchResult, FixedU128, SaturatedConversion}; @@ -63,30 +68,39 @@ mod private { impl Sealed for super::Nested {} } +/// The type of resource meter used at the root level for transactions as a whole. pub type TransactionMeter = ResourceMeter; +/// The type of resource meter used for an execution frame. pub type FrameMeter = ResourceMeter; /// Resource meter tracking weight and storage deposit consumption. -/// -/// This type maintains the core invariant that either: -/// - Both weight and deposit limits are None, or -/// - Both limits are Some(value) -/// -/// A resource meter tracks: -/// - Current frame's weight consumption via WeightMeter -/// - Current frame's storage deposit changes via GenericStorageMeter -/// - Total resources consumed before this frame started -/// - Transaction-wide resource limits and execution mode #[derive(DefaultNoBound)] pub struct ResourceMeter { + /// The weight meter. Tracks consumed weight and weight limits. weight: WeightMeter, + + /// The deposit meter. Tracks consumed storage deposit and storage deposit limits. deposit: GenericStorageMeter, - // this is always zero for Substrate executions + /// This is the maximum total consumable gas. + /// + /// It is the sum of a) the total consumed gas (i.e., including all previous frames) at the + /// time the frame started and b) the gas limit of the frame. We don't store the gas limit of + /// the frame separately, it can be derived from `max_total_gas` by subtracting the total gas + /// at the beginning of the frame. + /// + /// `max_total_gas` is only required for Ethereum execution, it is always zero for Substrate + /// executions. max_total_gas: SignedGas, + + /// The total consumed weight at the time the frame started. total_consumed_weight_before: Weight, + + /// The total consumed storage deposit at the time the frame started. total_consumed_deposit_before: DepositOf, + /// The limits defined for the transaction. This determines whether this transaction uses the + /// Ethereum or Substrate execution mode. transaction_limits: TransactionLimits, _phantom: PhantomData, @@ -99,17 +113,20 @@ pub struct ResourceMeter { /// - WeightAndDeposit: Explicit limits for both computational weight and storage deposit #[derive(DebugNoBound, Clone)] pub enum TransactionLimits { + /// Ethereum execution mode: the transaction only specifies a gas limit. EthereumGas { + /// The Ethereum gas limit eth_gas_limit: BalanceOf, - // if this is provided, we will additionally ensure that execution will not exhaust this - // weight limit + /// If this is provided, we will additionally ensure that execution will not exhaust this + /// weight limit. This is required for eth_transact extrinsic execution to ensure that the + /// max extrinsic weights is not overstepped. maybe_weight_limit: Option, + /// Some extra information about the transaction that is required to calculate gas usage. eth_tx_info: EthTxInfo, }, - WeightAndDeposit { - weight_limit: Weight, - deposit_limit: BalanceOf, - }, + /// Substrate execution mode: the transaction specifies a weight limit and a storage deposit + /// limit + WeightAndDeposit { weight_limit: Weight, deposit_limit: BalanceOf }, } impl Default for TransactionLimits { @@ -432,7 +449,10 @@ impl ResourceMeter { } /// Determine and set the new effective weight limit of the weight meter. - /// This function needs to be called whenever there is a change in the deposit meter. + /// + /// This function needs to be called whenever there is a change in the deposit meter. It is a + /// function of `ResourceMeter` instead of `WeightMeter` because its outcome also depends on the + /// consumed storage deposits. fn adjust_effective_weight_limit(&mut self) -> DispatchResult { if matches!(self.transaction_limits, TransactionLimits::WeightAndDeposit { .. }) { return Ok(()) diff --git a/substrate/frame/revive/src/metering/storage.rs b/substrate/frame/revive/src/metering/storage.rs index 24cf0a99092f0..98ca58e3514d3 100644 --- a/substrate/frame/revive/src/metering/storage.rs +++ b/substrate/frame/revive/src/metering/storage.rs @@ -17,6 +17,9 @@ //! This module contains functions to meter the storage deposit. +#[cfg(test)] +mod tests; + use super::{Nested, Root, State}; use crate::{ storage::ContractInfo, BalanceOf, Config, ExecConfig, ExecOrigin as Origin, HoldReason, Pallet, @@ -517,529 +520,3 @@ impl Ext for ReservingExt { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::{exec::AccountIdOf, test_utils::*, tests::Test}; - use frame_support::parameter_types; - use pretty_assertions::assert_eq; - - type TestMeter = RawMeter; - - parameter_types! { - static TestExtTestValue: TestExt = Default::default(); - } - - #[derive(Debug, PartialEq, Eq, Clone)] - struct Charge { - origin: AccountIdOf, - contract: AccountIdOf, - amount: DepositOf, - } - - #[derive(Default, Debug, PartialEq, Eq, Clone)] - pub struct TestExt { - charges: Vec, - } - - impl TestExt { - fn clear(&mut self) { - self.charges.clear(); - } - } - - impl Ext for TestExt { - fn charge( - origin: &AccountIdOf, - contract: &AccountIdOf, - amount: &DepositOf, - _exec_config: &ExecConfig, - ) -> Result<(), DispatchError> { - TestExtTestValue::mutate(|ext| { - ext.charges.push(Charge { - origin: origin.clone(), - contract: contract.clone(), - amount: amount.clone(), - }) - }); - Ok(()) - } - } - - fn clear_ext() { - TestExtTestValue::mutate(|ext| ext.clear()) - } - - struct ChargingTestCase { - origin: Origin, - deposit: DepositOf, - expected: TestExt, - } - - #[derive(Default)] - struct StorageInfo { - bytes: u32, - items: u32, - bytes_deposit: BalanceOf, - items_deposit: BalanceOf, - immutable_data_len: u32, - } - - fn new_info(info: StorageInfo) -> ContractInfo { - ContractInfo:: { - trie_id: Default::default(), - code_hash: Default::default(), - storage_bytes: info.bytes, - storage_items: info.items, - storage_byte_deposit: info.bytes_deposit, - storage_item_deposit: info.items_deposit, - storage_base_deposit: Default::default(), - immutable_data_len: info.immutable_data_len, - } - } - - #[test] - fn new_reserves_balance_works() { - clear_ext(); - - TestMeter::new(Some(1_000)); - - assert_eq!(TestExtTestValue::get(), TestExt { ..Default::default() }) - } - - /// Previously, passing a limit of 0 meant unlimited storage for a nested call. - /// - /// Now, a limit of 0 means the subcall will not be able to use any storage. - #[test] - fn nested_zero_limit_requested() { - clear_ext(); - - let meter = TestMeter::new(Some(1_000)); - assert_eq!(meter.available(), 1_000); - let nested0 = meter.nested(Some(BalanceOf::::zero())); - assert_eq!(nested0.available(), 0); - } - - #[test] - fn nested_some_limit_requested() { - clear_ext(); - - let meter = TestMeter::new(Some(1_000)); - assert_eq!(meter.available(), 1_000); - let nested0 = meter.nested(Some(500)); - assert_eq!(nested0.available(), 500); - } - - #[test] - fn nested_all_limit_requested() { - clear_ext(); - - let meter = TestMeter::new(Some(1_000)); - assert_eq!(meter.available(), 1_000); - let nested0 = meter.nested(Some(1_000)); - assert_eq!(nested0.available(), 1_000); - } - - #[test] - fn nested_over_limit_requested() { - clear_ext(); - - let meter = TestMeter::new(Some(1_000)); - assert_eq!(meter.available(), 1_000); - let nested0 = meter.nested(Some(2_000)); - assert_eq!(nested0.available(), 1_000); - } - - #[test] - fn empty_charge_works() { - clear_ext(); - - let mut meter = TestMeter::new(Some(1_000)); - assert_eq!(meter.available(), 1_000); - - // an empty charge does not create a `Charge` entry - let mut nested0 = meter.nested(Some(BalanceOf::::zero())); - nested0.charge(&Default::default()); - meter.absorb(nested0, &BOB, None); - assert_eq!( - meter - .execute_postponed_deposits( - &Origin::::from_account_id(ALICE), - &ExecConfig::new_substrate_tx(), - ) - .unwrap(), - Default::default() - ); - assert_eq!(TestExtTestValue::get(), TestExt { ..Default::default() }) - } - - #[test] - fn charging_works() { - let test_cases = vec![ - ChargingTestCase { - origin: Origin::::from_account_id(ALICE), - deposit: Deposit::Refund(28), - expected: TestExt { - charges: vec![ - Charge { origin: ALICE, contract: CHARLIE, amount: Deposit::Refund(30) }, - Charge { origin: ALICE, contract: BOB, amount: Deposit::Charge(2) }, - ], - }, - }, - ChargingTestCase { - origin: Origin::::Root, - deposit: Deposit::Charge(0), - expected: TestExt { charges: vec![] }, - }, - ]; - - for test_case in test_cases { - clear_ext(); - - let mut meter = TestMeter::new(Some(100)); - assert_eq!(meter.consumed(), Default::default()); - assert_eq!(meter.available(), 100); - - let mut nested0_info = new_info(StorageInfo { - bytes: 100, - items: 5, - bytes_deposit: 100, - items_deposit: 10, - immutable_data_len: 0, - }); - let mut nested0 = meter.nested(Some(BalanceOf::::zero())); - nested0.charge(&Diff { - bytes_added: 108, - bytes_removed: 5, - items_added: 1, - items_removed: 2, - }); - assert_eq!(nested0.consumed(), Deposit::Charge(103)); - assert_eq!(nested0.available(), 0); - nested0.charge(&Diff { bytes_removed: 99, ..Default::default() }); - assert_eq!(nested0.consumed(), Deposit::Charge(4)); - assert_eq!(nested0.available(), 0); - - let mut nested1_info = new_info(StorageInfo { - bytes: 100, - items: 10, - bytes_deposit: 100, - items_deposit: 20, - immutable_data_len: 0, - }); - let mut nested1 = nested0.nested(Some(BalanceOf::::zero())); - nested1.charge(&Diff { items_removed: 5, ..Default::default() }); - assert_eq!(nested1.consumed(), Default::default()); - assert_eq!(nested1.available(), 0); - nested1.finalize_own_contributions(Some(&mut nested1_info)); - assert_eq!(nested1.consumed(), Deposit::Refund(10)); - assert_eq!(nested1.available(), 10); - - nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info)); - assert_eq!(nested0.consumed(), Deposit::Refund(6)); - assert_eq!(nested0.available(), 6); - - let mut nested2_info = new_info(StorageInfo { - bytes: 100, - items: 7, - bytes_deposit: 100, - items_deposit: 20, - immutable_data_len: 0, - }); - let mut nested2 = nested0.nested(Some(BalanceOf::::zero())); - nested2.charge(&Diff { items_removed: 7, ..Default::default() }); - assert_eq!(nested2.consumed(), Default::default()); - assert_eq!(nested2.available(), 0); - nested2.finalize_own_contributions(Some(&mut nested2_info)); - assert_eq!(nested2.consumed(), Deposit::Refund(20)); - assert_eq!(nested2.available(), 20); - - nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info)); - assert_eq!(nested0.consumed(), Deposit::Refund(26)); - assert_eq!(nested0.available(), 26); - - nested0.finalize_own_contributions(Some(&mut nested0_info)); - assert_eq!(nested0.consumed(), Deposit::Refund(28)); - assert_eq!(nested0.available(), 28); - - meter.absorb(nested0, &BOB, Some(&mut nested0_info)); - assert_eq!(meter.consumed(), Deposit::Refund(28)); - assert_eq!(meter.available(), 128); - - assert_eq!( - meter - .execute_postponed_deposits(&test_case.origin, &ExecConfig::new_substrate_tx()) - .unwrap(), - test_case.deposit - ); - - assert_eq!(nested0_info.extra_deposit(), 112); - assert_eq!(nested1_info.extra_deposit(), 110); - assert_eq!(nested2_info.extra_deposit(), 100); - - assert_eq!(TestExtTestValue::get(), test_case.expected) - } - } - - #[test] - fn termination_works() { - let test_cases = vec![ - ChargingTestCase { - origin: Origin::::from_account_id(ALICE), - deposit: Deposit::Refund(108), - expected: TestExt { - charges: vec![Charge { - origin: ALICE, - contract: BOB, - amount: Deposit::Charge(12), - }], - }, - }, - ChargingTestCase { - origin: Origin::::Root, - deposit: Deposit::Charge(0), - expected: TestExt { charges: vec![] }, - }, - ]; - - for test_case in test_cases { - clear_ext(); - - let mut meter = TestMeter::new(Some(1_000)); - assert_eq!(meter.available(), 1_000); - - let mut nested0 = meter.nested(Some(BalanceOf::::max_value())); - assert_eq!(nested0.available(), 1_000); - - nested0.charge(&Diff { - bytes_added: 5, - bytes_removed: 1, - items_added: 3, - items_removed: 1, - }); - assert_eq!(nested0.consumed(), Deposit::Charge(8)); - - nested0.charge(&Diff { items_added: 2, ..Default::default() }); - assert_eq!(nested0.consumed(), Deposit::Charge(12)); - - let mut nested1_info = new_info(StorageInfo { - bytes: 100, - items: 10, - bytes_deposit: 100, - items_deposit: 20, - immutable_data_len: 0, - }); - let mut nested1 = nested0.nested(Some(BalanceOf::::max_value())); - assert_eq!(nested1.consumed(), Default::default()); - let total_deposit = nested1_info.total_deposit(); - nested1.charge(&Diff { items_removed: 5, ..Default::default() }); - assert_eq!(nested1.consumed(), Default::default()); - nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); - assert_eq!(nested1.consumed(), Deposit::Charge(20)); - nested1.finalize_own_contributions(Some(&mut nested1_info)); - assert_eq!(nested1.consumed(), Deposit::Charge(10)); - nested0.absorb(nested1, &CHARLIE, None); - assert_eq!(nested0.consumed(), Deposit::Charge(22)); - - meter.absorb(nested0, &BOB, None); - assert_eq!(meter.consumed(), Deposit::Charge(22)); - - meter.terminate(CHARLIE, total_deposit); - assert_eq!(meter.consumed(), Deposit::Refund(98)); - assert_eq!( - meter - .execute_postponed_deposits(&test_case.origin, &ExecConfig::new_substrate_tx()) - .unwrap(), - test_case.deposit - ); - assert_eq!(TestExtTestValue::get(), test_case.expected) - } - } - - #[test] - fn max_deposits_work_with_charges() { - clear_ext(); - let meter = TestMeter::new(None); - let mut nested = meter.nested(None); - - assert_eq!(nested.consumed(), Default::default()); - assert_eq!(nested.max_charged(), Default::default()); - - nested.record_charge(&Deposit::Charge(100)); - assert_eq!(nested.consumed(), Deposit::Charge(100)); - assert_eq!(nested.max_charged(), Deposit::Charge(100)); - - nested.record_charge(&Deposit::Refund(50)); - assert_eq!(nested.consumed(), Deposit::Charge(50)); - assert_eq!(nested.max_charged(), Deposit::Charge(100)); - - nested.record_charge(&Deposit::Charge(80)); - assert_eq!(nested.consumed(), Deposit::Charge(130)); - assert_eq!(nested.max_charged(), Deposit::Charge(130)); - - nested.record_charge(&Deposit::Refund(200)); - assert_eq!(nested.consumed(), Deposit::Refund(70)); - assert_eq!(nested.max_charged(), Deposit::Charge(130)); - - let meter = TestMeter::new(None); - let mut nested = meter.nested(None); - nested.record_charge(&Deposit::Refund(100)); - assert_eq!(nested.consumed(), Deposit::Refund(100)); - assert_eq!(nested.max_charged(), Default::default()); - - nested.record_charge(&Deposit::Charge(100)); - assert_eq!(nested.consumed(), Default::default()); - assert_eq!(nested.max_charged(), Default::default()); - - nested.record_charge(&Deposit::Charge(50)); - assert_eq!(nested.consumed(), Deposit::Charge(50)); - assert_eq!(nested.max_charged(), Deposit::Charge(50)); - - nested.record_charge(&Deposit::Refund(20)); - assert_eq!(nested.consumed(), Deposit::Charge(30)); - assert_eq!(nested.max_charged(), Deposit::Charge(50)); - } - - #[test] - fn max_deposits_work_with_diffs() { - clear_ext(); - let meter = TestMeter::new(None); - let mut nested = meter.nested(None); - - nested.charge(&Diff { bytes_added: 2, ..Default::default() }); - - assert_eq!(nested.consumed(), Deposit::Charge(2)); - assert_eq!(nested.max_charged(), Deposit::Charge(2)); - - nested.charge(&Diff { bytes_removed: 1, ..Default::default() }); - assert_eq!(nested.consumed(), Deposit::Charge(1)); - assert_eq!(nested.max_charged(), Deposit::Charge(2)); - - nested.charge(&Diff { items_added: 10, ..Default::default() }); - assert_eq!(nested.consumed(), Deposit::Charge(21)); - assert_eq!(nested.max_charged(), Deposit::Charge(21)); - - nested.charge(&Diff { items_removed: 8, ..Default::default() }); - assert_eq!(nested.consumed(), Deposit::Charge(5)); - assert_eq!(nested.max_charged(), Deposit::Charge(21)); - - nested.charge(&Diff { items_added: 10, bytes_added: 10, ..Default::default() }); - assert_eq!(nested.consumed(), Deposit::Charge(35)); - assert_eq!(nested.max_charged(), Deposit::Charge(35)); - - nested.charge(&Diff { items_removed: 5, bytes_added: 10, ..Default::default() }); - assert_eq!(nested.consumed(), Deposit::Charge(35)); - assert_eq!(nested.max_charged(), Deposit::Charge(35)); - - let meter = TestMeter::new(None); - let mut nested = meter.nested(None); - nested.charge(&Diff { bytes_removed: 10, items_added: 2, ..Default::default() }); - assert_eq!(nested.consumed(), Deposit::Charge(4)); - assert_eq!(nested.max_charged(), Deposit::Charge(4)); - - nested.charge(&Diff { bytes_added: 5, items_removed: 3, ..Default::default() }); - assert_eq!(nested.consumed(), Default::default()); - assert_eq!(nested.max_charged(), Deposit::Charge(4)); - - nested.charge(&Diff { bytes_added: 7, ..Default::default() }); - assert_eq!(nested.consumed(), Deposit::Charge(2)); - assert_eq!(nested.max_charged(), Deposit::Charge(4)); - - nested.record_charge(&Deposit::Refund(10)); - assert_eq!(nested.consumed(), Deposit::Refund(8)); - assert_eq!(nested.max_charged(), Deposit::Charge(4)); - - nested.charge(&Diff { bytes_removed: 4, items_added: 2, ..Default::default() }); - assert_eq!(nested.consumed(), Deposit::Refund(8)); - assert_eq!(nested.max_charged(), Deposit::Charge(4)); - - nested.charge(&Diff { bytes_added: 20, ..Default::default() }); - assert_eq!(nested.consumed(), Deposit::Charge(10)); - assert_eq!(nested.max_charged(), Deposit::Charge(10)); - - nested.record_charge(&Deposit::Refund(20)); - assert_eq!(nested.consumed(), Deposit::Refund(10)); - assert_eq!(nested.max_charged(), Deposit::Charge(10)); - } - - #[test] - fn max_deposits_work_nested() { - clear_ext(); - let mut meter = TestMeter::new(None); - let mut nested1 = meter.nested(None); - nested1.record_charge(&Deposit::Charge(10)); - - let mut nested2a = nested1.nested(None); - nested2a.record_charge(&Deposit::Charge(20)); - nested2a.record_charge(&Deposit::Refund(10)); - assert_eq!(nested2a.consumed(), Deposit::Charge(10)); - assert_eq!(nested2a.max_charged(), Deposit::Charge(20)); - - nested2a.charge(&Diff { bytes_removed: 20, items_removed: 10, ..Default::default() }); - assert_eq!(nested2a.consumed(), Deposit::Charge(10)); - assert_eq!(nested2a.max_charged(), Deposit::Charge(20)); - - nested2a.charge(&Diff { bytes_added: 15, items_added: 16, ..Default::default() }); - assert_eq!(nested2a.consumed(), Deposit::Charge(22)); - assert_eq!(nested2a.max_charged(), Deposit::Charge(22)); - - let mut nested2a_info = new_info(StorageInfo { - bytes: 100, - items: 100, - bytes_deposit: 100, - items_deposit: 100, - immutable_data_len: 0, - }); - nested1.absorb(nested2a, &BOB, Some(&mut nested2a_info)); - assert_eq!(nested1.consumed(), Deposit::Charge(27)); - assert_eq!(nested1.max_charged(), Deposit::Charge(32)); - - nested1.charge(&Diff { bytes_added: 10, ..Default::default() }); - assert_eq!(nested1.consumed(), Deposit::Charge(37)); - assert_eq!(nested1.max_charged(), Deposit::Charge(37)); - - nested1.record_charge(&Deposit::Refund(10)); - assert_eq!(nested1.consumed(), Deposit::Charge(27)); - assert_eq!(nested1.max_charged(), Deposit::Charge(37)); - - let mut nested2b = nested1.nested(None); - nested2b.record_charge(&Deposit::Refund(10)); - assert_eq!(nested2b.consumed(), Deposit::Refund(10)); - assert_eq!(nested2b.max_charged(), Default::default()); - - nested2b.charge(&Diff { bytes_added: 10, items_added: 10, ..Default::default() }); - assert_eq!(nested2b.consumed(), Deposit::Charge(20)); - assert_eq!(nested2b.max_charged(), Deposit::Charge(20)); - - nested2b.charge(&Diff { bytes_removed: 20, items_removed: 20, ..Default::default() }); - assert_eq!(nested2b.consumed(), Deposit::Refund(10)); - assert_eq!(nested2b.max_charged(), Deposit::Charge(20)); - - let mut nested2b_info = new_info(StorageInfo { - bytes: 100, - items: 100, - bytes_deposit: 100, - items_deposit: 100, - immutable_data_len: 0, - }); - nested1.absorb(nested2b, &BOB, Some(&mut nested2b_info)); - assert_eq!(nested1.consumed(), Deposit::Refund(3)); - assert_eq!(nested1.max_charged(), Deposit::Charge(47)); - - meter.absorb(nested1, &ALICE, None); - assert_eq!(meter.consumed(), Deposit::Refund(3)); - assert_eq!(meter.max_charged(), Deposit::Charge(47)); - } - - #[test] - fn max_deposits_work_for_reverts() { - clear_ext(); - let mut meter = TestMeter::new(None); - let mut nested1 = meter.nested(None); - nested1.record_charge(&Deposit::Charge(10)); - - meter.absorb_only_max_charged(nested1); - assert_eq!(meter.max_charged(), Deposit::Charge(10)); - } -} diff --git a/substrate/frame/revive/src/metering/storage/tests.rs b/substrate/frame/revive/src/metering/storage/tests.rs new file mode 100644 index 0000000000000..d1657e47757ee --- /dev/null +++ b/substrate/frame/revive/src/metering/storage/tests.rs @@ -0,0 +1,518 @@ +use super::*; +use crate::{exec::AccountIdOf, test_utils::*, tests::Test}; +use frame_support::parameter_types; +use pretty_assertions::assert_eq; + +type TestMeter = RawMeter; + +parameter_types! { + static TestExtTestValue: TestExt = Default::default(); +} + +#[derive(Debug, PartialEq, Eq, Clone)] +struct Charge { + origin: AccountIdOf, + contract: AccountIdOf, + amount: DepositOf, +} + +#[derive(Default, Debug, PartialEq, Eq, Clone)] +pub struct TestExt { + charges: Vec, +} + +impl TestExt { + fn clear(&mut self) { + self.charges.clear(); + } +} + +impl Ext for TestExt { + fn charge( + origin: &AccountIdOf, + contract: &AccountIdOf, + amount: &DepositOf, + _exec_config: &ExecConfig, + ) -> Result<(), DispatchError> { + TestExtTestValue::mutate(|ext| { + ext.charges.push(Charge { + origin: origin.clone(), + contract: contract.clone(), + amount: amount.clone(), + }) + }); + Ok(()) + } +} + +fn clear_ext() { + TestExtTestValue::mutate(|ext| ext.clear()) +} + +struct ChargingTestCase { + origin: Origin, + deposit: DepositOf, + expected: TestExt, +} + +#[derive(Default)] +struct StorageInfo { + bytes: u32, + items: u32, + bytes_deposit: BalanceOf, + items_deposit: BalanceOf, + immutable_data_len: u32, +} + +fn new_info(info: StorageInfo) -> ContractInfo { + ContractInfo:: { + trie_id: Default::default(), + code_hash: Default::default(), + storage_bytes: info.bytes, + storage_items: info.items, + storage_byte_deposit: info.bytes_deposit, + storage_item_deposit: info.items_deposit, + storage_base_deposit: Default::default(), + immutable_data_len: info.immutable_data_len, + } +} + +#[test] +fn new_reserves_balance_works() { + clear_ext(); + + TestMeter::new(Some(1_000)); + + assert_eq!(TestExtTestValue::get(), TestExt { ..Default::default() }) +} + +/// Previously, passing a limit of 0 meant unlimited storage for a nested call. +/// +/// Now, a limit of 0 means the subcall will not be able to use any storage. +#[test] +fn nested_zero_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(Some(1_000)); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(Some(BalanceOf::::zero())); + assert_eq!(nested0.available(), 0); +} + +#[test] +fn nested_some_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(Some(1_000)); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(Some(500)); + assert_eq!(nested0.available(), 500); +} + +#[test] +fn nested_all_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(Some(1_000)); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(Some(1_000)); + assert_eq!(nested0.available(), 1_000); +} + +#[test] +fn nested_over_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(Some(1_000)); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(Some(2_000)); + assert_eq!(nested0.available(), 1_000); +} + +#[test] +fn empty_charge_works() { + clear_ext(); + + let mut meter = TestMeter::new(Some(1_000)); + assert_eq!(meter.available(), 1_000); + + // an empty charge does not create a `Charge` entry + let mut nested0 = meter.nested(Some(BalanceOf::::zero())); + nested0.charge(&Default::default()); + meter.absorb(nested0, &BOB, None); + assert_eq!( + meter + .execute_postponed_deposits( + &Origin::::from_account_id(ALICE), + &ExecConfig::new_substrate_tx(), + ) + .unwrap(), + Default::default() + ); + assert_eq!(TestExtTestValue::get(), TestExt { ..Default::default() }) +} + +#[test] +fn charging_works() { + let test_cases = vec![ + ChargingTestCase { + origin: Origin::::from_account_id(ALICE), + deposit: Deposit::Refund(28), + expected: TestExt { + charges: vec![ + Charge { origin: ALICE, contract: CHARLIE, amount: Deposit::Refund(30) }, + Charge { origin: ALICE, contract: BOB, amount: Deposit::Charge(2) }, + ], + }, + }, + ChargingTestCase { + origin: Origin::::Root, + deposit: Deposit::Charge(0), + expected: TestExt { charges: vec![] }, + }, + ]; + + for test_case in test_cases { + clear_ext(); + + let mut meter = TestMeter::new(Some(100)); + assert_eq!(meter.consumed(), Default::default()); + assert_eq!(meter.available(), 100); + + let mut nested0_info = new_info(StorageInfo { + bytes: 100, + items: 5, + bytes_deposit: 100, + items_deposit: 10, + immutable_data_len: 0, + }); + let mut nested0 = meter.nested(Some(BalanceOf::::zero())); + nested0.charge(&Diff { + bytes_added: 108, + bytes_removed: 5, + items_added: 1, + items_removed: 2, + }); + assert_eq!(nested0.consumed(), Deposit::Charge(103)); + assert_eq!(nested0.available(), 0); + nested0.charge(&Diff { bytes_removed: 99, ..Default::default() }); + assert_eq!(nested0.consumed(), Deposit::Charge(4)); + assert_eq!(nested0.available(), 0); + + let mut nested1_info = new_info(StorageInfo { + bytes: 100, + items: 10, + bytes_deposit: 100, + items_deposit: 20, + immutable_data_len: 0, + }); + let mut nested1 = nested0.nested(Some(BalanceOf::::zero())); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + assert_eq!(nested1.consumed(), Default::default()); + assert_eq!(nested1.available(), 0); + nested1.finalize_own_contributions(Some(&mut nested1_info)); + assert_eq!(nested1.consumed(), Deposit::Refund(10)); + assert_eq!(nested1.available(), 10); + + nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info)); + assert_eq!(nested0.consumed(), Deposit::Refund(6)); + assert_eq!(nested0.available(), 6); + + let mut nested2_info = new_info(StorageInfo { + bytes: 100, + items: 7, + bytes_deposit: 100, + items_deposit: 20, + immutable_data_len: 0, + }); + let mut nested2 = nested0.nested(Some(BalanceOf::::zero())); + nested2.charge(&Diff { items_removed: 7, ..Default::default() }); + assert_eq!(nested2.consumed(), Default::default()); + assert_eq!(nested2.available(), 0); + nested2.finalize_own_contributions(Some(&mut nested2_info)); + assert_eq!(nested2.consumed(), Deposit::Refund(20)); + assert_eq!(nested2.available(), 20); + + nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info)); + assert_eq!(nested0.consumed(), Deposit::Refund(26)); + assert_eq!(nested0.available(), 26); + + nested0.finalize_own_contributions(Some(&mut nested0_info)); + assert_eq!(nested0.consumed(), Deposit::Refund(28)); + assert_eq!(nested0.available(), 28); + + meter.absorb(nested0, &BOB, Some(&mut nested0_info)); + assert_eq!(meter.consumed(), Deposit::Refund(28)); + assert_eq!(meter.available(), 128); + + assert_eq!( + meter + .execute_postponed_deposits(&test_case.origin, &ExecConfig::new_substrate_tx()) + .unwrap(), + test_case.deposit + ); + + assert_eq!(nested0_info.extra_deposit(), 112); + assert_eq!(nested1_info.extra_deposit(), 110); + assert_eq!(nested2_info.extra_deposit(), 100); + + assert_eq!(TestExtTestValue::get(), test_case.expected) + } +} + +#[test] +fn termination_works() { + let test_cases = vec![ + ChargingTestCase { + origin: Origin::::from_account_id(ALICE), + deposit: Deposit::Refund(108), + expected: TestExt { + charges: vec![Charge { origin: ALICE, contract: BOB, amount: Deposit::Charge(12) }], + }, + }, + ChargingTestCase { + origin: Origin::::Root, + deposit: Deposit::Charge(0), + expected: TestExt { charges: vec![] }, + }, + ]; + + for test_case in test_cases { + clear_ext(); + + let mut meter = TestMeter::new(Some(1_000)); + assert_eq!(meter.available(), 1_000); + + let mut nested0 = meter.nested(Some(BalanceOf::::max_value())); + assert_eq!(nested0.available(), 1_000); + + nested0.charge(&Diff { + bytes_added: 5, + bytes_removed: 1, + items_added: 3, + items_removed: 1, + }); + assert_eq!(nested0.consumed(), Deposit::Charge(8)); + + nested0.charge(&Diff { items_added: 2, ..Default::default() }); + assert_eq!(nested0.consumed(), Deposit::Charge(12)); + + let mut nested1_info = new_info(StorageInfo { + bytes: 100, + items: 10, + bytes_deposit: 100, + items_deposit: 20, + immutable_data_len: 0, + }); + let mut nested1 = nested0.nested(Some(BalanceOf::::max_value())); + assert_eq!(nested1.consumed(), Default::default()); + let total_deposit = nested1_info.total_deposit(); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + assert_eq!(nested1.consumed(), Default::default()); + nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); + assert_eq!(nested1.consumed(), Deposit::Charge(20)); + nested1.finalize_own_contributions(Some(&mut nested1_info)); + assert_eq!(nested1.consumed(), Deposit::Charge(10)); + nested0.absorb(nested1, &CHARLIE, None); + assert_eq!(nested0.consumed(), Deposit::Charge(22)); + + meter.absorb(nested0, &BOB, None); + assert_eq!(meter.consumed(), Deposit::Charge(22)); + + meter.terminate(CHARLIE, total_deposit); + assert_eq!(meter.consumed(), Deposit::Refund(98)); + assert_eq!( + meter + .execute_postponed_deposits(&test_case.origin, &ExecConfig::new_substrate_tx()) + .unwrap(), + test_case.deposit + ); + assert_eq!(TestExtTestValue::get(), test_case.expected) + } +} + +#[test] +fn max_deposits_work_with_charges() { + clear_ext(); + let meter = TestMeter::new(None); + let mut nested = meter.nested(None); + + assert_eq!(nested.consumed(), Default::default()); + assert_eq!(nested.max_charged(), Default::default()); + + nested.record_charge(&Deposit::Charge(100)); + assert_eq!(nested.consumed(), Deposit::Charge(100)); + assert_eq!(nested.max_charged(), Deposit::Charge(100)); + + nested.record_charge(&Deposit::Refund(50)); + assert_eq!(nested.consumed(), Deposit::Charge(50)); + assert_eq!(nested.max_charged(), Deposit::Charge(100)); + + nested.record_charge(&Deposit::Charge(80)); + assert_eq!(nested.consumed(), Deposit::Charge(130)); + assert_eq!(nested.max_charged(), Deposit::Charge(130)); + + nested.record_charge(&Deposit::Refund(200)); + assert_eq!(nested.consumed(), Deposit::Refund(70)); + assert_eq!(nested.max_charged(), Deposit::Charge(130)); + + let meter = TestMeter::new(None); + let mut nested = meter.nested(None); + nested.record_charge(&Deposit::Refund(100)); + assert_eq!(nested.consumed(), Deposit::Refund(100)); + assert_eq!(nested.max_charged(), Default::default()); + + nested.record_charge(&Deposit::Charge(100)); + assert_eq!(nested.consumed(), Default::default()); + assert_eq!(nested.max_charged(), Default::default()); + + nested.record_charge(&Deposit::Charge(50)); + assert_eq!(nested.consumed(), Deposit::Charge(50)); + assert_eq!(nested.max_charged(), Deposit::Charge(50)); + + nested.record_charge(&Deposit::Refund(20)); + assert_eq!(nested.consumed(), Deposit::Charge(30)); + assert_eq!(nested.max_charged(), Deposit::Charge(50)); +} + +#[test] +fn max_deposits_work_with_diffs() { + clear_ext(); + let meter = TestMeter::new(None); + let mut nested = meter.nested(None); + + nested.charge(&Diff { bytes_added: 2, ..Default::default() }); + + assert_eq!(nested.consumed(), Deposit::Charge(2)); + assert_eq!(nested.max_charged(), Deposit::Charge(2)); + + nested.charge(&Diff { bytes_removed: 1, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(1)); + assert_eq!(nested.max_charged(), Deposit::Charge(2)); + + nested.charge(&Diff { items_added: 10, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(21)); + assert_eq!(nested.max_charged(), Deposit::Charge(21)); + + nested.charge(&Diff { items_removed: 8, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(5)); + assert_eq!(nested.max_charged(), Deposit::Charge(21)); + + nested.charge(&Diff { items_added: 10, bytes_added: 10, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(35)); + assert_eq!(nested.max_charged(), Deposit::Charge(35)); + + nested.charge(&Diff { items_removed: 5, bytes_added: 10, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(35)); + assert_eq!(nested.max_charged(), Deposit::Charge(35)); + + let meter = TestMeter::new(None); + let mut nested = meter.nested(None); + nested.charge(&Diff { bytes_removed: 10, items_added: 2, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(4)); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.charge(&Diff { bytes_added: 5, items_removed: 3, ..Default::default() }); + assert_eq!(nested.consumed(), Default::default()); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.charge(&Diff { bytes_added: 7, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(2)); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.record_charge(&Deposit::Refund(10)); + assert_eq!(nested.consumed(), Deposit::Refund(8)); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.charge(&Diff { bytes_removed: 4, items_added: 2, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Refund(8)); + assert_eq!(nested.max_charged(), Deposit::Charge(4)); + + nested.charge(&Diff { bytes_added: 20, ..Default::default() }); + assert_eq!(nested.consumed(), Deposit::Charge(10)); + assert_eq!(nested.max_charged(), Deposit::Charge(10)); + + nested.record_charge(&Deposit::Refund(20)); + assert_eq!(nested.consumed(), Deposit::Refund(10)); + assert_eq!(nested.max_charged(), Deposit::Charge(10)); +} + +#[test] +fn max_deposits_work_nested() { + clear_ext(); + let mut meter = TestMeter::new(None); + let mut nested1 = meter.nested(None); + nested1.record_charge(&Deposit::Charge(10)); + + let mut nested2a = nested1.nested(None); + nested2a.record_charge(&Deposit::Charge(20)); + nested2a.record_charge(&Deposit::Refund(10)); + assert_eq!(nested2a.consumed(), Deposit::Charge(10)); + assert_eq!(nested2a.max_charged(), Deposit::Charge(20)); + + nested2a.charge(&Diff { bytes_removed: 20, items_removed: 10, ..Default::default() }); + assert_eq!(nested2a.consumed(), Deposit::Charge(10)); + assert_eq!(nested2a.max_charged(), Deposit::Charge(20)); + + nested2a.charge(&Diff { bytes_added: 15, items_added: 16, ..Default::default() }); + assert_eq!(nested2a.consumed(), Deposit::Charge(22)); + assert_eq!(nested2a.max_charged(), Deposit::Charge(22)); + + let mut nested2a_info = new_info(StorageInfo { + bytes: 100, + items: 100, + bytes_deposit: 100, + items_deposit: 100, + immutable_data_len: 0, + }); + nested1.absorb(nested2a, &BOB, Some(&mut nested2a_info)); + assert_eq!(nested1.consumed(), Deposit::Charge(27)); + assert_eq!(nested1.max_charged(), Deposit::Charge(32)); + + nested1.charge(&Diff { bytes_added: 10, ..Default::default() }); + assert_eq!(nested1.consumed(), Deposit::Charge(37)); + assert_eq!(nested1.max_charged(), Deposit::Charge(37)); + + nested1.record_charge(&Deposit::Refund(10)); + assert_eq!(nested1.consumed(), Deposit::Charge(27)); + assert_eq!(nested1.max_charged(), Deposit::Charge(37)); + + let mut nested2b = nested1.nested(None); + nested2b.record_charge(&Deposit::Refund(10)); + assert_eq!(nested2b.consumed(), Deposit::Refund(10)); + assert_eq!(nested2b.max_charged(), Default::default()); + + nested2b.charge(&Diff { bytes_added: 10, items_added: 10, ..Default::default() }); + assert_eq!(nested2b.consumed(), Deposit::Charge(20)); + assert_eq!(nested2b.max_charged(), Deposit::Charge(20)); + + nested2b.charge(&Diff { bytes_removed: 20, items_removed: 20, ..Default::default() }); + assert_eq!(nested2b.consumed(), Deposit::Refund(10)); + assert_eq!(nested2b.max_charged(), Deposit::Charge(20)); + + let mut nested2b_info = new_info(StorageInfo { + bytes: 100, + items: 100, + bytes_deposit: 100, + items_deposit: 100, + immutable_data_len: 0, + }); + nested1.absorb(nested2b, &BOB, Some(&mut nested2b_info)); + assert_eq!(nested1.consumed(), Deposit::Refund(3)); + assert_eq!(nested1.max_charged(), Deposit::Charge(47)); + + meter.absorb(nested1, &ALICE, None); + assert_eq!(meter.consumed(), Deposit::Refund(3)); + assert_eq!(meter.max_charged(), Deposit::Charge(47)); +} + +#[test] +fn max_deposits_work_for_reverts() { + clear_ext(); + let mut meter = TestMeter::new(None); + let mut nested1 = meter.nested(None); + nested1.record_charge(&Deposit::Charge(10)); + + meter.absorb_only_max_charged(nested1); + assert_eq!(meter.max_charged(), Deposit::Charge(10)); +} diff --git a/substrate/frame/revive/src/metering/weight.rs b/substrate/frame/revive/src/metering/weight.rs index a931c7480bfa9..f7289241ef40a 100644 --- a/substrate/frame/revive/src/metering/weight.rs +++ b/substrate/frame/revive/src/metering/weight.rs @@ -14,6 +14,10 @@ // 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. + +#[cfg(test)] +mod tests; + use crate::{vm::evm::Halt, weights::WeightInfo, Config, Error}; use core::{marker::PhantomData, ops::ControlFlow}; use frame_support::{weights::Weight, DefaultNoBound}; @@ -287,141 +291,3 @@ impl WeightMeter { Self::new(Some(self.weight_left().min(amount)), None) } } - -#[cfg(test)] -mod tests { - use super::{Token, Weight, WeightMeter}; - use crate::tests::Test; - - /// A simple utility macro that helps to match against a - /// list of tokens. - macro_rules! match_tokens { - ($tokens_iter:ident,) => { - }; - ($tokens_iter:ident, $x:expr, $($rest:tt)*) => { - { - let next = ($tokens_iter).next().unwrap(); - let pattern = $x; - - // Note that we don't specify the type name directly in this macro, - // we only have some expression $x of some type. At the same time, we - // have an iterator of Box and to downcast we need to specify - // the type which we want downcast to. - // - // So what we do is we assign `_pattern_typed_next_ref` to a variable which has - // the required type. - // - // Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes - // rustc infer the type `T` (in `downcast_ref`) to be the same as in $x. - - let mut _pattern_typed_next_ref = &pattern; - _pattern_typed_next_ref = match next.token.downcast_ref() { - Some(p) => { - assert_eq!(p, &pattern); - p - } - None => { - panic!("expected type {} got {}", stringify!($x), next.description); - } - }; - } - - match_tokens!($tokens_iter, $($rest)*); - }; - } - - /// A trivial token that charges the specified number of weight units. - #[derive(Copy, Clone, PartialEq, Eq, Debug)] - struct SimpleToken(u64); - impl Token for SimpleToken { - fn weight(&self) -> Weight { - Weight::from_parts(self.0, 0) - } - } - - #[test] - fn it_works() { - let weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0)), None); - assert_eq!(weight_meter.weight_left(), Weight::from_parts(50000, 0)); - } - - #[test] - fn tracing() { - let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0)), None); - assert!(!weight_meter.charge(SimpleToken(1)).is_err()); - - let mut tokens = weight_meter.tokens().iter(); - match_tokens!(tokens, SimpleToken(1),); - } - - // This test makes sure that nothing can be executed if there is no weight. - #[test] - fn refuse_to_execute_anything_if_zero() { - let mut weight_meter = WeightMeter::::new(Some(Weight::zero()), None); - assert!(weight_meter.charge(SimpleToken(1)).is_err()); - } - - /// Previously, passing a `Weight` of 0 to `nested` would consume all of the meter's current - /// weight. - /// - /// Now, a `Weight` of 0 means no weight for the nested call. - #[test] - fn nested_zero_weight_requested() { - let test_weight = 50000.into(); - let mut weight_meter = WeightMeter::::new(Some(test_weight), None); - let weight_for_nested_call = weight_meter.nested(0.into()); - - assert_eq!(weight_meter.weight_left(), 50000.into()); - assert_eq!(weight_for_nested_call.weight_left(), 0.into()) - } - - #[test] - fn nested_some_weight_requested() { - let test_weight = 50000.into(); - let mut weight_meter = WeightMeter::::new(Some(test_weight), None); - let weight_for_nested_call = weight_meter.nested(10000.into()); - - assert_eq!(weight_meter.weight_consumed(), 0.into()); - assert_eq!(weight_for_nested_call.weight_left(), 10000.into()) - } - - #[test] - fn nested_all_weight_requested() { - let test_weight = Weight::from_parts(50000, 50000); - let mut weight_meter = WeightMeter::::new(Some(test_weight), None); - let weight_for_nested_call = weight_meter.nested(test_weight); - - assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); - assert_eq!(weight_for_nested_call.weight_left(), 50_000.into()) - } - - #[test] - fn nested_excess_weight_requested() { - let test_weight = Weight::from_parts(50000, 50000); - let mut weight_meter = WeightMeter::::new(Some(test_weight), None); - let weight_for_nested_call = weight_meter.nested(test_weight + 10000.into()); - - assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); - assert_eq!(weight_for_nested_call.weight_left(), 50_000.into()) - } - - // Make sure that the weight meter does not charge in case of overcharge - #[test] - fn overcharge_does_not_charge() { - let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(200, 0)), None); - - // The first charge is should lead to OOG. - assert!(weight_meter.charge(SimpleToken(300)).is_err()); - - // The weight meter should still contain the full 200. - assert!(weight_meter.charge(SimpleToken(200)).is_ok()); - } - - // Charging the exact amount that the user paid for should be - // possible. - #[test] - fn charge_exact_amount() { - let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(25, 0)), None); - assert!(!weight_meter.charge(SimpleToken(25)).is_err()); - } -} diff --git a/substrate/frame/revive/src/metering/weight/tests.rs b/substrate/frame/revive/src/metering/weight/tests.rs new file mode 100644 index 0000000000000..540b7d42d2e05 --- /dev/null +++ b/substrate/frame/revive/src/metering/weight/tests.rs @@ -0,0 +1,134 @@ +use super::{Token, Weight, WeightMeter}; +use crate::tests::Test; + +/// A simple utility macro that helps to match against a +/// list of tokens. +macro_rules! match_tokens { + ($tokens_iter:ident,) => { + }; + ($tokens_iter:ident, $x:expr, $($rest:tt)*) => { + { + let next = ($tokens_iter).next().unwrap(); + let pattern = $x; + + // Note that we don't specify the type name directly in this macro, + // we only have some expression $x of some type. At the same time, we + // have an iterator of Box and to downcast we need to specify + // the type which we want downcast to. + // + // So what we do is we assign `_pattern_typed_next_ref` to a variable which has + // the required type. + // + // Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes + // rustc infer the type `T` (in `downcast_ref`) to be the same as in $x. + + let mut _pattern_typed_next_ref = &pattern; + _pattern_typed_next_ref = match next.token.downcast_ref() { + Some(p) => { + assert_eq!(p, &pattern); + p + } + None => { + panic!("expected type {} got {}", stringify!($x), next.description); + } + }; + } + + match_tokens!($tokens_iter, $($rest)*); + }; + } + +/// A trivial token that charges the specified number of weight units. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct SimpleToken(u64); +impl Token for SimpleToken { + fn weight(&self) -> Weight { + Weight::from_parts(self.0, 0) + } +} + +#[test] +fn it_works() { + let weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0)), None); + assert_eq!(weight_meter.weight_left(), Weight::from_parts(50000, 0)); +} + +#[test] +fn tracing() { + let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(50000, 0)), None); + assert!(!weight_meter.charge(SimpleToken(1)).is_err()); + + let mut tokens = weight_meter.tokens().iter(); + match_tokens!(tokens, SimpleToken(1),); +} + +// This test makes sure that nothing can be executed if there is no weight. +#[test] +fn refuse_to_execute_anything_if_zero() { + let mut weight_meter = WeightMeter::::new(Some(Weight::zero()), None); + assert!(weight_meter.charge(SimpleToken(1)).is_err()); +} + +/// Previously, passing a `Weight` of 0 to `nested` would consume all of the meter's current +/// weight. +/// +/// Now, a `Weight` of 0 means no weight for the nested call. +#[test] +fn nested_zero_weight_requested() { + let test_weight = 50000.into(); + let mut weight_meter = WeightMeter::::new(Some(test_weight), None); + let weight_for_nested_call = weight_meter.nested(0.into()); + + assert_eq!(weight_meter.weight_left(), 50000.into()); + assert_eq!(weight_for_nested_call.weight_left(), 0.into()) +} + +#[test] +fn nested_some_weight_requested() { + let test_weight = 50000.into(); + let mut weight_meter = WeightMeter::::new(Some(test_weight), None); + let weight_for_nested_call = weight_meter.nested(10000.into()); + + assert_eq!(weight_meter.weight_consumed(), 0.into()); + assert_eq!(weight_for_nested_call.weight_left(), 10000.into()) +} + +#[test] +fn nested_all_weight_requested() { + let test_weight = Weight::from_parts(50000, 50000); + let mut weight_meter = WeightMeter::::new(Some(test_weight), None); + let weight_for_nested_call = weight_meter.nested(test_weight); + + assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); + assert_eq!(weight_for_nested_call.weight_left(), 50_000.into()) +} + +#[test] +fn nested_excess_weight_requested() { + let test_weight = Weight::from_parts(50000, 50000); + let mut weight_meter = WeightMeter::::new(Some(test_weight), None); + let weight_for_nested_call = weight_meter.nested(test_weight + 10000.into()); + + assert_eq!(weight_meter.weight_consumed(), Weight::from_parts(0, 0)); + assert_eq!(weight_for_nested_call.weight_left(), 50_000.into()) +} + +// Make sure that the weight meter does not charge in case of overcharge +#[test] +fn overcharge_does_not_charge() { + let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(200, 0)), None); + + // The first charge is should lead to OOG. + assert!(weight_meter.charge(SimpleToken(300)).is_err()); + + // The weight meter should still contain the full 200. + assert!(weight_meter.charge(SimpleToken(200)).is_ok()); +} + +// Charging the exact amount that the user paid for should be +// possible. +#[test] +fn charge_exact_amount() { + let mut weight_meter = WeightMeter::::new(Some(Weight::from_parts(25, 0)), None); + assert!(!weight_meter.charge(SimpleToken(25)).is_err()); +} diff --git a/substrate/frame/revive/src/precompiles.rs b/substrate/frame/revive/src/precompiles.rs index dac54703ae025..a78f4371c8627 100644 --- a/substrate/frame/revive/src/precompiles.rs +++ b/substrate/frame/revive/src/precompiles.rs @@ -31,7 +31,7 @@ mod tests; pub use crate::{ exec::{ExecError, PrecompileExt as Ext, PrecompileWithInfoExt as ExtWithInfo}, - metering::{storage::Diff, weight::Token}, + metering::{Diff, Token}, vm::RuntimeCosts, AddressMapper, TransactionLimits, }; diff --git a/substrate/frame/revive/src/precompiles/builtin/modexp.rs b/substrate/frame/revive/src/precompiles/builtin/modexp.rs index ec80008aaeda7..7c94179adfa34 100644 --- a/substrate/frame/revive/src/precompiles/builtin/modexp.rs +++ b/substrate/frame/revive/src/precompiles/builtin/modexp.rs @@ -366,7 +366,7 @@ mod tests { #[test] fn test_long_exp_gas_cost_matches_specs() { - use crate::{call_builder::CallSetup, metering::weight::Token, tests::ExtBuilder}; + use crate::{call_builder::CallSetup, metering::Token, tests::ExtBuilder}; let input = vec![ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index e442c17a6231f..77110178c9d3e 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -46,7 +46,7 @@ use sp_runtime::{ DispatchError, RuntimeDebug, }; -use crate::metering::storage::Diff; +use crate::metering::Diff; pub enum AccountIdOrAddress { /// An account that is a contract. diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index b3ba3228a29cd..82f238137cd12 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -190,10 +190,15 @@ fn run_call<'a, E: Ext>( value: U256, return_memory_range: Range, ) -> ControlFlow { - // We use ALL_STIPEND to detect the typical gas limit solc defines as a call stipend - // This is just a heuristic - let add_stipend = - !value.is_zero() || gas_limit.try_into().is_ok_and(|limit: u64| limit == CALL_STIPEND); + // We use ALL_STIPEND to detect the typical gas limit solc defines as a call stipend. This is + // just a heuristic. + let (add_stipend, reentracy) = + match (value.is_zero(), gas_limit.try_into().is_ok_and(|limit: u64| limit == CALL_STIPEND)) + { + (false, _) => (true, ReentrancyProtection::AllowReentry), + (_, true) => (true, ReentrancyProtection::AllowNext), + (_, _) => (false, ReentrancyProtection::AllowReentry), + }; let call_result = match scheme { CallScheme::Call | CallScheme::StaticCall => interpreter.ext.call( @@ -201,12 +206,8 @@ fn run_call<'a, E: Ext>( &callee, value, input, - // protect against re-entrancy when we grant the stipend - if add_stipend { - ReentrancyProtection::AllowNext - } else { - ReentrancyProtection::AllowReentry - }, + // protect against rex-entrancy when we grant the stipend + reentracy, scheme.is_static_call(), ), CallScheme::DelegateCall => interpreter.ext.delegate_call( diff --git a/substrate/frame/revive/src/vm/evm/instructions/host.rs b/substrate/frame/revive/src/vm/evm/instructions/host.rs index c7073738489af..d3cb9c77a5462 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/host.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/host.rs @@ -16,7 +16,7 @@ // limitations under the License. use crate::{ limits, - metering::weight::Token, + metering::Token, storage::WriteOutcome, vec::Vec, vm::{ diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index 36cf716f172e6..ff58b525f4cb2 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -27,7 +27,7 @@ pub use runtime_costs::RuntimeCosts; use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, frame_support::{ensure, error::BadOrigin}, - metering::{weight::Token, ResourceMeter, State}, + metering::{ResourceMeter, State, Token}, weights::WeightInfo, AccountIdOf, BalanceOf, CodeInfoOf, CodeRemoved, Config, Error, ExecConfig, ExecError, HoldReason, Pallet, PristineCode, StorageDeposit, Weight, LOG_TARGET, diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index 7ec2b3d09f28e..73a6c712ed3b2 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -25,7 +25,7 @@ pub use env::SyscallDoc; use crate::{ exec::{CallResources, ExecError, ExecResult, Ext, Key}, limits, - metering::weight::ChargedAmount, + metering::ChargedAmount, precompiles::{All as AllPrecompiles, Precompiles}, primitives::ExecReturnValue, Code, Config, Error, Pallet, ReentrancyProtection, RuntimeCosts, LOG_TARGET, SENTINEL, diff --git a/substrate/frame/revive/src/vm/runtime_costs.rs b/substrate/frame/revive/src/vm/runtime_costs.rs index 67cb9822efca8..cf4e782cd245f 100644 --- a/substrate/frame/revive/src/vm/runtime_costs.rs +++ b/substrate/frame/revive/src/vm/runtime_costs.rs @@ -16,8 +16,8 @@ // limitations under the License. use crate::{ - limits, metering::weight::Token, weightinfo_extension::OnFinalizeBlockParts, - weights::WeightInfo, Config, + limits, metering::Token, weightinfo_extension::OnFinalizeBlockParts, weights::WeightInfo, + Config, }; use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; From 2d7b95fb4c4e3c975c52e914a0be58d040c2e130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:40:00 -0300 Subject: [PATCH 64/69] Raise endowment of dev node --- substrate/frame/revive/dev-node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index d54a3c2b7bf7d..cb83652e68683 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -71,7 +71,7 @@ pub mod genesis_config_presets { use alloc::{vec, vec::Vec}; use serde_json::Value; - pub const ENDOWMENT: Balance = 1_000_000_000_001 * DOLLARS; + pub const ENDOWMENT: Balance = 10_000_000_000_001 * DOLLARS; fn well_known_accounts() -> Vec { Sr25519Keyring::well_known() From 3d8eec19865e34c82a4ce87da11a17abb8a6b092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:10:24 -0300 Subject: [PATCH 65/69] Lower GasScale of dev node --- substrate/frame/revive/dev-node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index cb83652e68683..fd4bfb9c44d7c 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -356,7 +356,7 @@ impl pallet_revive::Config for Runtime { type Time = Timestamp; type FeeInfo = FeeInfo; type DebugEnabled = ConstBool; - type GasScale = ConstU128<500000>; + type GasScale = ConstU128<50000>; } pallet_revive::impl_runtime_apis_plus_revive_traits!( From f872bac26cd8958d2d117c95e41ea3acce7df7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 2 Dec 2025 14:32:41 -0300 Subject: [PATCH 66/69] Implement code review feedback --- prdoc/pr_10393.prdoc | 2 +- substrate/frame/revive/src/lib.rs | 2 +- substrate/frame/revive/src/metering/gas.rs | 3 +- substrate/frame/revive/src/metering/tests.rs | 45 ++++++++++++++------ 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/prdoc/pr_10393.prdoc b/prdoc/pr_10393.prdoc index ea3a97de543d7..5f48ef937ae64 100644 --- a/prdoc/pr_10393.prdoc +++ b/prdoc/pr_10393.prdoc @@ -32,7 +32,7 @@ crates: - name: revive-dev-runtime bump: patch - name: pallet-revive - bump: patch + bump: major - name: asset-hub-westend-runtime bump: patch - name: penpal-runtime diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index e55cf1a9e9b5e..8e798b9b41742 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -915,7 +915,7 @@ pub mod pallet { fn integrity_test() { assert!(T::ChainId::get() > 0, "ChainId must be greater than 0"); - assert!(T::GasScale::get() > 0u32.into(), "GasScale must no be 0"); + assert!(T::GasScale::get() > 0u32.into(), "GasScale must not be 0"); T::FeeInfo::integrity_test(); diff --git a/substrate/frame/revive/src/metering/gas.rs b/substrate/frame/revive/src/metering/gas.rs index 7a01174baedcb..d2aaf97008e39 100644 --- a/substrate/frame/revive/src/metering/gas.rs +++ b/substrate/frame/revive/src/metering/gas.rs @@ -85,7 +85,8 @@ impl SignedGas { let gas_scale = ::GasScale::get(); match self { - Positive(amount) => Some((*amount) / gas_scale), + Positive(amount) => + Some((amount.saturating_add(gas_scale.saturating_sub(1u32.into()))) / gas_scale), Negative(..) => None, } } diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs index 7d764ca39da64..417166abb3b3e 100644 --- a/substrate/frame/revive/src/metering/tests.rs +++ b/substrate/frame/revive/src/metering/tests.rs @@ -127,14 +127,17 @@ fn substrate_metering_initialization_works() { EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit: eth_gas_limit / gas_scale, + eth_gas_limit: (eth_gas_limit + gas_scale - 1) / gas_scale, maybe_weight_limit: None, eth_tx_info, }); if let Some((gas_left, ref_time_left, proof_size_left, deposit_left)) = remaining { let transaction_meter = transaction_meter.unwrap(); - assert_eq!(gas_left / gas_scale, transaction_meter.eth_gas_left().unwrap()); + assert_eq!( + (gas_left + gas_scale - 1) / gas_scale, + transaction_meter.eth_gas_left().unwrap() + ); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), transaction_meter.weight_left().unwrap() @@ -254,7 +257,7 @@ fn substrate_metering_charges_works() { EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let mut transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit: eth_gas_limit / gas_scale, + eth_gas_limit: (eth_gas_limit + gas_scale - 1) / gas_scale, maybe_weight_limit: None, eth_tx_info, }) @@ -285,14 +288,17 @@ fn substrate_metering_charges_works() { )) = remaining { assert!(is_ok); - assert_eq!(gas_left / gas_scale, transaction_meter.eth_gas_left().unwrap()); + assert_eq!( + (gas_left + gas_scale - 1) / gas_scale, + transaction_meter.eth_gas_left().unwrap() + ); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), transaction_meter.weight_left().unwrap() ); assert_eq!(deposit_left, transaction_meter.deposit_left().unwrap()); assert_eq!( - gas_consumed / gas_scale, + (gas_consumed + gas_scale - 1) / gas_scale, transaction_meter.total_consumed_gas() ); } else { @@ -478,7 +484,7 @@ fn substrate_nesting_works() { let eth_tx_info = EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let mut transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit: eth_gas_limit / gas_scale, + eth_gas_limit: (eth_gas_limit + gas_scale - 1) / gas_scale, maybe_weight_limit: None, eth_tx_info: eth_tx_info.clone(), }) @@ -499,7 +505,8 @@ fn substrate_nesting_works() { .unwrap(); let scaled_call_resource = match call_resource { - Ethereum { gas, add_stipend } => Ethereum { gas: gas / gas_scale, add_stipend }, + Ethereum { gas, add_stipend } => + Ethereum { gas: (gas + gas_scale - 1) / gas_scale, add_stipend }, _ => call_resource, }; let nested = transaction_meter.new_nested(&scaled_call_resource); @@ -513,13 +520,19 @@ fn substrate_nesting_works() { )) = remaining { let nested = nested.unwrap(); - assert_eq!(gas_left / gas_scale, nested.eth_gas_left().unwrap()); + assert_eq!( + (gas_left + gas_scale - 1) / gas_scale, + nested.eth_gas_left().unwrap() + ); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), nested.weight_left().unwrap() ); assert_eq!(deposit_left, nested.deposit_left().unwrap()); - assert_eq!(gas_consumed / gas_scale, nested.total_consumed_gas()); + assert_eq!( + (gas_consumed + gas_scale - 1) / gas_scale, + nested.total_consumed_gas() + ); } else { assert!(nested.is_err()); } @@ -580,7 +593,7 @@ fn substrate_nesting_charges_works() { EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let mut transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit: eth_gas_limit / gas_scale, + eth_gas_limit: (eth_gas_limit + gas_scale - 1) / gas_scale, maybe_weight_limit: None, eth_tx_info, }) @@ -602,7 +615,7 @@ fn substrate_nesting_charges_works() { let mut nested = transaction_meter .new_nested(&CallResources::Ethereum { - gas: gas_limit / gas_scale, + gas: (gas_limit + gas_scale - 1) / gas_scale, add_stipend: false, }) .unwrap(); @@ -632,13 +645,19 @@ fn substrate_nesting_charges_works() { )) = remaining { assert!(is_ok); - assert_eq!(gas_left / gas_scale, nested.eth_gas_left().unwrap()); + assert_eq!( + (gas_left + gas_scale - 1) / gas_scale, + nested.eth_gas_left().unwrap() + ); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), nested.weight_left().unwrap() ); assert_eq!(deposit_left, nested.deposit_left().unwrap()); - assert_eq!(gas_consumed / gas_scale, nested.total_consumed_gas()); + assert_eq!( + (gas_consumed + gas_scale - 1) / gas_scale, + nested.total_consumed_gas() + ); } else { assert!(!is_ok); } From 4a591e0e109b86a6e52d82e590f1a27ac21f115d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Tue, 2 Dec 2025 15:38:09 -0300 Subject: [PATCH 67/69] Address clippy complaints --- substrate/frame/revive/src/metering/tests.rs | 64 +++++++++----------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs index 417166abb3b3e..c081f594e1ffe 100644 --- a/substrate/frame/revive/src/metering/tests.rs +++ b/substrate/frame/revive/src/metering/tests.rs @@ -18,8 +18,8 @@ use crate::{ test_utils::{builder::Contract, ALICE, ALICE_ADDR}, tests::{builder, ExtBuilder, Test}, - CallResources, Code, Config, EthTxInfo, StorageDeposit, TransactionLimits, TransactionMeter, - WeightToken, + BalanceOf, CallResources, Code, Config, EthTxInfo, StorageDeposit, TransactionLimits, + TransactionMeter, WeightToken, }; use alloy_core::sol_types::SolCall; use frame_support::traits::fungible::Mutate; @@ -107,7 +107,12 @@ fn substrate_metering_initialization_works() { let gas_scale = ::GasScale::get(); let tests = vec![ - (5_000_000_000, 1_000_000_000, 2_000, Some((2999999500, 1499999750, 11107, 599999900))), + ( + 5_000_000_000u64, + 1_000_000_000, + 2_000, + Some((2999999500u64, 1499999750, 11107, 599999900)), + ), (6_000_000_000, 1_000_000_000, 2_000, Some((3999999500, 1999999750, 13728, 799999900))), (6_000_000_000, 1_000_000_000, 10_000, Some((2185302235, 1999999750, 5728, 437060447))), (2_000_000_000, 1_000_000_000, 2_000, None), @@ -127,7 +132,7 @@ fn substrate_metering_initialization_works() { EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit: (eth_gas_limit + gas_scale - 1) / gas_scale, + eth_gas_limit: eth_gas_limit.div_ceil(gas_scale), maybe_weight_limit: None, eth_tx_info, }); @@ -135,7 +140,7 @@ fn substrate_metering_initialization_works() { if let Some((gas_left, ref_time_left, proof_size_left, deposit_left)) = remaining { let transaction_meter = transaction_meter.unwrap(); assert_eq!( - (gas_left + gas_scale - 1) / gas_scale, + gas_left.div_ceil(gas_scale), transaction_meter.eth_gas_left().unwrap() ); assert_eq!( @@ -189,8 +194,11 @@ fn substrate_metering_charges_works() { let gas_scale = ::GasScale::get(); let tests = vec![ ( - (5_000_000_000, 1_000_000_000, 2_000), - vec![(W(1000, 100), Some((2999997500, 1499998750, 11007, 599999500, 2000002500u64)))], + (5_000_000_000u64, 1_000_000_000, 2_000), + vec![( + W(1000, 100), + Some((2999997500u64, 1499998750, 11007, 599999500, 2000002500u64)), + )], ), ( (5_000_000_000, 1_000_000_000, 2_000), @@ -257,7 +265,7 @@ fn substrate_metering_charges_works() { EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let mut transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit: (eth_gas_limit + gas_scale - 1) / gas_scale, + eth_gas_limit: eth_gas_limit.div_ceil(gas_scale), maybe_weight_limit: None, eth_tx_info, }) @@ -289,7 +297,7 @@ fn substrate_metering_charges_works() { { assert!(is_ok); assert_eq!( - (gas_left + gas_scale - 1) / gas_scale, + gas_left.div_ceil(gas_scale), transaction_meter.eth_gas_left().unwrap() ); assert_eq!( @@ -298,7 +306,7 @@ fn substrate_metering_charges_works() { ); assert_eq!(deposit_left, transaction_meter.deposit_left().unwrap()); assert_eq!( - (gas_consumed + gas_scale - 1) / gas_scale, + gas_consumed.div_ceil(gas_scale), transaction_meter.total_consumed_gas() ); } else { @@ -316,8 +324,8 @@ fn substrate_nesting_works() { let gas_scale = ::GasScale::get(); let tests = vec![ ( - ((5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000i64), NoLimits), - Some((2999992500, 1499996250, 10107, 599998500, 2000007500)), + ((5_000_000_000u64, 1_000_000_000, 2_000, 1000, 1000, 1000i64), NoLimits), + Some((2999992500u64, 1499996250, 10107, 599998500, 2000007500u64)), ), ( ((5_000_000_000, 1_000_000_000, 2_000, 1000000000, 10000, 50000), NoLimits), @@ -484,7 +492,7 @@ fn substrate_nesting_works() { let eth_tx_info = EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let mut transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit: (eth_gas_limit + gas_scale - 1) / gas_scale, + eth_gas_limit: eth_gas_limit.div_ceil(gas_scale), maybe_weight_limit: None, eth_tx_info: eth_tx_info.clone(), }) @@ -506,7 +514,7 @@ fn substrate_nesting_works() { let scaled_call_resource = match call_resource { Ethereum { gas, add_stipend } => - Ethereum { gas: (gas + gas_scale - 1) / gas_scale, add_stipend }, + Ethereum { gas: (gas as BalanceOf).div_ceil(gas_scale), add_stipend }, _ => call_resource, }; let nested = transaction_meter.new_nested(&scaled_call_resource); @@ -520,19 +528,13 @@ fn substrate_nesting_works() { )) = remaining { let nested = nested.unwrap(); - assert_eq!( - (gas_left + gas_scale - 1) / gas_scale, - nested.eth_gas_left().unwrap() - ); + assert_eq!(gas_left.div_ceil(gas_scale), nested.eth_gas_left().unwrap()); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), nested.weight_left().unwrap() ); assert_eq!(deposit_left, nested.deposit_left().unwrap()); - assert_eq!( - (gas_consumed + gas_scale - 1) / gas_scale, - nested.total_consumed_gas() - ); + assert_eq!(gas_consumed.div_ceil(gas_scale), nested.total_consumed_gas()); } else { assert!(nested.is_err()); } @@ -547,9 +549,9 @@ fn substrate_nesting_charges_works() { let gas_scale = ::GasScale::get(); let tests = vec![ ( - (5_000_000_000, 1_000_000_000, 2_000, 1000, 100, 1000i64, 1000), + (5_000_000_000u64, 1_000_000_000, 2_000, 1000, 100, 1000i64, 1000u64), vec![ - (W(100, 100), Some((800, 400, 3042, 160, 2000007700))), + (W(100, 100), Some((800u64, 400, 3042, 160, 2000007700u64))), (D(100), Some((300, 150, 3042, 60, 2000008200))), ], ), @@ -593,7 +595,7 @@ fn substrate_nesting_charges_works() { EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof)); let mut transaction_meter = TransactionMeter::::new(TransactionLimits::EthereumGas { - eth_gas_limit: (eth_gas_limit + gas_scale - 1) / gas_scale, + eth_gas_limit: eth_gas_limit.div_ceil(gas_scale), maybe_weight_limit: None, eth_tx_info, }) @@ -615,7 +617,7 @@ fn substrate_nesting_charges_works() { let mut nested = transaction_meter .new_nested(&CallResources::Ethereum { - gas: (gas_limit + gas_scale - 1) / gas_scale, + gas: gas_limit.div_ceil(gas_scale), add_stipend: false, }) .unwrap(); @@ -645,19 +647,13 @@ fn substrate_nesting_charges_works() { )) = remaining { assert!(is_ok); - assert_eq!( - (gas_left + gas_scale - 1) / gas_scale, - nested.eth_gas_left().unwrap() - ); + assert_eq!(gas_left.div_ceil(gas_scale), nested.eth_gas_left().unwrap()); assert_eq!( Weight::from_parts(ref_time_left, proof_size_left), nested.weight_left().unwrap() ); assert_eq!(deposit_left, nested.deposit_left().unwrap()); - assert_eq!( - (gas_consumed + gas_scale - 1) / gas_scale, - nested.total_consumed_gas() - ); + assert_eq!(gas_consumed.div_ceil(gas_scale), nested.total_consumed_gas()); } else { assert!(!is_ok); } From c27a97eb35025be15761989c101b6e936669a4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 3 Dec 2025 03:23:29 -0300 Subject: [PATCH 68/69] Add missing licenses --- .../frame/revive/src/metering/storage/tests.rs | 17 +++++++++++++++++ .../frame/revive/src/metering/weight/tests.rs | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/substrate/frame/revive/src/metering/storage/tests.rs b/substrate/frame/revive/src/metering/storage/tests.rs index d1657e47757ee..7a3f3be561b02 100644 --- a/substrate/frame/revive/src/metering/storage/tests.rs +++ b/substrate/frame/revive/src/metering/storage/tests.rs @@ -1,3 +1,20 @@ +// 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. + use super::*; use crate::{exec::AccountIdOf, test_utils::*, tests::Test}; use frame_support::parameter_types; diff --git a/substrate/frame/revive/src/metering/weight/tests.rs b/substrate/frame/revive/src/metering/weight/tests.rs index 540b7d42d2e05..bf8801e6f76e9 100644 --- a/substrate/frame/revive/src/metering/weight/tests.rs +++ b/substrate/frame/revive/src/metering/weight/tests.rs @@ -1,3 +1,20 @@ +// 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. + use super::{Token, Weight, WeightMeter}; use crate::tests::Test; From 922eb7a0f87292a2c10513b0af304f3cba752b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20Stu=CC=88ber?= <15174476+TorstenStueber@users.noreply.github.com> Date: Wed, 3 Dec 2025 17:47:41 -0300 Subject: [PATCH 69/69] Change GasScale to u32 --- .../runtimes/assets/asset-hub-westend/src/lib.rs | 2 +- cumulus/parachains/runtimes/testing/penpal/src/lib.rs | 2 +- substrate/bin/node/runtime/src/lib.rs | 2 +- substrate/frame/revive/dev-node/runtime/src/lib.rs | 2 +- substrate/frame/revive/src/lib.rs | 4 ++-- substrate/frame/revive/src/metering/gas.rs | 4 ++-- substrate/frame/revive/src/metering/tests.rs | 8 ++++---- 7 files changed, 12 insertions(+), 12 deletions(-) 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 7bff872958c81..4e16eeba4c10e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1214,7 +1214,7 @@ impl pallet_revive::Config for Runtime { type FeeInfo = pallet_revive::evm::fees::Info; type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; type DebugEnabled = ConstBool; - type GasScale = ConstU128<1000>; + type GasScale = ConstU32<1000>; } parameter_types! { diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 794d40d5fb148..01f6dd1700d0d 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -842,7 +842,7 @@ impl pallet_revive::Config for Runtime { type FeeInfo = pallet_revive::evm::fees::Info; type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; type DebugEnabled = ConstBool; - type GasScale = ConstU128<1000>; + type GasScale = ConstU32<1000>; } impl pallet_sudo::Config for Runtime { diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 539f5fccd9eb9..8eb710dfa99e4 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1539,7 +1539,7 @@ impl pallet_revive::Config for Runtime { type FeeInfo = pallet_revive::evm::fees::Info; type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; type DebugEnabled = ConstBool; - type GasScale = ConstU128<1000>; + type GasScale = ConstU32<1000>; } impl pallet_sudo::Config for Runtime { diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index fd4bfb9c44d7c..046d16ea41083 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -356,7 +356,7 @@ impl pallet_revive::Config for Runtime { type Time = Timestamp; type FeeInfo = FeeInfo; type DebugEnabled = ConstBool; - type GasScale = ConstU128<50000>; + type GasScale = ConstU32<50000>; } pallet_revive::impl_runtime_apis_plus_revive_traits!( diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 8e798b9b41742..b47caf6f98aff 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -367,7 +367,7 @@ pub mod pallet { /// Requirement: `GasScale` must not be 0 #[pallet::constant] #[pallet::no_default_bounds] - type GasScale: Get>; + type GasScale: Get; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -396,7 +396,7 @@ pub mod pallet { pub const DepositPerByte: Balance = deposit(0, 1); pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); pub const MaxEthExtrinsicWeight: FixedU128 = FixedU128::from_rational(9, 10); - pub const GasScale: Balance = 10 as Balance; + pub const GasScale: u32 = 10u32; } /// A type providing default configurations for this pallet in testing environment. diff --git a/substrate/frame/revive/src/metering/gas.rs b/substrate/frame/revive/src/metering/gas.rs index 4ed4671145007..4e51c82c57ade 100644 --- a/substrate/frame/revive/src/metering/gas.rs +++ b/substrate/frame/revive/src/metering/gas.rs @@ -54,7 +54,7 @@ impl SignedGas { /// the internally used SignedGas. pub fn from_ethereum_gas(gas: BalanceOf) -> Self { let gas_scale = ::GasScale::get(); - Self::Positive(gas.saturating_mul(gas_scale)) + Self::Positive(gas.saturating_mul(gas_scale.into())) } /// Transform a storage deposit into a gas value. The value will be adjusted by dividing it @@ -82,7 +82,7 @@ impl SignedGas { /// Transform the gas amount to an Ethereum gas amount usable for external purposes /// Returns None if the gas amount is negative. pub fn to_ethereum_gas(&self) -> Option> { - let gas_scale = ::GasScale::get(); + let gas_scale: BalanceOf = ::GasScale::get().into(); match self { Positive(amount) => diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs index c081f594e1ffe..35089c593cc04 100644 --- a/substrate/frame/revive/src/metering/tests.rs +++ b/substrate/frame/revive/src/metering/tests.rs @@ -104,7 +104,7 @@ fn max_consumed_deposit_integration_refunds_subframes( #[test] fn substrate_metering_initialization_works() { - let gas_scale = ::GasScale::get(); + let gas_scale = ::GasScale::get().into(); let tests = vec![ ( @@ -191,7 +191,7 @@ fn substrate_metering_initialization_works() { fn substrate_metering_charges_works() { use Charge::{D, W}; - let gas_scale = ::GasScale::get(); + let gas_scale = ::GasScale::get().into(); let tests = vec![ ( (5_000_000_000u64, 1_000_000_000, 2_000), @@ -321,7 +321,7 @@ fn substrate_metering_charges_works() { fn substrate_nesting_works() { use CallResources::{Ethereum, NoLimits, WeightDeposit}; - let gas_scale = ::GasScale::get(); + let gas_scale = ::GasScale::get().into(); let tests = vec![ ( ((5_000_000_000u64, 1_000_000_000, 2_000, 1000, 1000, 1000i64), NoLimits), @@ -546,7 +546,7 @@ fn substrate_nesting_works() { fn substrate_nesting_charges_works() { use Charge::{D, W}; - let gas_scale = ::GasScale::get(); + let gas_scale = ::GasScale::get().into(); let tests = vec![ ( (5_000_000_000u64, 1_000_000_000, 2_000, 1000, 100, 1000i64, 1000u64),