Skip to content

Commit d65db6e

Browse files
[pallet-revive] Implement the consume_all_gas syscall (#9997)
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. --------- Signed-off-by: Cyrill Leutwiler <[email protected]> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 1cbdb4d commit d65db6e

7 files changed

Lines changed: 119 additions & 1 deletion

File tree

prdoc/pr_9997.prdoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
title: '[pallet-revive] Implement the consume_all_gas syscall'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
This PR implements a new API `consume_all_gas` which is required for 100% EVM `INVALID` opcode compatibility.
6+
7+
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.
8+
9+
Didn't implement a benchmark since the first (and only) thing this does is consuming all remaining gas anyways.
10+
crates:
11+
- name: pallet-revive-fixtures
12+
bump: minor
13+
- name: pallet-revive
14+
bump: minor
15+
- name: pallet-revive-uapi
16+
bump: minor
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
#![no_std]
19+
#![no_main]
20+
include!("../panic_handler.rs");
21+
22+
use uapi::{u64_output, HostFn, HostFnImpl as api, ReturnFlags};
23+
24+
fn decide_my_fate() -> ! {
25+
match u64_output!(api::value_transferred,) {
26+
0 => api::consume_all_gas(),
27+
1 => api::return_value(ReturnFlags::REVERT, &[]),
28+
_ => api::return_value(ReturnFlags::empty(), &[]),
29+
}
30+
}
31+
32+
#[no_mangle]
33+
#[polkavm_derive::polkavm_export]
34+
pub extern "C" fn deploy() {
35+
decide_my_fate();
36+
}
37+
38+
#[no_mangle]
39+
#[polkavm_derive::polkavm_export]
40+
pub extern "C" fn call() {
41+
decide_my_fate();
42+
}

substrate/frame/revive/src/tests/pvm.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5115,3 +5115,41 @@ fn get_set_immutables_works() {
51155115
assert_eq!(immutable_data, new_data);
51165116
});
51175117
}
5118+
5119+
#[test]
5120+
fn consume_all_gas_works() {
5121+
let (code, code_hash) = compile_module("consume_all_gas").unwrap();
5122+
5123+
ExtBuilder::default().build().execute_with(|| {
5124+
let _ = <Test as Config>::Currency::set_balance(&ALICE, 100_000_000_000);
5125+
5126+
assert_eq!(
5127+
builder::bare_instantiate(Code::Upload(code)).build().gas_consumed,
5128+
GAS_LIMIT,
5129+
"callvalue == 0 should consume all gas in deploy"
5130+
);
5131+
assert_ne!(
5132+
builder::bare_instantiate(Code::Existing(code_hash))
5133+
.evm_value(1.into())
5134+
.build()
5135+
.gas_consumed,
5136+
GAS_LIMIT,
5137+
"callvalue == 1 should not consume all gas in deploy"
5138+
);
5139+
5140+
let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash))
5141+
.evm_value(2.into())
5142+
.build_and_unwrap_contract();
5143+
5144+
assert_eq!(
5145+
builder::bare_call(addr).build().gas_consumed,
5146+
GAS_LIMIT,
5147+
"callvalue == 0 should consume all gas"
5148+
);
5149+
assert_ne!(
5150+
builder::bare_call(addr).evm_value(1.into()).build().gas_consumed,
5151+
GAS_LIMIT,
5152+
"callvalue == 1 should not consume all gas"
5153+
);
5154+
});
5155+
}

substrate/frame/revive/src/vm/pvm/env.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,18 @@ pub mod env {
840840
Ok(self.ext.gas_left())
841841
}
842842

843+
/// Reverts the execution without data and cedes all remaining gas.
844+
///
845+
/// See [`pallet_revive_uapi::HostFn::consume_all_gas`].
846+
#[stable]
847+
fn consume_all_gas(&mut self, memory: &mut M) -> Result<(), TrapReason> {
848+
self.ext.gas_meter_mut().consume_all();
849+
Err(TrapReason::Return(ReturnData {
850+
flags: ReturnFlags::REVERT.bits(),
851+
data: Default::default(),
852+
}))
853+
}
854+
843855
/// Calculates Ethereum address from the ECDSA compressed public key and stores
844856
/// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`].
845857
fn ecdsa_to_eth_address(

substrate/frame/revive/uapi/src/flags.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ bitflags! {
2323
#[derive(Default)]
2424
pub struct ReturnFlags: u32 {
2525
/// If this bit is set all changes made by the contract execution are rolled back.
26-
const REVERT = 0x0000_0001;
26+
const REVERT = 0b0000_0001;
2727
}
2828
}
2929

substrate/frame/revive/uapi/src/host.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,10 @@ pub trait HostFn: private::Sealed {
432432
/// - `output`: A reference to the output data buffer to write the block number.
433433
fn block_hash(block_number: &[u8; 32], output: &mut [u8; 32]);
434434

435+
/// Reverts the execution and cedes all supplied gas,
436+
/// akin to the `INVALID` EVM opcode.
437+
fn consume_all_gas() -> !;
438+
435439
/// Calculates Ethereum address from the ECDSA compressed public key and stores
436440
/// it into the supplied buffer.
437441
///

substrate/frame/revive/uapi/src/host/riscv64.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ mod sys {
122122
pub fn instantiation_nonce() -> u64;
123123
pub fn return_data_size() -> u64;
124124
pub fn return_data_copy(out_ptr: *mut u8, out_len_ptr: *mut u32, offset: u32);
125+
pub fn consume_all_gas();
125126
}
126127
}
127128

@@ -429,6 +430,11 @@ impl HostFn for HostFnImpl {
429430
unsafe { sys::call_data_copy(output.as_mut_ptr(), len, offset) };
430431
}
431432

433+
fn consume_all_gas() -> ! {
434+
unsafe { sys::consume_all_gas() }
435+
unreachable!("consume_all_gas does not return");
436+
}
437+
432438
#[unstable_hostfn]
433439
fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result {
434440
let ret_code = unsafe { sys::ecdsa_to_eth_address(pubkey.as_ptr(), output.as_mut_ptr()) };

0 commit comments

Comments
 (0)