From 86019edba891fee6b5c008f0f5d0b5d782e42d2d Mon Sep 17 00:00:00 2001
From: PG Herveou <pgherveou@gmail.com>
Date: Wed, 26 Feb 2025 16:32:49 +0100
Subject: [PATCH] [pallet-revive] ecrecover (#7652)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add ECrecover 0x1 precompile and remove the unstable equivalent host
function.

- depend on https://github.com/paritytech/polkadot-sdk/pull/7676

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
---
 prdoc/pr_7652.prdoc                           | 16 +++
 substrate/frame/revive/Cargo.toml             |  2 +-
 .../fixtures/contracts/call_and_return.rs     | 57 +++++++++++
 .../fixtures/contracts/ecdsa_recover.rs       | 44 ---------
 .../frame/revive/src/benchmarking/mod.rs      | 28 ++----
 substrate/frame/revive/src/exec.rs            | 99 ++++++++++++++++---
 substrate/frame/revive/src/lib.rs             |  1 +
 .../frame/revive/src/pure_precompiles.rs      | 48 +++++++++
 .../revive/src/pure_precompiles/ecrecover.rs  | 60 +++++++++++
 substrate/frame/revive/src/tests.rs           | 91 ++++++++---------
 substrate/frame/revive/src/wasm/runtime.rs    | 44 +++------
 substrate/frame/revive/uapi/src/host.rs       | 21 ----
 .../frame/revive/uapi/src/host/riscv64.rs     | 17 ----
 13 files changed, 339 insertions(+), 189 deletions(-)
 create mode 100644 prdoc/pr_7652.prdoc
 create mode 100644 substrate/frame/revive/fixtures/contracts/call_and_return.rs
 delete mode 100644 substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs
 create mode 100644 substrate/frame/revive/src/pure_precompiles.rs
 create mode 100644 substrate/frame/revive/src/pure_precompiles/ecrecover.rs

diff --git a/prdoc/pr_7652.prdoc b/prdoc/pr_7652.prdoc
new file mode 100644
index 00000000000..b56b4bb8fcc
--- /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 77f0f7eb1e6..a10f8935fb6 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 00000000000..3fc16fc1670
--- /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 0f28ca2c819..00000000000
--- 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 f72b9f206c8..074b8607128 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::<T>::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 e13212f5e5e..2fe5e7cb19b 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,
@@ -1372,13 +1373,80 @@ where
 		}
 		Some(System::<T>::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::<T>::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<Result<_, DispatchError>> {
+				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>
@@ -1411,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::<T>::UnsupportedPrecompileAddress.into());
+				return self.run_precompile(*dest_addr, false, is_read_only, value, &input_data);
 			}
 
 			let dest = T::AddressMapper::to_account_id(dest_addr);
@@ -1434,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,
@@ -1494,6 +1561,16 @@ where
 		address: H160,
 		input_data: Vec<u8>,
 	) -> 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 b1851ebe41f..62633a8f30b 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 00000000000..8882b7dc440
--- /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<T: Config> {
+	/// Executes the precompile with the provided input data.
+	fn execute(gas_meter: &mut GasMeter<T>, input: &[u8]) -> ExecResult;
+}
+
+pub struct Precompiles<T: Config> {
+	_phantom: core::marker::PhantomData<T>,
+}
+
+impl<T: Config> Precompiles<T> {
+	pub fn execute(addr: H160, gas_meter: &mut GasMeter<T>, input: &[u8]) -> ExecResult {
+		if addr == ECRECOVER {
+			ECRecover::execute(gas_meter, input)
+		} else {
+			Err(Error::<T>::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 00000000000..8fde0fb40d2
--- /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<T: Config> Precompile<T> for ECRecover {
+	fn execute(gas_meter: &mut GasMeter<T>, 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 bda47030b23..a20b6d0c630 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 _ = <Test as Config>::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<dyn FnOnce(_)>)> = vec![
 			(
-				H160::from_low_u64_be(0x1),
+				H160::from_low_u64_be(0x2),
 				Box::new(|result| {
-					assert_err!(result, <Error<Test>>::UnsupportedPrecompileAddress);
+					assert_err!(result, <Error<Test>>::ContractTrapped);
 				}),
 			),
 			(
 				H160::from_low_u64_be(0xff),
 				Box::new(|result| {
-					assert_err!(result, <Error<Test>>::UnsupportedPrecompileAddress);
+					assert_err!(result, <Error<Test>>::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 _ = <Test as Config>::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(&<Test as Config>::AddressMapper::to_account_id(
+				&pure_precompiles::ECRECOVER,
+			));
+			assert_eq!(
+				test_utils::get_balance(&<Test as Config>::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 b15d461c62f..5e4c29f289c 100644
--- a/substrate/frame/revive/src/wasm/runtime.rs
+++ b/substrate/frame/revive/src/wasm/runtime.rs
@@ -24,6 +24,7 @@ use crate::{
 	gas::{ChargedAmount, Token},
 	limits,
 	primitives::ExecReturnValue,
+	pure_precompiles::is_precompile,
 	weights::WeightInfo,
 	Config, Error, LOG_TARGET, SENTINEL,
 };
@@ -1012,9 +1013,18 @@ impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> {
 		output_ptr: u32,
 		output_len_ptr: u32,
 	) -> Result<ReturnErrorCode, TrapReason> {
-		self.charge_gas(call_type.cost())?;
+		let callee = match memory.read_h160(callee_ptr) {
+			Ok(callee) if is_precompile(&callee) => callee,
+			Ok(callee) => {
+				self.charge_gas(call_type.cost())?;
+				callee
+			},
+			Err(err) => {
+				self.charge_gas(call_type.cost())?;
+				return Err(err.into());
+			},
+		};
 
-		let callee = memory.read_h160(callee_ptr)?;
 		let deposit_limit = memory.read_u256(deposit_ptr)?;
 
 		let input_data = if flags.contains(CallFlags::CLONE_INPUT) {
@@ -1862,36 +1872,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<ReturnErrorCode, TrapReason> {
-		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 8e14eefc636..3b0432d9300 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<u32>;
 
-	/// 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 588579dc83e..23b69bd4ca9 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()) };
-- 
GitLab