From ebbe2e912d509fbedd96bef8f79bb323319db5bd Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Fri, 10 Oct 2025 16:36:49 +0200 Subject: [PATCH 1/3] the consume_all_gas syscall Signed-off-by: Cyrill Leutwiler --- .../fixtures/contracts/consume_all_gas.rs | 42 +++++++++++++++++++ substrate/frame/revive/src/tests/pvm.rs | 38 +++++++++++++++++ substrate/frame/revive/src/vm/pvm/env.rs | 14 +++++++ substrate/frame/revive/uapi/src/flags.rs | 2 +- substrate/frame/revive/uapi/src/host.rs | 4 ++ .../frame/revive/uapi/src/host/riscv64.rs | 6 +++ 6 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 substrate/frame/revive/fixtures/contracts/consume_all_gas.rs diff --git a/substrate/frame/revive/fixtures/contracts/consume_all_gas.rs b/substrate/frame/revive/fixtures/contracts/consume_all_gas.rs new file mode 100644 index 0000000000000..4556d7e5e18c1 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/consume_all_gas.rs @@ -0,0 +1,42 @@ +// 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. + +#![no_std] +#![no_main] +include!("../panic_handler.rs"); + +use uapi::{u64_output, HostFn, HostFnImpl as api, ReturnFlags}; + +fn decide_my_fate() -> ! { + match u64_output!(api::value_transferred,) { + 0 => api::consume_all_gas(), + 1 => api::return_value(ReturnFlags::REVERT, &[]), + _ => api::return_value(ReturnFlags::empty(), &[]), + } +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + decide_my_fate(); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + decide_my_fate(); +} diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index 56f380a633363..6b3777eaf41c2 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -5115,3 +5115,41 @@ fn get_set_immutables_works() { assert_eq!(immutable_data, new_data); }); } + +#[test] +fn consume_all_gas_works() { + let (code, code_hash) = compile_module("consume_all_gas").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + assert_eq!( + builder::bare_instantiate(Code::Upload(code)).build().gas_consumed, + GAS_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, + "callvalue == 1 should not consume all gas in deploy" + ); + + let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash)) + .evm_value(2.into()) + .build_and_unwrap_contract(); + + assert_eq!( + builder::bare_call(addr).build().gas_consumed, + GAS_LIMIT, + "callvalue == 0 should consume all gas" + ); + assert_ne!( + builder::bare_call(addr).evm_value(1.into()).build().gas_consumed, + GAS_LIMIT, + "callvalue == 1 should not consume all gas" + ); + }); +} diff --git a/substrate/frame/revive/src/vm/pvm/env.rs b/substrate/frame/revive/src/vm/pvm/env.rs index 764fd8dffa6a7..2dbf613d60f0f 100644 --- a/substrate/frame/revive/src/vm/pvm/env.rs +++ b/substrate/frame/revive/src/vm/pvm/env.rs @@ -185,6 +185,8 @@ impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { // for every function. #[define_env] pub mod env { + use pallet_revive_uapi::ReturnFlags; + /// Noop function used to benchmark the time it takes to execute an empty function. /// /// Marked as stable because it needs to be called from benchmarks even when the benchmarked @@ -840,6 +842,18 @@ pub mod env { Ok(self.ext.gas_left()) } + /// Reverts the execution without data and cedes all remaining gas. + /// + /// 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(); + Err(TrapReason::Return(ReturnData { + flags: ReturnFlags::REVERT.bits(), + data: Default::default(), + })) + } + /// Calculates Ethereum address from the ECDSA compressed public key and stores /// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`]. fn ecdsa_to_eth_address( diff --git a/substrate/frame/revive/uapi/src/flags.rs b/substrate/frame/revive/uapi/src/flags.rs index 6a0f47c38c2c8..88a7bf1303224 100644 --- a/substrate/frame/revive/uapi/src/flags.rs +++ b/substrate/frame/revive/uapi/src/flags.rs @@ -23,7 +23,7 @@ bitflags! { #[derive(Default)] pub struct ReturnFlags: u32 { /// If this bit is set all changes made by the contract execution are rolled back. - const REVERT = 0x0000_0001; + const REVERT = 0b0000_0001; } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 5d5c89090668f..c85d175237a6f 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -432,6 +432,10 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the block number. fn block_hash(block_number: &[u8; 32], output: &mut [u8; 32]); + /// Reverts the execution and cedes all supplied gas, + /// akin to the `INVALID` EVM opcode. + fn consume_all_gas() -> !; + /// Calculates Ethereum address from the ECDSA compressed public key and stores /// it into the supplied buffer. /// diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index a7381ce1cccda..fdfd234d7de5e 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -122,6 +122,7 @@ mod sys { pub fn instantiation_nonce() -> u64; pub fn return_data_size() -> u64; pub fn return_data_copy(out_ptr: *mut u8, out_len_ptr: *mut u32, offset: u32); + pub fn consume_all_gas(); } } @@ -429,6 +430,11 @@ impl HostFn for HostFnImpl { unsafe { sys::call_data_copy(output.as_mut_ptr(), len, offset) }; } + fn consume_all_gas() -> ! { + unsafe { sys::consume_all_gas() } + unreachable!("consume_all_gas does not return"); + } + #[unstable_hostfn] fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result { let ret_code = unsafe { sys::ecdsa_to_eth_address(pubkey.as_ptr(), output.as_mut_ptr()) }; From a8676ca5858a4e7acf2ea837c28adbf8dc412f70 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:56:58 +0000 Subject: [PATCH 2/3] Update from github-actions[bot] running command 'prdoc --audience runtime_dev --bump minor' --- prdoc/pr_9997.prdoc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 prdoc/pr_9997.prdoc diff --git a/prdoc/pr_9997.prdoc b/prdoc/pr_9997.prdoc new file mode 100644 index 0000000000000..c1c161f1a5d5a --- /dev/null +++ b/prdoc/pr_9997.prdoc @@ -0,0 +1,16 @@ +title: '[pallet-revive] Implement the consume_all_gas syscall' +doc: +- audience: Runtime Dev + description: |- + This PR implements a new API `consume_all_gas` which is required for 100% EVM `INVALID` opcode compatibility. + + Since ceding of all remaining gas is handled in the EVM interpreter, I decided to not add a return flag but make this a dedicated syscall for consistency instead. + + Didn't implement a benchmark since the first (and only) thing this does is consuming all remaining gas anyways. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor From 7140887222978a3ab6999cc9f682708384d05c50 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Fri, 10 Oct 2025 17:17:38 +0200 Subject: [PATCH 3/3] rm spurious import Signed-off-by: Cyrill Leutwiler --- substrate/frame/revive/src/vm/pvm/env.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/substrate/frame/revive/src/vm/pvm/env.rs b/substrate/frame/revive/src/vm/pvm/env.rs index 2dbf613d60f0f..7c923f7b9fb49 100644 --- a/substrate/frame/revive/src/vm/pvm/env.rs +++ b/substrate/frame/revive/src/vm/pvm/env.rs @@ -185,8 +185,6 @@ impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { // for every function. #[define_env] pub mod env { - use pallet_revive_uapi::ReturnFlags; - /// Noop function used to benchmark the time it takes to execute an empty function. /// /// Marked as stable because it needs to be called from benchmarks even when the benchmarked