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