Skip to content

Commit 5a1128b

Browse files
[pallet-revive] add EVM gas call syscalls (#10554)
This PR adds two new syscalls for calls accepting EVM gas instead of Weight and Deposit. This is an important change for the initial release as it will align PVM contracts closer to EVM (the problem can't be solved in the Solidity compiler). --------- Signed-off-by: xermicus <[email protected]> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent d6a8d0a commit 5a1128b

File tree

8 files changed

+365
-21
lines changed

8 files changed

+365
-21
lines changed

prdoc/pr_10554.prdoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
title: '[pallet-revive] add EVM gas call syscalls'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
This PR adds two new syscalls for calls accepting EVM gas instead of Weight and Deposit.
6+
7+
This is an important change for the initial release as it will align PVM contracts closer to EVM (the problem can't be solved in the Solidity compiler).
8+
crates:
9+
- name: pallet-revive-fixtures
10+
bump: minor
11+
- name: pallet-revive
12+
bump: minor
13+
- name: pallet-revive-uapi
14+
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+
21+
include!("../panic_handler.rs");
22+
23+
use uapi::{input, CallFlags, HostFn, HostFnImpl as api};
24+
25+
#[no_mangle]
26+
#[polkavm_derive::polkavm_export]
27+
pub extern "C" fn deploy() {}
28+
29+
#[no_mangle]
30+
#[polkavm_derive::polkavm_export]
31+
pub extern "C" fn call() {
32+
input!(
33+
256,
34+
callee_addr: &[u8; 20],
35+
gas: u64,
36+
);
37+
38+
let mut value = [0; 32];
39+
api::value_transferred(&mut value);
40+
41+
api::call_evm(CallFlags::empty(), callee_addr, gas, &value, &[], None).unwrap();
42+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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::{input, CallFlags, HostFn, HostFnImpl as api, StorageFlags};
23+
24+
#[no_mangle]
25+
#[polkavm_derive::polkavm_export]
26+
pub extern "C" fn deploy() {}
27+
28+
#[no_mangle]
29+
#[polkavm_derive::polkavm_export]
30+
pub extern "C" fn call() {
31+
input!(
32+
address: &[u8; 20],
33+
gas: u64,
34+
);
35+
36+
let mut key = [0u8; 32];
37+
key[0] = 1u8;
38+
39+
let mut value = [0u8; 32];
40+
let value = &mut &mut value[..];
41+
value[0] = 2u8;
42+
43+
api::set_storage(StorageFlags::empty(), &key, value);
44+
api::get_storage(StorageFlags::empty(), &key, value).unwrap();
45+
assert!(value[0] == 2u8);
46+
47+
api::delegate_call_evm(CallFlags::empty(), address, gas, &[], None).unwrap();
48+
49+
api::get_storage(StorageFlags::empty(), &key, value).unwrap();
50+
assert!(value[0] == 1u8);
51+
}
52+

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

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5536,3 +5536,78 @@ fn self_destruct_by_syscall_tracing_works() {
55365536
});
55375537
}
55385538
}
5539+
5540+
#[test]
5541+
fn delegate_call_with_gas_limit() {
5542+
let (caller_binary, _caller_code_hash) = compile_module("delegate_call_evm").unwrap();
5543+
let (callee_binary, _callee_code_hash) = compile_module("delegate_call_lib").unwrap();
5544+
5545+
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
5546+
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
5547+
5548+
let Contract { addr: caller_addr, .. } =
5549+
builder::bare_instantiate(Code::Upload(caller_binary))
5550+
.native_value(300_000)
5551+
.build_and_unwrap_contract();
5552+
5553+
let Contract { addr: callee_addr, .. } =
5554+
builder::bare_instantiate(Code::Upload(callee_binary))
5555+
.native_value(100_000)
5556+
.build_and_unwrap_contract();
5557+
5558+
// fails, not enough gas
5559+
assert_err!(
5560+
builder::bare_call(caller_addr)
5561+
.native_value(1337)
5562+
.data((callee_addr, 100u64).encode())
5563+
.build()
5564+
.result,
5565+
Error::<Test>::ContractTrapped,
5566+
);
5567+
5568+
assert_ok!(builder::call(caller_addr)
5569+
.value(1337)
5570+
.data((callee_addr, 100_000_000_000u64).encode())
5571+
.build());
5572+
});
5573+
}
5574+
5575+
#[test]
5576+
fn call_with_gas_limit() {
5577+
let (caller_binary, _caller_code_hash) = compile_module("call_with_gas").unwrap();
5578+
let (callee_binary, _callee_code_hash) = compile_module("dummy").unwrap();
5579+
5580+
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
5581+
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
5582+
5583+
let Contract { addr: caller_addr, .. } =
5584+
builder::bare_instantiate(Code::Upload(caller_binary))
5585+
.native_value(300_000)
5586+
.build_and_unwrap_contract();
5587+
5588+
let Contract { addr: callee_addr, .. } =
5589+
builder::bare_instantiate(Code::Upload(callee_binary))
5590+
.native_value(100_000)
5591+
.build_and_unwrap_contract();
5592+
5593+
// fails, not enough gas
5594+
assert_err!(
5595+
builder::bare_call(caller_addr)
5596+
.data((callee_addr, 1u64).encode())
5597+
.build()
5598+
.result,
5599+
Error::<Test>::ContractTrapped,
5600+
);
5601+
5602+
// succeeds, not enough gas but call stipend will be added
5603+
assert_ok!(builder::call(caller_addr)
5604+
.value(1337)
5605+
.data((callee_addr, 100u64).encode())
5606+
.build());
5607+
5608+
// succeeds, enough gas
5609+
assert_ok!(builder::call(caller_addr)
5610+
.data((callee_addr, 100_000_000_000u64).encode())
5611+
.build());
5612+
});
5613+
}

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

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -631,8 +631,7 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> {
631631
flags: CallFlags,
632632
call_type: CallType,
633633
callee_ptr: u32,
634-
deposit_ptr: u32,
635-
weight: Weight,
634+
resources: &CallResources<E::T>,
636635
input_data_ptr: u32,
637636
input_data_len: u32,
638637
output_ptr: u32,
@@ -647,8 +646,6 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> {
647646
None => self.charge_gas(call_type.cost())?,
648647
};
649648

650-
let deposit_limit = memory.read_u256(deposit_ptr)?;
651-
652649
// we do check this in exec.rs but we want to error out early
653650
if input_data_len > limits::CALLDATA_BYTES {
654651
Err(<Error<E::T>>::CallDataTooLarge)?;
@@ -693,24 +690,13 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> {
693690
ReentrancyProtection::Strict
694691
};
695692

696-
self.ext.call(
697-
&CallResources::from_weight_and_deposit(weight, deposit_limit),
698-
&callee,
699-
value,
700-
input_data,
701-
reentrancy,
702-
read_only,
703-
)
693+
self.ext.call(resources, &callee, value, input_data, reentrancy, read_only)
704694
},
705695
CallType::DelegateCall => {
706696
if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) {
707697
return Err(Error::<E::T>::InvalidCallFlags.into());
708698
}
709-
self.ext.delegate_call(
710-
&CallResources::from_weight_and_deposit(weight, deposit_limit),
711-
callee,
712-
input_data,
713-
)
699+
self.ext.delegate_call(resources, callee, input_data)
714700
},
715701
};
716702

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

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,14 +304,55 @@ pub mod env {
304304
let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value);
305305
let (input_data_len, input_data_ptr) = extract_hi_lo(input_data);
306306
let (output_len_ptr, output_ptr) = extract_hi_lo(output_data);
307+
let weight = Weight::from_parts(ref_time_limit, proof_size_limit);
308+
309+
self.charge_gas(RuntimeCosts::CopyFromContract(32))?;
310+
let deposit_limit = memory.read_u256(deposit_ptr)?;
307311

