diff --git a/prdoc/pr_7652.prdoc b/prdoc/pr_7652.prdoc new file mode 100644 index 0000000000000..b56b4bb8fcc89 --- /dev/null +++ b/prdoc/pr_7652.prdoc @@ -0,0 +1,16 @@ +title: '[pallet-revive] ecrecover' +doc: +- audience: Runtime Dev + description: |- + Add ECrecover 0x1 precompile and remove the unstable equivalent host function. +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 77f0f7eb1e6dd..a10f8935fb60d 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -24,6 +24,7 @@ environmental = { workspace = true } ethabi = { workspace = true } ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] } hex = { workspace = true } +hex-literal = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } paste = { workspace = true } @@ -61,7 +62,6 @@ xcm-builder = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } -hex-literal = { workspace = true } pretty_assertions = { workspace = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } diff --git a/substrate/frame/revive/fixtures/contracts/call_and_return.rs b/substrate/frame/revive/fixtures/contracts/call_and_return.rs new file mode 100644 index 0000000000000..3fc16fc16706b --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_and_return.rs @@ -0,0 +1,57 @@ +// 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. + +//! This calls another contract as passed as its account id. +#![no_std] +#![no_main] + +use common::{input, u256_bytes}; +use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + callee_addr: &[u8; 20], + value: u64, + callee_input: [u8], + ); + + // Call the callee + let mut output = [0u8; 32]; + let output = &mut &mut output[..]; + + match api::call( + uapi::CallFlags::empty(), + callee_addr, + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &u256_bytes(value), // Value transferred to the contract. + callee_input, + Some(output), + ) { + Ok(_) => api::return_value(uapi::ReturnFlags::empty(), output), + Err(ReturnErrorCode::CalleeReverted) => api::return_value(ReturnFlags::REVERT, output), + Err(_) => panic!(), + } +} diff --git a/substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs b/substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs deleted file mode 100644 index 0f28ca2c81980..0000000000000 --- a/substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs +++ /dev/null @@ -1,44 +0,0 @@ -// 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] - -use common::input; -use uapi::{HostFn, HostFnImpl as api}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - input!( - signature: [u8; 65], - hash: [u8; 32], - ); - - let mut output = [0u8; 33]; - api::ecdsa_recover( - &signature[..].try_into().unwrap(), - &hash[..].try_into().unwrap(), - &mut output, - ) - .unwrap(); - api::return_value(uapi::ReturnFlags::empty(), &output); -} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index f72b9f206c84c..074b8607128d4 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -26,6 +26,7 @@ use crate::{ evm::runtime::GAS_PRICE, exec::{Ext, Key, MomentOf}, limits, + pure_precompiles::Precompile, storage::WriteOutcome, ConversionPrecision, Pallet as Contracts, *, }; @@ -1945,30 +1946,21 @@ mod benchmarks { } #[benchmark(pov_mode = Measured)] - fn seal_ecdsa_recover() { - let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); - let key_type = sp_core::crypto::KeyTypeId(*b"code"); - let signature = { - let pub_key = sp_io::crypto::ecdsa_generate(key_type, None); - let sig = sp_io::crypto::ecdsa_sign_prehashed(key_type, &pub_key, &message_hash) - .expect("Generates signature"); - AsRef::<[u8; 65]>::as_ref(&sig).to_vec() - }; - - build_runtime!(runtime, memory: [signature, message_hash, [0u8; 33], ]); + fn ecdsa_recover() { + use hex_literal::hex; + let input = hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"); + let expected = hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"); + let mut call_setup = CallSetup::::default(); + let (mut ext, _) = call_setup.ext(); let result; + #[block] { - result = runtime.bench_ecdsa_recover( - memory.as_mut_slice(), - 0, // signature_ptr - 65, // message_hash_ptr - 65 + 32, // output_ptr - ); + result = pure_precompiles::ECRecover::execute(ext.gas_meter_mut(), &input); } - assert_eq!(result.unwrap(), ReturnErrorCode::Success); + assert_eq!(result.unwrap().data, expected); } // Only calling the function itself for the list of diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 721254180bf5d..2fe5e7cb19bcb 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -20,6 +20,7 @@ use crate::{ gas::GasMeter, limits, primitives::{ExecReturnValue, StorageDeposit}, + pure_precompiles::{self, is_precompile}, runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, storage::{self, meter::Diff, WriteOutcome}, tracing::if_tracing, @@ -1015,10 +1016,21 @@ where fn run(&mut self, executable: E, input_data: Vec) -> Result<(), ExecError> { let frame = self.top_frame(); let entry_point = frame.entry_point; - let is_delegate_call = frame.delegate.is_some(); let delegated_code_hash = if frame.delegate.is_some() { Some(*executable.code_hash()) } else { None }; + if_tracing(|tracer| { + tracer.enter_child_span( + self.caller().account_id().map(T::AddressMapper::to_address).unwrap_or_default(), + T::AddressMapper::to_address(&frame.account_id), + frame.delegate.is_some(), + frame.read_only, + frame.value_transferred, + &input_data, + frame.nested_gas.gas_left(), + ); + }); + // The output of the caller frame will be replaced by the output of this run. // It is also not accessible from nested frames. // Hence we drop it early to save the memory. @@ -1036,8 +1048,6 @@ where let do_transaction = || -> ExecResult { let caller = self.caller(); let frame = top_frame_mut!(self); - let read_only = frame.read_only; - let value_transferred = frame.value_transferred; let account_id = &frame.account_id.clone(); // We need to make sure that the contract's account exists before calling its @@ -1081,35 +1091,10 @@ where )?; } - let contract_address = T::AddressMapper::to_address(account_id); - let maybe_caller_address = caller.account_id().map(T::AddressMapper::to_address); let code_deposit = executable.code_info().deposit(); - - if_tracing(|tracer| { - tracer.enter_child_span( - maybe_caller_address.unwrap_or_default(), - contract_address, - is_delegate_call, - read_only, - value_transferred, - &input_data, - frame.nested_gas.gas_left(), - ); - }); - - let output = executable.execute(self, entry_point, input_data).map_err(|e| { - if_tracing(|tracer| { - tracer.exit_child_span_with_error( - e.error, - top_frame_mut!(self).nested_gas.gas_consumed(), - ); - }); - ExecError { error: e.error, origin: ErrorOrigin::Callee } - })?; - - if_tracing(|tracer| { - tracer.exit_child_span(&output, top_frame_mut!(self).nested_gas.gas_consumed()); - }); + let output = executable + .execute(self, entry_point, input_data) + .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; // Avoid useless work that would be reverted anyways. if output.did_revert() { @@ -1157,10 +1142,27 @@ where let (success, output) = match transaction_outcome { // `with_transactional` executed successfully, and we have the expected output. - Ok((success, output)) => (success, output), + Ok((success, output)) => { + if_tracing(|tracer| { + let gas_consumed = top_frame!(self).nested_gas.gas_consumed(); + match &output { + Ok(output) => tracer.exit_child_span(&output, gas_consumed), + Err(e) => tracer.exit_child_span_with_error(e.error.into(), gas_consumed), + } + }); + + (success, output) + }, // `with_transactional` returned an error, and we propagate that error and note no state // has changed. - Err(error) => (false, Err(error.into())), + Err(error) => { + if_tracing(|tracer| { + let gas_consumed = top_frame!(self).nested_gas.gas_consumed(); + tracer.exit_child_span_with_error(error.into(), gas_consumed); + }); + + (false, Err(error.into())) + }, }; if success { @@ -1371,13 +1373,80 @@ where } Some(System::::block_hash(&block_number).into()) } -} -/// Determine if the given address is a precompile. -/// For now, we consider that all addresses between 0x1 and 0xff are reserved for precompiles. -fn is_precompile(address: &H160) -> bool { - let bytes = address.as_bytes(); - bytes.starts_with(&[0u8; 19]) && bytes[19] != 0 + fn run_precompile( + &mut self, + precompile_address: H160, + is_delegate: bool, + is_read_only: bool, + value_transferred: U256, + input_data: &[u8], + ) -> Result<(), ExecError> { + if_tracing(|tracer| { + tracer.enter_child_span( + self.caller().account_id().map(T::AddressMapper::to_address).unwrap_or_default(), + precompile_address, + is_delegate, + is_read_only, + value_transferred, + &input_data, + self.gas_meter().gas_left(), + ); + }); + + let mut do_transaction = || -> ExecResult { + if !is_delegate { + Self::transfer_from_origin( + &self.origin, + &self.caller(), + &T::AddressMapper::to_fallback_account_id(&precompile_address), + value_transferred, + )?; + } + + pure_precompiles::Precompiles::::execute( + precompile_address, + self.gas_meter_mut(), + input_data, + ) + .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee }) + }; + + let transaction_outcome = + with_transaction(|| -> TransactionOutcome> { + let output = do_transaction(); + match &output { + Ok(result) if !result.did_revert() => TransactionOutcome::Commit(Ok(output)), + _ => TransactionOutcome::Rollback(Ok(output)), + } + }); + + let output = match transaction_outcome { + Ok(output) => { + if_tracing(|tracer| { + let gas_consumed = top_frame!(self).nested_gas.gas_consumed(); + match &output { + Ok(output) => tracer.exit_child_span(&output, gas_consumed), + Err(e) => tracer.exit_child_span_with_error(e.error.into(), gas_consumed), + } + }); + + output + }, + Err(error) => { + if_tracing(|tracer| { + let gas_consumed = top_frame!(self).nested_gas.gas_consumed(); + tracer.exit_child_span_with_error(error.into(), gas_consumed); + }); + + Err(error.into()) + }, + }; + + output.map(|output| { + self.top_frame_mut().last_frame_output = output; + }) + } } impl<'a, T, E> Ext for Stack<'a, T, E> @@ -1410,9 +1479,11 @@ where *self.last_frame_output_mut() = Default::default(); let try_call = || { + // Enable read-only access if requested; cannot disable it if already set. + let is_read_only = read_only || self.is_read_only(); + if is_precompile(dest_addr) { - log::debug!(target: crate::LOG_TARGET, "Unsupported precompile address {dest_addr:?}"); - return Err(Error::::UnsupportedPrecompileAddress.into()); + return self.run_precompile(*dest_addr, false, is_read_only, value, &input_data); } let dest = T::AddressMapper::to_account_id(dest_addr); @@ -1433,9 +1504,6 @@ where _ => None, }); - // Enable read-only access if requested; cannot disable it if already set. - let is_read_only = read_only || self.is_read_only(); - if let Some(executable) = self.push_frame( FrameArgs::Call { dest: dest.clone(), cached_info, delegated_call: None }, value, @@ -1493,6 +1561,16 @@ where address: H160, input_data: Vec, ) -> Result<(), ExecError> { + if is_precompile(&address) { + return self.run_precompile( + address, + true, + self.is_read_only(), + 0u32.into(), + &input_data, + ); + } + // We reset the return data now, so it is cleared out even if no new frame was executed. // This is for example the case for unknown code hashes or creating the frame fails. *self.last_frame_output_mut() = Default::default(); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index b1851ebe41f09..62633a8f30b80 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -27,6 +27,7 @@ mod exec; mod gas; mod limits; mod primitives; +mod pure_precompiles; mod storage; mod transient_storage; mod wasm; diff --git a/substrate/frame/revive/src/pure_precompiles.rs b/substrate/frame/revive/src/pure_precompiles.rs new file mode 100644 index 0000000000000..8882b7dc44064 --- /dev/null +++ b/substrate/frame/revive/src/pure_precompiles.rs @@ -0,0 +1,48 @@ +// 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. + +use crate::{exec::ExecResult, Config, Error, GasMeter, H160}; + +mod ecrecover; +pub use ecrecover::*; + +/// Determine if the given address is a precompile. +/// For now, we consider that all addresses between 0x1 and 0xff are reserved for precompiles. +pub fn is_precompile(address: &H160) -> bool { + let bytes = address.as_bytes(); + bytes.starts_with(&[0u8; 19]) && bytes[19] != 0 +} + +/// The `Precompile` trait defines the functionality for executing a precompiled contract. +pub trait Precompile { + /// Executes the precompile with the provided input data. + fn execute(gas_meter: &mut GasMeter, input: &[u8]) -> ExecResult; +} + +pub struct Precompiles { + _phantom: core::marker::PhantomData, +} + +impl Precompiles { + pub fn execute(addr: H160, gas_meter: &mut GasMeter, input: &[u8]) -> ExecResult { + if addr == ECRECOVER { + ECRecover::execute(gas_meter, input) + } else { + Err(Error::::UnsupportedPrecompileAddress.into()) + } + } +} diff --git a/substrate/frame/revive/src/pure_precompiles/ecrecover.rs b/substrate/frame/revive/src/pure_precompiles/ecrecover.rs new file mode 100644 index 0000000000000..8fde0fb40d2f7 --- /dev/null +++ b/substrate/frame/revive/src/pure_precompiles/ecrecover.rs @@ -0,0 +1,60 @@ +// 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. +use super::Precompile; +use crate::{exec::ExecResult, Config, ExecReturnValue, GasMeter, RuntimeCosts}; +use hex_literal::hex; +use pallet_revive_uapi::ReturnFlags; +use sp_core::H160; +pub const ECRECOVER: H160 = H160(hex!("0000000000000000000000000000000000000001")); + +/// The ecrecover precompile. +pub struct ECRecover; + +impl Precompile for ECRecover { + fn execute(gas_meter: &mut GasMeter, i: &[u8]) -> ExecResult { + gas_meter.charge(RuntimeCosts::EcdsaRecovery)?; + + let mut input = [0u8; 128]; + let len = i.len().min(128); + input[..len].copy_from_slice(&i[..len]); + + let mut msg = [0u8; 32]; + let mut sig = [0u8; 65]; + + msg[0..32].copy_from_slice(&input[0..32]); + sig[0..32].copy_from_slice(&input[64..96]); // r + sig[32..64].copy_from_slice(&input[96..128]); // s + sig[64] = input[63]; // v + + // v can only be 27 or 28 on the full 32 bytes value. + // https://github.com/ethereum/go-ethereum/blob/a907d7e81aaeea15d80b2d3209ad8e08e3bf49e0/core/vm/contracts.go#L177 + if input[32..63] != [0u8; 31] || ![27, 28].contains(&input[63]) { + return Ok(ExecReturnValue { data: [0u8; 0].to_vec(), flags: ReturnFlags::empty() }); + } + + let data = match sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) { + Ok(pubkey) => { + let mut address = sp_io::hashing::keccak_256(&pubkey); + address[0..12].copy_from_slice(&[0u8; 12]); + address.to_vec() + }, + Err(_) => [0u8; 0].to_vec(), + }; + + Ok(ExecReturnValue { data, flags: ReturnFlags::empty() }) + } +} diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index bda47030b23bb..a20b6d0c63056 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -27,7 +27,7 @@ use crate::{ }, evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction}, exec::Key, - limits, + limits, pure_precompiles, storage::DeletionQueueManager, test_utils::*, tests::test_utils::{get_contract, get_contract_checked}, @@ -2304,47 +2304,6 @@ fn call_runtime_reentrancy_guarded() { }); } -#[test] -fn ecdsa_recover() { - let (wasm, _code_hash) = compile_module("ecdsa_recover").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the ecdsa_recover contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(100_000) - .build_and_unwrap_contract(); - - #[rustfmt::skip] - let signature: [u8; 65] = [ - 161, 234, 203, 74, 147, 96, 51, 212, 5, 174, 231, 9, 142, 48, 137, 201, - 162, 118, 192, 67, 239, 16, 71, 216, 125, 86, 167, 139, 70, 7, 86, 241, - 33, 87, 154, 251, 81, 29, 160, 4, 176, 239, 88, 211, 244, 232, 232, 52, - 211, 234, 100, 115, 230, 47, 80, 44, 152, 166, 62, 50, 8, 13, 86, 175, - 28, - ]; - #[rustfmt::skip] - let message_hash: [u8; 32] = [ - 162, 28, 244, 179, 96, 76, 244, 178, 188, 83, 230, 248, 143, 106, 77, 117, - 239, 95, 244, 171, 65, 95, 62, 153, 174, 166, 182, 28, 130, 73, 196, 208 - ]; - #[rustfmt::skip] - const EXPECTED_COMPRESSED_PUBLIC_KEY: [u8; 33] = [ - 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, - 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, - 152, - ]; - let mut params = vec![]; - params.extend_from_slice(&signature); - params.extend_from_slice(&message_hash); - assert!(params.len() == 65 + 32); - let result = builder::bare_call(addr).data(params).build_and_unwrap_result(); - assert!(!result.did_revert()); - assert_eq!(result.data, EXPECTED_COMPRESSED_PUBLIC_KEY); - }) -} - #[test] fn sr25519_verify() { let (wasm, _code_hash) = compile_module("sr25519_verify").unwrap(); @@ -4601,15 +4560,15 @@ fn unknown_precompiles_revert() { let cases: Vec<(H160, Box)> = vec![ ( - H160::from_low_u64_be(0x1), + H160::from_low_u64_be(0x2), Box::new(|result| { - assert_err!(result, >::UnsupportedPrecompileAddress); + assert_err!(result, >::ContractTrapped); }), ), ( H160::from_low_u64_be(0xff), Box::new(|result| { - assert_err!(result, >::UnsupportedPrecompileAddress); + assert_err!(result, >::ContractTrapped); }), ), ( @@ -4627,3 +4586,45 @@ fn unknown_precompiles_revert() { } }); } + +#[test] +fn ecrecover_precompile_works() { + use hex_literal::hex; + + let cases = vec![ + ( + hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"), + hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b").to_vec(), + ), + ( + hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000000173b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"), + [0u8; 0].to_vec(), + ), + ]; + + for (input, output) in cases { + let (code, _code_hash) = compile_module("call_and_return").unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .value(1000) + .build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data((pure_precompiles::ECRECOVER, 100u64, input).encode()) + .build_and_unwrap_result(); + + test_utils::get_balance(&::AddressMapper::to_account_id( + &pure_precompiles::ECRECOVER, + )); + assert_eq!( + test_utils::get_balance(&::AddressMapper::to_account_id( + &pure_precompiles::ECRECOVER + )), + 101u64 + ); + assert_eq!(result.data, output); + assert_eq!(result.flags, ReturnFlags::empty()); + }); + } +} diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index b15d461c62f27..6b4fab0f1358f 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -1012,9 +1012,12 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { output_ptr: u32, output_len_ptr: u32, ) -> Result { - self.charge_gas(call_type.cost())?; - let callee = memory.read_h160(callee_ptr)?; + + if !crate::pure_precompiles::is_precompile(&callee) { + self.charge_gas(call_type.cost())?; + } + let deposit_limit = memory.read_u256(deposit_ptr)?; let input_data = if flags.contains(CallFlags::CLONE_INPUT) { @@ -1862,36 +1865,6 @@ pub mod env { self.contains_storage(memory, flags, key_ptr, key_len) } - /// Recovers the ECDSA public key from the given message hash and signature. - /// See [`pallet_revive_uapi::HostFn::ecdsa_recover`]. - fn ecdsa_recover( - &mut self, - memory: &mut M, - signature_ptr: u32, - message_hash_ptr: u32, - output_ptr: u32, - ) -> Result { - self.charge_gas(RuntimeCosts::EcdsaRecovery)?; - - let mut signature: [u8; 65] = [0; 65]; - memory.read_into_buf(signature_ptr, &mut signature)?; - let mut message_hash: [u8; 32] = [0; 32]; - memory.read_into_buf(message_hash_ptr, &mut message_hash)?; - - let result = self.ext.ecdsa_recover(&signature, &message_hash); - - match result { - Ok(pub_key) => { - // Write the recovered compressed ecdsa public key back into the sandboxed output - // buffer. - memory.write(output_ptr, pub_key.as_ref())?; - - Ok(ReturnErrorCode::Success) - }, - Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed), - } - } - /// 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/host.rs b/substrate/frame/revive/uapi/src/host.rs index 8e14eefc63645..3b0432d93005f 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -533,27 +533,6 @@ pub trait HostFn: private::Sealed { #[unstable_hostfn] fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option; - /// Recovers the ECDSA public key from the given message hash and signature. - /// - /// Writes the public key into the given output buffer. - /// Assumes the secp256k1 curve. - /// - /// # Parameters - /// - /// - `signature`: The signature bytes. - /// - `message_hash`: The message hash bytes. - /// - `output`: A reference to the output data buffer to write the public key. - /// - /// # Errors - /// - /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] - #[unstable_hostfn] - fn ecdsa_recover( - signature: &[u8; 65], - message_hash: &[u8; 32], - output: &mut [u8; 33], - ) -> Result; - /// 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 588579dc83ebf..23b69bd4ca90f 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -133,11 +133,6 @@ mod sys { out_len_ptr: *mut u32, ) -> ReturnCode; pub fn call_runtime(call_ptr: *const u8, call_len: u32) -> ReturnCode; - pub fn ecdsa_recover( - signature_ptr: *const u8, - message_hash_ptr: *const u8, - out_ptr: *mut u8, - ) -> ReturnCode; pub fn sr25519_verify( signature_ptr: *const u8, pub_key_ptr: *const u8, @@ -510,18 +505,6 @@ impl HostFn for HostFnImpl { ret_code.into() } - #[unstable_hostfn] - fn ecdsa_recover( - signature: &[u8; 65], - message_hash: &[u8; 32], - output: &mut [u8; 33], - ) -> Result { - let ret_code = unsafe { - sys::ecdsa_recover(signature.as_ptr(), message_hash.as_ptr(), output.as_mut_ptr()) - }; - ret_code.into() - } - #[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()) };