Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions prdoc/pr_9997.prdoc
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions substrate/frame/revive/fixtures/contracts/consume_all_gas.rs
Original file line number Diff line number Diff line change
@@ -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();
}
38 changes: 38 additions & 0 deletions substrate/frame/revive/src/tests/pvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _ = <Test as Config>::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"
);
});
}
12 changes: 12 additions & 0 deletions substrate/frame/revive/src/vm/pvm/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,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(
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/revive/uapi/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
4 changes: 4 additions & 0 deletions substrate/frame/revive/uapi/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
6 changes: 6 additions & 0 deletions substrate/frame/revive/uapi/src/host/riscv64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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()) };
Expand Down
Loading