308312
self.call(
309313
memory,
310314
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
311315
CallType::Call { value_ptr },
312316
callee_ptr,
313-
deposit_ptr,
314-
Weight::from_parts(ref_time_limit, proof_size_limit),
317+
&CallResources::from_weight_and_deposit(weight, deposit_limit),
318+
input_data_ptr,
319+
input_data_len,
320+
output_ptr,
321+
output_len_ptr,
322+
)
323+
}
324+
325+
/// Make a call to another contract.
326+
/// See [`pallet_revive_uapi::HostFn::call_evm`].
327+
#[stable]
328+
fn call_evm(
329+
&mut self,
330+
memory: &mut M,
331+
flags: u32,
332+
callee: u32,
333+
value_ptr: u32,
334+
gas: u64,
335+
input_data: u64,
336+
output_data: u64,
337+
) -> Result<ReturnErrorCode, TrapReason> {
338+
let (input_data_len, input_data_ptr) = extract_hi_lo(input_data);
339+
let (output_len_ptr, output_ptr) = extract_hi_lo(output_data);
340+
let resources = if gas == u64::MAX {
341+
CallResources::NoLimits
342+
} else {
343+
self.charge_gas(RuntimeCosts::CopyFromContract(32))?;
344+
let value = memory.read_u256(value_ptr)?;
345+
// We also need to detect the 2300: We need to add something scaled.
346+
let add_stipend = !value.is_zero() || gas == revm::interpreter::gas::CALL_STIPEND;
347+
CallResources::from_ethereum_gas(gas.into(), add_stipend)
348+
};
349+
350+
self.call(
351+
memory,
352+
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
353+
CallType::Call { value_ptr },
354+
callee,
355+
&resources,
315356
input_data_ptr,
316357
input_data_len,
317358
output_ptr,
@@ -335,14 +376,50 @@ pub mod env {
335376
let (flags, address_ptr) = extract_hi_lo(flags_and_callee);
336377
let (input_data_len, input_data_ptr) = extract_hi_lo(input_data);
337378
let (output_len_ptr, output_ptr) = extract_hi_lo(output_data);
379+
let weight = Weight::from_parts(ref_time_limit, proof_size_limit);
380+
381+
self.charge_gas(RuntimeCosts::CopyFromContract(32))?;
382+
let deposit_limit = memory.read_u256(deposit_ptr)?;
338383

339384
self.call(
340385
memory,
341386
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
342387
CallType::DelegateCall,
343388
address_ptr,
344-
deposit_ptr,
345-
Weight::from_parts(ref_time_limit, proof_size_limit),
389+
&CallResources::from_weight_and_deposit(weight, deposit_limit),
390+
input_data_ptr,
391+
input_data_len,
392+
output_ptr,
393+
output_len_ptr,
394+
)
395+
}
396+
397+
/// Same as `delegate_call` but with EVM gas.
398+
/// See [`pallet_revive_uapi::HostFn::delegate_call_evm`].
399+
#[stable]
400+
fn delegate_call_evm(
401+
&mut self,
402+
memory: &mut M,
403+
flags: u32,
404+
callee: u32,
405+
gas: u64,
406+
input_data: u64,
407+
output_data: u64,
408+
) -> Result<ReturnErrorCode, TrapReason> {
409+
let (input_data_len, input_data_ptr) = extract_hi_lo(input_data);
410+
let (output_len_ptr, output_ptr) = extract_hi_lo(output_data);
411+
let resources = if gas == u64::MAX {
412+
CallResources::NoLimits
413+
} else {
414+
CallResources::from_ethereum_gas(gas.into(), false)
415+
};
416+
417+
self.call(
418+
memory,
419+
CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
420+
CallType::DelegateCall,
421+
callee,
422+
&resources,
346423
input_data_ptr,
347424
input_data_len,
348425
output_ptr,

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@ pub trait HostFn: private::Sealed {
119119
output: Option<&mut &mut [u8]>,
120120
) -> Result;
121121

122+
/// Same as [HostFn::call] but receives the one-dimensional EVM gas argument.
123+
///
124+
/// Adds the EVM gas stipend for non-zero value calls.
125+
///
126+
/// If gas is `u64::MAX`, the call will run with uncapped limits.
127+
fn call_evm(
128+
flags: CallFlags,
129+
callee: &[u8; 20],
130+
gas: u64,
131+
value: &[u8; 32],
132+
input_data: &[u8],
133+
output: Option<&mut &mut [u8]>,
134+
) -> Result;
135+
122136
/// Stores the address of the caller into the supplied buffer.
123137
///
124138
/// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the
@@ -207,6 +221,17 @@ pub trait HostFn: private::Sealed {
207221
output: Option<&mut &mut [u8]>,
208222
) -> Result;
209223

224+
/// Same as [HostFn::delegate_call] but receives the one-dimensional EVM gas argument.
225+
///
226+
/// If gas is `u64::MAX`, the call will run with uncapped limits.
227+
fn delegate_call_evm(
228+
flags: CallFlags,
229+
address: &[u8; 20],
230+
gas: u64,
231+
input_data: &[u8],
232+
output: Option<&mut &mut [u8]>,
233+
) -> Result;
234+
210235
/// Deposit a contract event with the data buffer and optional list of topics. There is a limit
211236
/// on the maximum number of topics specified by `event_topics`.
212237
///

0 commit comments

Comments
 (0)