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	exec::{ExecError, PrecompileExt as Ext, PrecompileWithInfoExt as ExtWithInfo},
35	metering::{Diff, Token},
36	vm::RuntimeCosts,
37};
38pub use alloy_core as alloy;
39pub use sp_core::{H160, H256, U256};
40
41use crate::{
42	Config, Error as CrateError, exec::ExecResult, precompiles::builtin::Builtin,
43	primitives::ExecReturnValue,
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			},
317			Err(Error::Panic(kind)) => Ok(ExecReturnValue {
318				flags: ReturnFlags::REVERT,
319				data: Panic::from(kind).abi_encode(),
320			}),
321			Err(Error::Error(err)) => Err(err.into()),
322		}
323	}
324}
325
326/// A composition of pre-compiles.
327///
328/// Automatically implemented for tuples of types that implement any of the
329/// pre-compile traits.
330pub(crate) trait Precompiles<T: Config> {
331	/// Used to generate compile time error when multiple pre-compiles use the same matcher.
332	const CHECK_COLLISION: ();
333	/// Does any of the pre-compiles use the range reserved for external pre-compiles.
334	///
335	/// This is just used to generate a compile time error if `Builtin` is using the external
336	/// range by accident.
337	const USES_EXTERNAL_RANGE: bool;
338
339	/// Returns the code of the pre-compile.
340	///
341	/// Just used when queried by `EXTCODESIZE` or the RPC. It is just
342	/// a bogus code that is never executed. Returns None if no pre-compile
343	/// exists at the specified address.
344	fn code(address: &[u8; 20]) -> Option<&'static [u8]>;
345
346	/// Get a reference to a specific pre-compile.
347	///
348	/// Returns `None` if no pre-compile exists at `address`.
349	fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>>;
350}
351
352impl<P: Precompile> BuiltinPrecompile for P {
353	type T = <Self as Precompile>::T;
354	type Interface = <Self as Precompile>::Interface;
355	const MATCHER: BuiltinAddressMatcher = P::MATCHER.into_builtin();
356	const HAS_CONTRACT_INFO: bool = P::HAS_CONTRACT_INFO;
357
358	fn call(
359		address: &[u8; 20],
360		input: &Self::Interface,
361		env: &mut impl Ext<T = Self::T>,
362	) -> Result<Vec<u8>, Error> {
363		Self::call(address, input, env)
364	}
365
366	fn call_with_info(
367		address: &[u8; 20],
368		input: &Self::Interface,
369		env: &mut impl ExtWithInfo<T = Self::T>,
370	) -> Result<Vec<u8>, Error> {
371		Self::call_with_info(address, input, env)
372	}
373}
374
375impl<P: BuiltinPrecompile> PrimitivePrecompile for P {
376	type T = <Self as BuiltinPrecompile>::T;
377	const MATCHER: BuiltinAddressMatcher = P::MATCHER;
378	const HAS_CONTRACT_INFO: bool = P::HAS_CONTRACT_INFO;
379	const CODE: &[u8] = P::CODE;
380
381	fn call(
382		address: &[u8; 20],
383		input: Vec<u8>,
384		env: &mut impl Ext<T = Self::T>,
385	) -> Result<Vec<u8>, Error> {
386		log::trace!(target: crate::LOG_TARGET, "pre-compile call at {:?} with {:x?}", address, input);
387		let call = <Self as BuiltinPrecompile>::Interface::abi_decode_validate(&input)
388			.map_err(|_| Error::Panic(PanicKind::ResourceError))?;
389		let res = <Self as BuiltinPrecompile>::call(address, &call, env);
390		log::trace!(target: crate::LOG_TARGET, "pre-compile call at {:?} result: {:x?}", address, res);
391		res
392	}
393
394	fn call_with_info(
395		address: &[u8; 20],
396		input: Vec<u8>,
397		env: &mut impl ExtWithInfo<T = Self::T>,
398	) -> Result<Vec<u8>, Error> {
399		log::trace!(target: crate::LOG_TARGET, "pre-compile call_with_info at {:?} with {:x?}", address, input);
400		let call = <Self as BuiltinPrecompile>::Interface::abi_decode_validate(&input)
401			.map_err(|_| Error::Panic(PanicKind::ResourceError))?;
402		let res = <Self as BuiltinPrecompile>::call_with_info(address, &call, env);
403		log::trace!(target: crate::LOG_TARGET, "pre-compile call_with_info at {:?} result: {:x?}", address, res);
404		res
405	}
406}
407
408/// The collision check is verified by a trybuild test in `ui-tests/src/ui/precompiles_ui.rs`.
409#[impl_trait_for_tuples::impl_for_tuples(20)]
410#[tuple_types_custom_trait_bound(PrimitivePrecompile<T=T>)]
411impl<T: Config> Precompiles<T> for Tuple {
412	const CHECK_COLLISION: () = {
413		let matchers = [for_tuples!( #( Tuple::MATCHER ),* )];
414		if BuiltinAddressMatcher::has_duplicates(&matchers) {
415			panic!("Precompiles with duplicate matcher detected")
416		}
417		for_tuples!(
418			#(
419				let is_fixed = Tuple::MATCHER.is_fixed();
420				let has_info = Tuple::HAS_CONTRACT_INFO;
421				assert!(is_fixed || !has_info, "Only fixed precompiles can have a contract info.");
422			)*
423		);
424	};
425	const USES_EXTERNAL_RANGE: bool = {
426		let mut uses_external = false;
427		for_tuples!(
428			#(
429				if Tuple::MATCHER.suffix() > u16::MAX as u32 {
430					uses_external = true;
431				}
432			)*
433		);
434		uses_external
435	};
436
437	fn code(address: &[u8; 20]) -> Option<&'static [u8]> {
438		for_tuples!(
439			#(
440				if Tuple::MATCHER.matches(address) {
441					return Some(Tuple::CODE)
442				}
443			)*
444		);
445		None
446	}
447
448	fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>> {
449		let _ = <Self as Precompiles<T>>::CHECK_COLLISION;
450		let mut instance: Option<Instance<E>> = None;
451		for_tuples!(
452			#(
453				if Tuple::MATCHER.matches(address) {
454					if Tuple::HAS_CONTRACT_INFO {
455						instance = Some(Instance {
456							address: *address,
457							has_contract_info: true,
458							function: Tuple::call_with_info,
459						})
460					} else {
461						instance = Some(Instance {
462							address: *address,
463							has_contract_info: false,
464							function: Tuple::call,
465						})
466					}
467				}
468			)*
469		);
470		instance
471	}
472}
473
474/// This references the private trait inside the crate.
475#[cfg(feature = "trybuild")]
476#[allow(private_bounds)]
477pub const fn check_collision_for<T: Config, Tuple: Precompiles<T>>() {
478	let _ = <Tuple as Precompiles<T>>::CHECK_COLLISION;
479}
480
481impl<T: Config> Precompiles<T> for (Builtin<T>, <T as Config>::Precompiles) {
482	const CHECK_COLLISION: () = {
483		assert!(
484			!<Builtin<T>>::USES_EXTERNAL_RANGE,
485			"Builtin precompiles must not use addresses reserved for external precompiles"
486		);
487	};
488	const USES_EXTERNAL_RANGE: bool = { <T as Config>::Precompiles::USES_EXTERNAL_RANGE };
489
490	fn code(address: &[u8; 20]) -> Option<&'static [u8]> {
491		<Builtin<T>>::code(address).or_else(|| <T as Config>::Precompiles::code(address))
492	}
493
494	fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>> {
495		let _ = <Self as Precompiles<T>>::CHECK_COLLISION;
496		<Builtin<T>>::get(address).or_else(|| <T as Config>::Precompiles::get(address))
497	}
498}
499
500impl AddressMatcher {
501	pub const fn base_address(&self) -> [u8; 20] {
502		self.into_builtin().base_address()
503	}
504
505	pub const fn highest_address(&self) -> [u8; 20] {
506		self.into_builtin().highest_address()
507	}
508
509	pub const fn matches(&self, address: &[u8; 20]) -> bool {
510		self.into_builtin().matches(address)
511	}
512
513	const fn into_builtin(&self) -> BuiltinAddressMatcher {
514		const fn left_shift(val: NonZero<u16>) -> NonZero<u32> {
515			let shifted = (val.get() as u32) << 16;
516			NonZero::new(shifted).expect(
517				"Value was non zero before.
518				The shift is small enough to not truncate any existing bits.
519				Hence the value is still non zero; qed",
520			)
521		}
522
523		match self {
524			Self::Fixed(i) => BuiltinAddressMatcher::Fixed(left_shift(*i)),
525			Self::Prefix(i) => BuiltinAddressMatcher::Prefix(left_shift(*i)),
526		}
527	}
528}
529
530impl BuiltinAddressMatcher {
531	pub const fn base_address(&self) -> [u8; 20] {
532		let suffix = self.suffix().to_be_bytes();
533		let mut address = [0u8; 20];
534		let mut i = 16;
535		while i < address.len() {
536			address[i] = suffix[i - 16];
537			i = i + 1;
538		}
539		address
540	}
541
542	pub const fn highest_address(&self) -> [u8; 20] {
543		let mut address = self.base_address();
544		match self {
545			Self::Fixed(_) => (),
546			Self::Prefix(_) => {
547				address[0] = 0xFF;
548				address[1] = 0xFF;
549				address[2] = 0xFF;
550				address[3] = 0xFF;
551			},
552		}
553		address
554	}
555
556	pub const fn matches(&self, address: &[u8; 20]) -> bool {
557		let base_address = self.base_address();
558		let mut i = match self {
559			Self::Fixed(_) => 0,
560			Self::Prefix(_) => 4,
561		};
562		while i < base_address.len() {
563			if address[i] != base_address[i] {
564				return false;
565			}
566			i = i + 1;
567		}
568		true
569	}
570
571	const fn suffix(&self) -> u32 {
572		match self {
573			Self::Fixed(i) => i.get(),
574			Self::Prefix(i) => i.get(),
575		}
576	}
577
578	const fn has_duplicates(nums: &[Self]) -> bool {
579		let len = nums.len();
580		let mut i = 0;
581		while i < len {
582			let mut j = i + 1;
583			while j < len {
584				if nums[i].suffix() == nums[j].suffix() {
585					return true;
586				}
587				j += 1;
588			}
589			i += 1;
590		}
591		false
592	}
593
594	const fn is_fixed(&self) -> bool {
595		matches!(self, Self::Fixed(_))
596	}
597}
598
599/// Types to run a pre-compile during testing or benchmarking.
600///
601/// Use the types exported from this module in order to test or benchmark
602/// your pre-compile. Module only exists when compiles for benchmarking
603/// or tests.
604#[cfg(any(test, feature = "runtime-benchmarks"))]
605pub mod run {
606	pub use crate::{
607		BalanceOf, MomentOf,
608		call_builder::{CallSetup, Contract, VmBinaryModule},
609	};
610	pub use sp_core::{H256, U256};
611
612	use super::*;
613
614	/// Convenience function to run pre-compiles for testing or benchmarking purposes.
615	///
616	/// Use [`CallSetup`] to create an appropriate environment to pass as the `ext` parameter.
617	/// Panics in case the `MATCHER` of `P` does not match the passed `address`.
618	pub fn precompile<P, E>(
619		ext: &mut E,
620		address: &[u8; 20],
621		input: &P::Interface,
622	) -> Result<Vec<u8>, Error>
623	where
624		P: Precompile<T = E::T>,
625		E: ExtWithInfo,
626	{
627		assert!(P::MATCHER.into_builtin().matches(address));
628		if P::HAS_CONTRACT_INFO {
629			P::call_with_info(address, input, ext)
630		} else {
631			P::call(address, input, ext)
632		}
633	}
634
635	/// Convenience function to run builtin pre-compiles from benchmarks.
636	#[cfg(feature = "runtime-benchmarks")]
637	pub(crate) fn builtin<E>(ext: &mut E, address: &[u8; 20], input: Vec<u8>) -> ExecResult
638	where
639		E: ExtWithInfo,
640	{
641		let precompile = <Builtin<E::T>>::get(address)
642			.ok_or(DispatchError::from("No pre-compile at address"))
643			.inspect_err(|_| {
644				log::debug!(target: crate::LOG_TARGET, "No pre-compile at address {address:?}");
645			})?;
646		precompile.call(input, ext)
647	}
648}