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