Skip to content

Commit 95d4218

Browse files
pgherveougithub-actions[bot]athei
committed
[pallet-revive] ecrecover (#7652)
Add ECrecover 0x1 precompile and remove the unstable equivalent host function. - depend on #7676 --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Alexander Theißen <[email protected]>
1 parent 77b1586 commit 95d4218

File tree

13 files changed

+339
-189
lines changed

13 files changed

+339
-189
lines changed

prdoc/pr_7652.prdoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
title: '[pallet-revive] ecrecover'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
Add ECrecover 0x1 precompile and remove the unstable equivalent host function.
6+
crates:
7+
- name: asset-hub-westend-runtime
8+
bump: minor
9+
- name: pallet-revive-eth-rpc
10+
bump: minor
11+
- name: pallet-revive
12+
bump: minor
13+
- name: pallet-revive-fixtures
14+
bump: minor
15+
- name: pallet-revive-uapi
16+
bump: minor

substrate/frame/revive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ environmental = { workspace = true }
2424
ethabi = { workspace = true }
2525
ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] }
2626
hex = { workspace = true }
27+
hex-literal = { workspace = true }
2728
impl-trait-for-tuples = { workspace = true }
2829
log = { workspace = true }
2930
paste = { workspace = true }
@@ -61,7 +62,6 @@ xcm-builder = { workspace = true }
6162
[dev-dependencies]
6263
array-bytes = { workspace = true, default-features = true }
6364
assert_matches = { workspace = true }
64-
hex-literal = { workspace = true }
6565
pretty_assertions = { workspace = true }
6666
secp256k1 = { workspace = true, features = ["recovery"] }
6767
serde_json = { workspace = true }
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
//! This calls another contract as passed as its account id.
19+
#![no_std]
20+
#![no_main]
21+
22+
use common::{input, u256_bytes};
23+
use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode, ReturnFlags};
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+
value: u64,
36+
callee_input: [u8],
37+
);
38+
39+
// Call the callee
40+
let mut output = [0u8; 32];
41+
let output = &mut &mut output[..];
42+
43+
match api::call(
44+
uapi::CallFlags::empty(),
45+
callee_addr,
46+
u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all.
47+
u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all.
48+
&[u8::MAX; 32], // No deposit limit.
49+
&u256_bytes(value), // Value transferred to the contract.
50+
callee_input,
51+
Some(output),
52+
) {
53+
Ok(_) => api::return_value(uapi::ReturnFlags::empty(), output),
54+
Err(ReturnErrorCode::CalleeReverted) => api::return_value(ReturnFlags::REVERT, output),
55+
Err(_) => panic!(),
56+
}
57+
}

substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs

Lines changed: 0 additions & 44 deletions
This file was deleted.

substrate/frame/revive/src/benchmarking/mod.rs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::{
2626
evm::runtime::GAS_PRICE,
2727
exec::{Ext, Key, MomentOf},
2828
limits,
29+
pure_precompiles::Precompile,
2930
storage::WriteOutcome,
3031
ConversionPrecision, Pallet as Contracts, *,
3132
};
@@ -1945,30 +1946,21 @@ mod benchmarks {
19451946
}
19461947

19471948
#[benchmark(pov_mode = Measured)]
1948-
fn seal_ecdsa_recover() {
1949-
let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes());
1950-
let key_type = sp_core::crypto::KeyTypeId(*b"code");
1951-
let signature = {
1952-
let pub_key = sp_io::crypto::ecdsa_generate(key_type, None);
1953-
let sig = sp_io::crypto::ecdsa_sign_prehashed(key_type, &pub_key, &message_hash)
1954-
.expect("Generates signature");
1955-
AsRef::<[u8; 65]>::as_ref(&sig).to_vec()
1956-
};
1957-
1958-
build_runtime!(runtime, memory: [signature, message_hash, [0u8; 33], ]);
1949+
fn ecdsa_recover() {
1950+
use hex_literal::hex;
1951+
let input = hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549");
1952+
let expected = hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b");
1953+
let mut call_setup = CallSetup::<T>::default();
1954+
let (mut ext, _) = call_setup.ext();
19591955

19601956
let result;
1957+
19611958
#[block]
19621959
{
1963-
result = runtime.bench_ecdsa_recover(
1964-
memory.as_mut_slice(),
1965-
0, // signature_ptr
1966-
65, // message_hash_ptr
1967-
65 + 32, // output_ptr
1968-
);
1960+
result = pure_precompiles::ECRecover::execute(ext.gas_meter_mut(), &input);
19691961
}
19701962

1971-
assert_eq!(result.unwrap(), ReturnErrorCode::Success);
1963+
assert_eq!(result.unwrap().data, expected);
19721964
}
19731965

19741966
// Only calling the function itself for the list of

substrate/frame/revive/src/exec.rs

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::{
2020
gas::GasMeter,
2121
limits,
2222
primitives::{ExecReturnValue, StorageDeposit},
23+
pure_precompiles::{self, is_precompile},
2324
runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo},
2425
storage::{self, meter::Diff, WriteOutcome},
2526
tracing::if_tracing,
@@ -1372,13 +1373,80 @@ where
13721373
}
13731374
Some(System::<T>::block_hash(&block_number).into())
13741375
}
1375-
}
13761376

