referrerpolicy=no-referrer-when-downgrade

pallet_revive/
precompiles.rs

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//! Exposes types that can be used to extend `pallet_revive` with additional functionality.
19//!
20//! In order to add a pre-compile:
21//!
22//! - Implement [`Precompile`] on a type. Most likely another pallet.
23//! - Add the type to a tuple passed into [`Config::Precompiles`].
24//! - Use the types inside the `run` module to test and benchmark your pre-compile.
25//!
26//! Use `alloy` through our re-export in this module to implement Eth ABI.
27
28mod builtin;
29
30mod tests;
31
32pub use crate::{
33	AddressMapper, TransactionLimits,
34	access_list::{StorageOp, Warmth},
35	exec::{
36		ExecError, PrecompileExt as Ext, PrecompileWithInfoExt as ExtWithInfo, ReentrancyProtection,
37	},
38	metering::{Diff, Token},
39	vm::{RuntimeCosts, StorageAccessKind},
40};
41pub use alloy_core as alloy;
42pub use sp_core::{H160, H256, U256};
43
44use crate::{
45	Config, Error as CrateError, exec::ExecResult, precompiles::builtin::Builtin,
46	primitives::ExecReturnValue,
47};
48use alloc::vec::Vec;
49use alloy::sol_types::{Panic, PanicKind, Revert, SolError, SolInterface};
50use core::num::NonZero;
51use pallet_revive_uapi::ReturnFlags;
52use sp_runtime::DispatchError;
53
54#[cfg(feature = "runtime-benchmarks")]
55pub(crate) use builtin::{
56	IBenchmarking, NoInfo as BenchmarkNoInfo, Storage as BenchmarkStorage,
57	System as BenchmarkSystem, WithInfo as BenchmarkWithInfo,
58};
59
60const UNIMPLEMENTED: &str = "A precompile must either implement `call` or `call_with_info`";
61
62/// A minimal EVM bytecode to be returned when a pre-compile is queried for its code.
63pub(crate) const EVM_REVERT: [u8; 5] = sp_core::hex2array!("60006000fd");
64
65/// The composition of all available pre-compiles.
66///
67/// This is how the rest of the pallet discovers and calls pre-compiles.
68pub(crate) type All<T> = (Builtin<T>, <T as Config>::Precompiles);
69
70/// Used by [`Precompile`] in order to declare at which addresses it will be called.
71///
72/// The 2 byte integer supplied here will be interpreted as big endian and copied to
73/// `address[16,17]`. Address `address[18,19]` is reserved for builtin precompiles. All other
74/// bytes are set to zero.
75///
76/// Big endian is chosen because it lines up with how you would invoke a pre-compile in Solidity.
77/// For example writing `staticcall(..., 0x05, ...)` in Solidity sets the highest (`address[19]`)
78/// byte to `5`.
79pub enum AddressMatcher {
80	/// The pre-compile will only be called for a single address.
81	///
82	/// This means the precompile will only be invoked for:
83	/// ```ignore
84	/// 00000000000000000000000000000000pppp0000
85	/// ```
86	///
87	/// Where `p` is the `u16` defined here as big endian.
88	Fixed(NonZero<u16>),
89	/// The pre-compile will be called for multiple addresses.
90	///
91	/// This is useful when some information should be encoded into the address.
92	///
93	/// This means the precompile will be invoked for all `x`:
94	/// ```ignore
95	/// xxxxxxxx000000000000000000000000pppp0000
96	/// ```
97	///
98	/// Where `p` is the `u16` defined here as big endian. The first 4 bytes of the address are
99	/// unconstrained, so a maximum of 4 bytes can be encoded into it. Allowing more bytes could
100	/// lead to the situation where legitimate accounts could exist at this address. Either by
101	/// accident or on purpose.
102	Prefix(NonZero<u16>),
103}
104
105/// Same as `AddressMatcher` but for builtin pre-compiles.
106///
107/// It works in the same way as `AddressMatcher` but allows setting the full 4 byte prefix.
108/// Builtin pre-compiles must only use values `<= u16::MAX` to prevent collisions with
109/// external pre-compiles.
110pub(crate) enum BuiltinAddressMatcher {
111	Fixed(NonZero<u32>),
112	Prefix(NonZero<u32>),
113}
114
115/// A pre-compile can error in the same way that a real contract can.
116#[derive(derive_more::From, Debug, Eq, PartialEq)]
117pub enum Error {
118	/// This is the same as a contract writing `revert("I reverted")`.
119	///
120	/// Those are the errors that are commonly caught by Solidity try-catch blocks. Encodes
121	/// a string onto the output buffer.
122	Revert(Revert),
123	/// An error generated by Solidity itself.
124	///
125	/// Encodes an error code into the output buffer.
126	Panic(PanicKind),
127	/// Don't encode anything into the output buffer. Just trap.
128	///
129	/// Commonly used for out of gas or other resource errors.
130	Error(ExecError),
131}
132
133impl From<DispatchError> for Error {
134	fn from(error: DispatchError) -> Self {
135		Self::Error(error.into())
136	}
137}
138
139impl<T: Config> From<CrateError<T>> for Error {
140	fn from(error: CrateError<T>) -> Self {
141		Self::Error(DispatchError::from(error).into())
142	}
143}
144
145impl Error {
146	pub fn try_to_revert<T: Config>(e: DispatchError) -> Self {
147		let delegate_denied = CrateError::<T>::PrecompileDelegateDenied.into();
148		let construct = CrateError::<T>::TerminatedInConstructor.into();
149		let cannot_terminate_delegated = CrateError::<T>::CannotTerminateDelegatedAccount.into();
150		let message = match () {
151			_ if e == delegate_denied => "illegal to call this pre-compile via delegate call",
152			_ if e == construct => "terminate pre-compile cannot be called from the constructor",
153			_ if e == cannot_terminate_delegated => {
154				"cannot terminate an EIP-7702 delegated account via the terminate pre-compile"
155			},
156			_ => return e.into(),
157		};
158		Self::Revert(message.into())
159	}
160}
161
162/// Type that can be implemented in other crates to extend the list of pre-compiles.
163///
164/// Only implement exactly one function. Either `call` or `call_with_info`.
165///
166/// # Warning
167///
168/// Pre-compiles are unmetered code. Hence they have to charge an appropriate amount of weight
169/// themselves. Generally, their first line of code should be a call to `env.charge(weight)`.
170pub trait Precompile {
171	/// Your runtime.
172	type T: Config;
173	/// The Solidity ABI definition of this pre-compile.
174	///
175	/// Use the [`self::alloy::sol`] macro to define your interface using Solidity syntax.
176	/// The input the caller passes to the pre-compile will be validated and parsed
177	/// according to this interface.
178	///
179	/// Please note that the return value is not validated and it is the pre-compiles
180	/// duty to return the abi encoded bytes conformant with the interface here.
181	type Interface: SolInterface;
182	/// Defines at which addresses this pre-compile exists.
183	const MATCHER: AddressMatcher;
184	/// Defines whether this pre-compile needs a contract info data structure in storage.
185	///
186	/// Enabling it unlocks more APIs for the pre-compile to use. Only pre-compiles with a
187	/// fixed matcher can set this to true. This is enforced at compile time. Reason is that
188	/// contract info is per address and not per pre-compile. Too many contract info structures
189	/// and accounts would be created otherwise.
190	///
191	/// # When set to **true**
192	///
193	/// - An account will be created at the pre-compiles address when it is called for the first
194	///   time. The ed is minted.
195	/// - Contract info data structure will be created in storage on first call.
196	/// - Only `call_with_info` should be implemented. `call` is never called.
197	///
198	/// # When set to **false**
199	///
200	/// - No account or any other state will be created for the address.
201	/// - Only `call` should be implemented. `call_with_info` is never called.
202	///
203	/// # What to use
204	///
205	/// Should be set to false if the additional functionality is not needed. A pre-compile with
206	/// contract info will incur both a storage read and write to its contract metadata when called.
207	///
208	/// The contract info enables additional functionality:
209	/// - Storage deposits: Collect deposits from the origin rather than the caller. This makes it
210	///   easier for contracts to interact with the pre-compile as deposits
211	/// 	are paid by the transaction signer (just like gas). It also makes refunding easier.
212	/// - Contract storage: You can use the contracts key value child trie storage instead of
213	///   providing your own state.
214	/// 	The contract storage automatically takes care of deposits.
215	/// 	Providing your own storage and using pallet_revive to collect deposits is also possible,
216	/// though.
217	/// - Instantitation: Contract instantiation requires the instantiator to have an account. This
218	/// 	is because its nonce is used to derive the new contracts account id and child trie id.
219	///
220	/// Have a look at [`ExtWithInfo`] to learn about the additional APIs that a contract info
221	/// unlocks.
222	const HAS_CONTRACT_INFO: bool;
223
224	/// Entry point for your pre-compile when `HAS_CONTRACT_INFO = false`.
225	#[allow(unused_variables)]
226	fn call(
227		address: &[u8; 20],
228		input: &Self::Interface,
229		env: &mut impl Ext<T = Self::T>,
230	) -> Result<Vec<u8>, Error> {
231		unimplemented!("{UNIMPLEMENTED}")
232	}
233
234	/// Entry point for your pre-compile when `HAS_CONTRACT_INFO = true`.
235	#[allow(unused_variables)]
236	fn call_with_info(
237		address: &[u8; 20],
238		input: &Self::Interface,
239		env: &mut impl ExtWithInfo<T = Self::T>,
240	) -> Result<Vec<u8>, Error> {
241		unimplemented!("{UNIMPLEMENTED}")
242	}
243}
244
245/// Same as `Precompile` but meant to be used by builtin pre-compiles.
246///
247/// This enabled builtin precompiles to exist at the highest bits. Those are not
248/// available to external pre-compiles in order to avoid collisions.
249///
250/// Automatically implemented for all types that implement `Precompile`.
251pub(crate) trait BuiltinPrecompile {
252	type T: Config;
253	type Interface: SolInterface;
254	const MATCHER: BuiltinAddressMatcher;
255	const HAS_CONTRACT_INFO: bool;
256	const CODE: &[u8] = &EVM_REVERT;
257
258	fn call(
259		_address: &[u8; 20],
260		_input: &Self::Interface,
261		_env: &mut impl Ext<T = Self::T>,
262	) -> Result<Vec<u8>, Error> {
263		unimplemented!("{UNIMPLEMENTED}")
264	}
265
266	fn call_with_info(
267		_address: &[u8; 20],
268		_input: &Self::Interface,
269		_env: &mut impl ExtWithInfo<T = Self::T>,
270	) -> Result<Vec<u8>, Error> {
271		unimplemented!("{UNIMPLEMENTED}")
272	}
273}
274
275/// A low level pre-compile that does not use Solidity ABI.
276///
277/// It is used to implement the original Ethereum pre-compiles which do not
278/// use Solidity ABI but just encode inputs and outputs packed in memory.
279///
280/// Automatically implemented for all types that implement `BuiltinPrecompile`.
281/// By extension also automatically implemented for all types implementing `Precompile`.
282pub(crate) trait PrimitivePrecompile {
283	type T: Config;
284	const MATCHER: BuiltinAddressMatcher;
285	const HAS_CONTRACT_INFO: bool;
286	const CODE: &[u8] = &[];
287
288	fn call(
289		_address: &[u8; 20],
290		_input: Vec<u8>,
291		_env: &mut impl Ext<T = Self::T>,
292	) -> Result<Vec<u8>, Error> {
293		unimplemented!("{UNIMPLEMENTED}")
294	}
295
296	fn call_with_info(
297		_address: &[u8; 20],
298		_input: Vec<u8>,
299		_env: &mut impl ExtWithInfo<T = Self::T>,
300	) -> Result<Vec<u8>, Error> {
301		unimplemented!("{UNIMPLEMENTED}")
302	}
303}
304
305/// A pre-compile ready to be called.
306pub(crate) struct Instance<E> {
307	has_contract_info: bool,
308	address: [u8; 20],
309	/// This is the function inside `PrimitivePrecompile` at `address`.
310	function: fn(&[u8; 20], Vec<u8>, &mut E) -> Result<Vec<u8>, Error>,
311}
312
313impl<E> Instance<E> {
314	pub fn has_contract_info(&self) -> bool {
315		self.has_contract_info
316	}
317
318	pub fn call(&self, input: Vec<u8>, env: &mut E) -> ExecResult {
319		let result = (self.function)(&self.address, input, env);
320		match result {
321			Ok(data) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data }),
322			Err(Error::Revert(msg)) => {
323				Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: msg.abi_encode() })
324			},
325			Err(Error::Panic(kind)) => Ok(ExecReturnValue {
326				flags: ReturnFlags::REVERT,
327				data: Panic::from(kind).abi_encode(),
328			}),
329			Err(Error::Error(err)) => Err(err.into()),
330		}
331	}
332}
333
334/// A composition of pre-compiles.
335///
336/// Automatically implemented for tuples of types that implement any of the
337/// pre-compile traits.
338pub(crate) trait Precompiles<T: Config> {
339	/// Used to generate compile time error when multiple pre-compiles use the same matcher.
340	const CHECK_COLLISION: ();
341	/// Does any of the pre-compiles use the range reserved for external pre-compiles.
342	///
343	/// This is just used to generate a compile time error if `Builtin` is using the external
344	/// range by accident.
345	const USES_EXTERNAL_RANGE: bool;
346
347	/// Returns the code of the pre-compile.
348	///
349	/// Just used when queried by `EXTCODESIZE` or the RPC. It is just
350	/// a bogus code that is never executed. Returns None if no pre-compile
351	/// exists at the specified address.
352	fn code(address: &[u8; 20]) -> Option<&'static [u8]>;
353
354	/// Get a reference to a specific pre-compile.
355	///
356	/// Returns `None` if no pre-compile exists at `address`.
357	fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>>;
358}
359
360impl<P: Precompile> BuiltinPrecompile for P {
361	type T = <Self as Precompile>::T;
362	type Interface = <Self as Precompile>::Interface;
363	const MATCHER: BuiltinAddressMatcher = P::MATCHER.into_builtin();
364	const HAS_CONTRACT_INFO: bool = P::HAS_CONTRACT_INFO;
365
366	fn call(
367		address: &[u8; 20],
368		input: &Self::Interface,
369		env: &mut impl Ext<T = Self::T>,
370	) -> Result<Vec<u8>, Error> {
371		Self::call(address, input, env)
372	}
373
374	fn call_with_info(
375		address: &[u8; 20],
376		input: &Self::Interface,
377		env: &mut impl ExtWithInfo<T = Self::T>,
378	) -> Result<Vec<u8>, Error> {
379		Self::call_with_info(address, input, env)
380	}
381}
382
383impl<P: BuiltinPrecompile> PrimitivePrecompile for P {
384	type T = <Self as BuiltinPrecompile>::T;
385	const MATCHER: BuiltinAddressMatcher = P::MATCHER;
386	const HAS_CONTRACT_INFO: bool = P::HAS_CONTRACT_INFO;
387	const CODE: &[u8] = P::CODE;
388
389	fn call(
390		address: &[u8; 20],
391		input: Vec<u8>,
392		env: &mut impl Ext<T = Self::T>,
393	) -> Result<Vec<u8>, Error> {
394		log::trace!(target: crate::LOG_TARGET, "pre-compile call at {:?} with {:x?}", address, input);
395		let call = <Self as BuiltinPrecompile>::Interface::abi_decode_validate(&input)
396			.map_err(|_| Error::Panic(PanicKind::ResourceError))?;
397		let res = <Self as BuiltinPrecompile>::call(address, &call, env);
398		log::trace!(target: crate::LOG_TARGET, "pre-compile call at {:?} result: {:x?}", address, res);
399		res
400	}
401
402	fn call_with_info(
403		address: &[u8; 20],
404		input: Vec<u8>,
405		env: &mut impl ExtWithInfo<T = Self::T>,
406	) -> Result<Vec<u8>, Error> {
407		log::trace!(target: crate::LOG_TARGET, "pre-compile call_with_info at {:?} with {:x?}", address, input);
408		let call = <Self as BuiltinPrecompile>::Interface::abi_decode_validate(&input)
409			.map_err(|_| Error::Panic(PanicKind::ResourceError))?;
410		let res = <Self as BuiltinPrecompile>::call_with_info(address, &call, env);
411		log::trace!(target: crate::LOG_TARGET, "pre-compile call_with_info at {:?} result: {:x?}", address, res);
412		res
413	}
414}
415
416/// The collision check is verified by a trybuild test in `ui-tests/src/ui/precompiles_ui.rs`.
417#[impl_trait_for_tuples::impl_for_tuples(20)]
418#[tuple_types_custom_trait_bound(PrimitivePrecompile<T=T>)]
419impl<T: Config> Precompiles<T> for Tuple {
420	const CHECK_COLLISION: () = {
421		let matchers = [for_tuples!( #( Tuple::MATCHER ),* )];
422		if BuiltinAddressMatcher::has_duplicates(&matchers) {
423			panic!("Precompiles with duplicate matcher detected")
424		}
425		for_tuples!(
426			#(
427				let is_fixed = Tuple::MATCHER.is_fixed();
428				let has_info = Tuple::HAS_CONTRACT_INFO;
429				assert!(is_fixed || !has_info, "Only fixed precompiles can have a contract info.");
430			)*
431		);
432	};
433	const USES_EXTERNAL_RANGE: bool = {
434		let mut uses_external = false;
435		for_tuples!(
436			#(
437				if Tuple::MATCHER.suffix() > u16::MAX as u32 {
438					uses_external = true;
439				}
440			)*
441		);
442		uses_external
443	};
444
445	fn code(address: &[u8; 20]) -> Option<&'static [u8]> {
446		for_tuples!(
447			#(
448				if Tuple::MATCHER.matches(address) {
449					return Some(Tuple::CODE)
450				}
451			)*
452		);
453		None
454	}
455
456	fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>> {
457		let _ = <Self as Precompiles<T>>::CHECK_COLLISION;
458		let mut instance: Option<Instance<E>> = None;
459		for_tuples!(
460			#(
461				if Tuple::MATCHER.matches(address) {
462					if Tuple::HAS_CONTRACT_INFO {
463						instance = Some(Instance {
464							address: *address,
465							has_contract_info: true,
466							function: Tuple::call_with_info,
467						})
468					} else {
469						instance = Some(Instance {
470							address: *address,
471							has_contract_info: false,
472							function: Tuple::call,
473						})
474					}
475				}
476			)*
477		);
478		instance
479	}
480}
481
482/// This references the private trait inside the crate.
483#[cfg(feature = "trybuild")]
484#[allow(private_bounds)]
485pub const fn check_collision_for<T: Config, Tuple: Precompiles<T>>() {
486	let _ = <Tuple as Precompiles<T>>::CHECK_COLLISION;
487}
488
489impl<T: Config> Precompiles<T> for (Builtin<T>, <T as Config>::Precompiles) {
490	const CHECK_COLLISION: () = {
491		assert!(
492			!<Builtin<T>>::USES_EXTERNAL_RANGE,
493			"Builtin precompiles must not use addresses reserved for external precompiles"
494		);
495	};
496	const USES_EXTERNAL_RANGE: bool = { <T as Config>::Precompiles::USES_EXTERNAL_RANGE };
497
498	fn code(address: &[u8; 20]) -> Option<&'static [u8]> {
499		<Builtin<T>>::code(address).or_else(|| <T as Config>::Precompiles::code(address))
500	}
501
502	fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>> {
503		let _ = <Self as Precompiles<T>>::CHECK_COLLISION;
504		<Builtin<T>>::get(address).or_else(|| <T as Config>::Precompiles::get(address))
505	}
506}
507
508impl AddressMatcher {
509	pub const fn base_address(&self) -> [u8; 20] {
510		self.into_builtin().base_address()
511	}
512
513	pub const fn highest_address(&self) -> [u8; 20] {
514		self.into_builtin().highest_address()
515	}
516
517	pub const fn matches(&self, address: &[u8; 20]) -> bool {
518		self.into_builtin().matches(address)
519	}
520
521	const fn into_builtin(&self) -> BuiltinAddressMatcher {
522		const fn left_shift(val: NonZero<u16>) -> NonZero<u32> {
523			let shifted = (val.get() as u32) << 16;
524			NonZero::new(shifted).expect(
525				"Value was non zero before.
526				The shift is small enough to not truncate any existing bits.
527				Hence the value is still non zero; qed",
528			)
529		}
530
531		match self {
532			Self::Fixed(i) => BuiltinAddressMatcher::Fixed(left_shift(*i)),
533			Self::Prefix(i) => BuiltinAddressMatcher::Prefix(left_shift(*i)),
534		}
535	}
536}
537
538impl BuiltinAddressMatcher {
539	pub const fn base_address(&self) -> [u8; 20] {
540		let suffix = self.suffix().to_be_bytes();
541		let mut address = [0u8; 20];
542		let mut i = 16;
543		while i < address.len() {
544			address[i] = suffix[i - 16];
545			i = i + 1;
546		}
547		address
548	}
549
550	pub const fn highest_address(&self) -> [u8; 20] {
551		let mut address = self.base_address();
552		match self {
553			Self::Fixed(_) => (),
554			Self::Prefix(_) => {
555				address[0] = 0xFF;
556				address[1] = 0xFF;
557				address[2] = 0xFF;
558				address[3] = 0xFF;
559			},
560		}
561		address
562	}
563
564	pub const fn matches(&self, address: &[u8; 20]) -> bool {
565		let base_address = self.base_address();
566		let mut i = match self {
567			Self::Fixed(_) => 0,
568			Self::Prefix(_) => 4,
569		};
570		while i < base_address.len() {
571			if address[i] != base_address[i] {
572				return false;
573			}
574			i = i + 1;
575		}
576		true
577	}
578
579	const fn suffix(&self) -> u32 {
580		match self {
581			Self::Fixed(i) => i.get(),
582			Self::Prefix(i) => i.get(),
583		}
584	}
585
586	const fn has_duplicates(nums: &[Self]) -> bool {
587		let len = nums.len();
588		let mut i = 0;
589		while i < len {
590			let mut j = i + 1;
591			while j < len {
592				if nums[i].suffix() == nums[j].suffix() {
593					return true;
594				}
595				j += 1;
596			}
597			i += 1;
598		}
599		false
600	}
601
602	const fn is_fixed(&self) -> bool {
603		matches!(self, Self::Fixed(_))
604	}
605}
606
607/// Types to run a pre-compile during testing or benchmarking.
608///
609/// Use the types exported from this module in order to test or benchmark
610/// your pre-compile. Module only exists when compiles for benchmarking
611/// or tests.
612#[cfg(any(test, feature = "runtime-benchmarks"))]
613pub mod run {
614	pub use crate::{
615		BalanceOf, MomentOf,
616		call_builder::{CallSetup, Contract, VmBinaryModule},
617	};
618	pub use sp_core::{H256, U256};
619
620	use super::*;
621
622	/// Convenience function to run pre-compiles for testing or benchmarking purposes.
623	///
624	/// Use [`CallSetup`] to create an appropriate environment to pass as the `ext` parameter.
625	/// Panics in case the `MATCHER` of `P` does not match the passed `address`.
626	pub fn precompile<P, E>(
627		ext: &mut E,
628		address: &[u8; 20],
629		input: &P::Interface,
630	) -> Result<Vec<u8>, Error>
631	where
632		P: Precompile<T = E::T>,
633		E: ExtWithInfo,
634	{
635		assert!(P::MATCHER.into_builtin().matches(address));
636		if P::HAS_CONTRACT_INFO {
637			P::call_with_info(address, input, ext)
638		} else {
639			P::call(address, input, ext)
640		}
641	}
642
643	/// Convenience function to run builtin pre-compiles from benchmarks.
644	#[cfg(feature = "runtime-benchmarks")]
645	pub(crate) fn builtin<E>(ext: &mut E, address: &[u8; 20], input: Vec<u8>) -> ExecResult
646	where
647		E: ExtWithInfo,
648	{
649		let precompile = <Builtin<E::T>>::get(address)
650			.ok_or(DispatchError::from("No pre-compile at address"))
651			.inspect_err(|_| {
652				log::debug!(target: crate::LOG_TARGET, "No pre-compile at address {address:?}");
653			})?;
654		precompile.call(input, ext)
655	}
656}