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