1377-
/// Determine if the given address is a precompile.
1378-
/// For now, we consider that all addresses between 0x1 and 0xff are reserved for precompiles.
1379-
fn is_precompile(address: &H160) -> bool {
1380-
let bytes = address.as_bytes();
1381-
bytes.starts_with(&[0u8; 19]) && bytes[19] != 0
1377+
fn run_precompile(
1378+
&mut self,
1379+
precompile_address: H160,
1380+
is_delegate: bool,
1381+
is_read_only: bool,
1382+
value_transferred: U256,
1383+
input_data: &[u8],
1384+
) -> Result<(), ExecError> {
1385+
if_tracing(|tracer| {
1386+
tracer.enter_child_span(
1387+
self.caller().account_id().map(T::AddressMapper::to_address).unwrap_or_default(),
1388+
precompile_address,
1389+
is_delegate,
1390+
is_read_only,
1391+
value_transferred,
1392+
&input_data,
1393+
self.gas_meter().gas_left(),
1394+
);
1395+
});
1396+
1397+
let mut do_transaction = || -> ExecResult {
1398+
if !is_delegate {
1399+
Self::transfer_from_origin(
1400+
&self.origin,
1401+
&self.caller(),
1402+
&T::AddressMapper::to_fallback_account_id(&precompile_address),
1403+
value_transferred,
1404+
)?;
1405+
}
1406+
1407+
pure_precompiles::Precompiles::<T>::execute(
1408+
precompile_address,
1409+
self.gas_meter_mut(),
1410+
input_data,
1411+
)
1412+
.map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })
1413+
};
1414+
1415+
let transaction_outcome =
1416+
with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
1417+
let output = do_transaction();
1418+
match &output {
1419+
Ok(result) if !result.did_revert() => TransactionOutcome::Commit(Ok(output)),
1420+
_ => TransactionOutcome::Rollback(Ok(output)),
1421+
}
1422+
});
1423+
1424+
let output = match transaction_outcome {
1425+
Ok(output) => {
1426+
if_tracing(|tracer| {
1427+
let gas_consumed = top_frame!(self).nested_gas.gas_consumed();
1428+
match &output {
1429+
Ok(output) => tracer.exit_child_span(&output, gas_consumed),
1430+
Err(e) => tracer.exit_child_span_with_error(e.error.into(), gas_consumed),
1431+
}
1432+
});
1433+
1434+
output
1435+
},
1436+
Err(error) => {
1437+
if_tracing(|tracer| {
1438+
let gas_consumed = top_frame!(self).nested_gas.gas_consumed();
1439+
tracer.exit_child_span_with_error(error.into(), gas_consumed);
1440+
});
1441+
1442+
Err(error.into())
1443+
},
1444+
};
1445+
1446+
output.map(|output| {
1447+
self.top_frame_mut().last_frame_output = output;
1448+
})
1449+
}
13821450
}
13831451

13841452
impl<'a, T, E> Ext for Stack<'a, T, E>
@@ -1411,9 +1479,11 @@ where
14111479
*self.last_frame_output_mut() = Default::default();
14121480

14131481
let try_call = || {
1482+
// Enable read-only access if requested; cannot disable it if already set.
1483+
let is_read_only = read_only || self.is_read_only();
1484+
14141485
if is_precompile(dest_addr) {
1415-
log::debug!(target: crate::LOG_TARGET, "Unsupported precompile address {dest_addr:?}");
1416-
return Err(Error::<T>::UnsupportedPrecompileAddress.into());
1486+
return self.run_precompile(*dest_addr, false, is_read_only, value, &input_data);
14171487
}
14181488

14191489
let dest = T::AddressMapper::to_account_id(dest_addr);
@@ -1434,9 +1504,6 @@ where
14341504
_ => None,
14351505
});
14361506

1437-
// Enable read-only access if requested; cannot disable it if already set.
1438-
let is_read_only = read_only || self.is_read_only();
1439-
14401507
if let Some(executable) = self.push_frame(
14411508
FrameArgs::Call { dest: dest.clone(), cached_info, delegated_call: None },
14421509
value,
@@ -1494,6 +1561,16 @@ where
14941561
address: H160,
14951562
input_data: Vec<u8>,
14961563
) -> Result<(), ExecError> {
1564+
if is_precompile(&address) {
1565+
return self.run_precompile(
1566+
address,
1567+
true,
1568+
self.is_read_only(),
1569+
0u32.into(),
1570+
&input_data,
1571+
);
1572+
}
1573+
14971574
// We reset the return data now, so it is cleared out even if no new frame was executed.
14981575
// This is for example the case for unknown code hashes or creating the frame fails.
14991576
*self.last_frame_output_mut() = Default::default();

substrate/frame/revive/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod exec;
2727
mod gas;
2828
mod limits;
2929
mod primitives;
30+
mod pure_precompiles;
3031
mod storage;
3132
mod transient_storage;
3233
mod wasm;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
use crate::{exec::ExecResult, Config, Error, GasMeter, H160};
19+
20+
mod ecrecover;
21+
pub use ecrecover::*;
22+
23+
/// Determine if the given address is a precompile.
24+
/// For now, we consider that all addresses between 0x1 and 0xff are reserved for precompiles.
25+
pub fn is_precompile(address: &H160) -> bool {
26+
let bytes = address.as_bytes();
27+
bytes.starts_with(&[0u8; 19]) && bytes[19] != 0
28+
}
29+
30+
/// The `Precompile` trait defines the functionality for executing a precompiled contract.
31+
pub trait Precompile<T: Config> {
32+
/// Executes the precompile with the provided input data.
33+
fn execute(gas_meter: &mut GasMeter<T>, input: &[u8]) -> ExecResult;
34+
}
35+
36+
pub struct Precompiles<T: Config> {
37+
_phantom: core::marker::PhantomData<T>,
38+
}
39+
40+
impl<T: Config> Precompiles<T> {
41+
pub fn execute(addr: H160, gas_meter: &mut GasMeter<T>, input: &[u8]) -> ExecResult {
42+
if addr == ECRECOVER {
43+
ECRecover::execute(gas_meter, input)
44+
} else {
45+
Err(Error::<T>::UnsupportedPrecompileAddress.into())
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)