mod builtin;
mod tests;
pub use crate::{
exec::{ExecError, PrecompileExt as Ext, PrecompileWithInfoExt as ExtWithInfo},
gas::{GasMeter, Token},
storage::meter::Diff,
wasm::RuntimeCosts,
AddressMapper,
};
pub use alloy_core as alloy;
pub use sp_core::{H160, H256, U256};
use crate::{
exec::ExecResult, precompiles::builtin::Builtin, primitives::ExecReturnValue, Config,
Error as CrateError,
};
use alloc::vec::Vec;
use alloy::sol_types::{Panic, PanicKind, Revert, SolError, SolInterface};
use core::num::NonZero;
use pallet_revive_uapi::ReturnFlags;
use sp_runtime::DispatchError;
#[cfg(feature = "runtime-benchmarks")]
pub(crate) use builtin::{IBenchmarking, NoInfo as BenchmarkNoInfo, WithInfo as BenchmarkWithInfo};
const UNIMPLEMENTED: &str = "A precompile must either implement `call` or `call_with_info`";
pub(crate) type All<T> = (Builtin<T>, <T as Config>::Precompiles);
pub enum AddressMatcher {
Fixed(NonZero<u16>),
Prefix(NonZero<u16>),
}
pub(crate) enum BuiltinAddressMatcher {
Fixed(NonZero<u32>),
Prefix(NonZero<u32>),
}
#[derive(derive_more::From, Debug)]
pub enum Error {
Revert(Revert),
Panic(PanicKind),
Error(ExecError),
}
impl From<DispatchError> for Error {
fn from(error: DispatchError) -> Self {
Self::Error(error.into())
}
}
impl<T: Config> From<CrateError<T>> for Error {
fn from(error: CrateError<T>) -> Self {
Self::Error(DispatchError::from(error).into())
}
}
pub trait Precompile {
type T: Config;
type Interface: SolInterface;
const MATCHER: AddressMatcher;
const HAS_CONTRACT_INFO: bool;
#[allow(unused_variables)]
fn call(
address: &[u8; 20],
input: &Self::Interface,
env: &mut impl Ext<T = Self::T>,
) -> Result<Vec<u8>, Error> {
unimplemented!("{UNIMPLEMENTED}")
}
#[allow(unused_variables)]
fn call_with_info(
address: &[u8; 20],
input: &Self::Interface,
env: &mut impl ExtWithInfo<T = Self::T>,
) -> Result<Vec<u8>, Error> {
unimplemented!("{UNIMPLEMENTED}")
}
}
pub(crate) trait BuiltinPrecompile {
type T: Config;
type Interface: SolInterface;
const MATCHER: BuiltinAddressMatcher;
const HAS_CONTRACT_INFO: bool;
fn call(
_address: &[u8; 20],
_input: &Self::Interface,
_env: &mut impl Ext<T = Self::T>,
) -> Result<Vec<u8>, Error> {
unimplemented!("{UNIMPLEMENTED}")
}
fn call_with_info(
_address: &[u8; 20],
_input: &Self::Interface,
_env: &mut impl ExtWithInfo<T = Self::T>,
) -> Result<Vec<u8>, Error> {
unimplemented!("{UNIMPLEMENTED}")
}
}
pub(crate) trait PrimitivePrecompile {
type T: Config;
const MATCHER: BuiltinAddressMatcher;
const HAS_CONTRACT_INFO: bool;
fn call(
_address: &[u8; 20],
_input: Vec<u8>,
_env: &mut impl Ext<T = Self::T>,
) -> Result<Vec<u8>, Error> {
unimplemented!("{UNIMPLEMENTED}")
}
fn call_with_info(
_address: &[u8; 20],
_input: Vec<u8>,
_env: &mut impl ExtWithInfo<T = Self::T>,
) -> Result<Vec<u8>, Error> {
unimplemented!("{UNIMPLEMENTED}")
}
}
pub(crate) struct Instance<E> {
has_contract_info: bool,
address: [u8; 20],
function: fn(&[u8; 20], Vec<u8>, &mut E) -> Result<Vec<u8>, Error>,
}
impl<E> Instance<E> {
pub fn has_contract_info(&self) -> bool {
self.has_contract_info
}
pub fn call(&self, input: Vec<u8>, env: &mut E) -> ExecResult {
let result = (self.function)(&self.address, input, env);
match result {
Ok(data) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data }),
Err(Error::Revert(msg)) =>
Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: msg.abi_encode() }),
Err(Error::Panic(kind)) => Ok(ExecReturnValue {
flags: ReturnFlags::REVERT,
data: Panic::from(kind).abi_encode(),
}),
Err(Error::Error(err)) => Err(err.into()),
}
}
}
pub(crate) trait Precompiles<T: Config> {
const CHECK_COLLISION: ();
const USES_EXTERNAL_RANGE: bool;
fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>>;
}
impl<P: Precompile> BuiltinPrecompile for P {
type T = <Self as Precompile>::T;
type Interface = <Self as Precompile>::Interface;
const MATCHER: BuiltinAddressMatcher = P::MATCHER.into_builtin();
const HAS_CONTRACT_INFO: bool = P::HAS_CONTRACT_INFO;
fn call(
address: &[u8; 20],
input: &Self::Interface,
env: &mut impl Ext<T = Self::T>,
) -> Result<Vec<u8>, Error> {
Self::call(address, input, env)
}
fn call_with_info(
address: &[u8; 20],
input: &Self::Interface,
env: &mut impl ExtWithInfo<T = Self::T>,
) -> Result<Vec<u8>, Error> {
Self::call_with_info(address, input, env)
}
}
impl<P: BuiltinPrecompile> PrimitivePrecompile for P {
type T = <Self as BuiltinPrecompile>::T;
const MATCHER: BuiltinAddressMatcher = P::MATCHER;
const HAS_CONTRACT_INFO: bool = P::HAS_CONTRACT_INFO;
fn call(
address: &[u8; 20],
input: Vec<u8>,
env: &mut impl Ext<T = Self::T>,
) -> Result<Vec<u8>, Error> {
let call = <Self as BuiltinPrecompile>::Interface::abi_decode_validate(&input)
.map_err(|_| Error::Panic(PanicKind::ResourceError))?;
<Self as BuiltinPrecompile>::call(address, &call, env)
}
fn call_with_info(
address: &[u8; 20],
input: Vec<u8>,
env: &mut impl ExtWithInfo<T = Self::T>,
) -> Result<Vec<u8>, Error> {
let call = <Self as BuiltinPrecompile>::Interface::abi_decode_validate(&input)
.map_err(|_| Error::Panic(PanicKind::ResourceError))?;
<Self as BuiltinPrecompile>::call_with_info(address, &call, env)
}
}
#[impl_trait_for_tuples::impl_for_tuples(20)]
#[tuple_types_custom_trait_bound(PrimitivePrecompile<T=T>)]
impl<T: Config> Precompiles<T> for Tuple {
const CHECK_COLLISION: () = {
let matchers = [for_tuples!( #( Tuple::MATCHER ),* )];
if BuiltinAddressMatcher::has_duplicates(&matchers) {
panic!("Precompiles with duplicate matcher detected")
}
for_tuples!(
#(
let is_fixed = Tuple::MATCHER.is_fixed();
let has_info = Tuple::HAS_CONTRACT_INFO;
assert!(is_fixed || !has_info, "Only fixed precompiles can have a contract info.");
)*
);
};
const USES_EXTERNAL_RANGE: bool = {
let mut uses_external = false;
for_tuples!(
#(
if Tuple::MATCHER.suffix() > u16::MAX as u32 {
uses_external = true;
}
)*
);
uses_external
};
fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>> {
let _ = <Self as Precompiles<T>>::CHECK_COLLISION;
let mut has_contract_info = false;
let mut function: Option<fn(&[u8; 20], Vec<u8>, &mut E) -> Result<Vec<u8>, Error>> = None;
for_tuples!(
#(
if Tuple::MATCHER.matches(address) {
if Tuple::HAS_CONTRACT_INFO {
has_contract_info = true;
function = Some(Tuple::call_with_info);
} else {
has_contract_info = false;
function = Some(Tuple::call);
}
}
)*
);
function.map(|function| Instance { has_contract_info, address: *address, function })
}
}
impl<T: Config> Precompiles<T> for (Builtin<T>, <T as Config>::Precompiles) {
const CHECK_COLLISION: () = {
assert!(
!<Builtin<T>>::USES_EXTERNAL_RANGE,
"Builtin precompiles must not use addresses reserved for external precompiles"
);
};
const USES_EXTERNAL_RANGE: bool = { <T as Config>::Precompiles::USES_EXTERNAL_RANGE };
fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>> {
let _ = <Self as Precompiles<T>>::CHECK_COLLISION;
<Builtin<T>>::get(address).or_else(|| <T as Config>::Precompiles::get(address))
}
}
impl AddressMatcher {
pub const fn base_address(&self) -> [u8; 20] {
self.into_builtin().base_address()
}
pub const fn highest_address(&self) -> [u8; 20] {
self.into_builtin().highest_address()
}
pub const fn matches(&self, address: &[u8; 20]) -> bool {
self.into_builtin().matches(address)
}
const fn into_builtin(&self) -> BuiltinAddressMatcher {
const fn left_shift(val: NonZero<u16>) -> NonZero<u32> {
let shifted = (val.get() as u32) << 16;
NonZero::new(shifted).expect(
"Value was non zero before.
The shift is small enough to not truncate any existing bits.
Hence the value is still non zero; qed",
)
}
match self {
Self::Fixed(i) => BuiltinAddressMatcher::Fixed(left_shift(*i)),
Self::Prefix(i) => BuiltinAddressMatcher::Prefix(left_shift(*i)),
}
}
}
impl BuiltinAddressMatcher {
pub const fn base_address(&self) -> [u8; 20] {
let suffix = self.suffix().to_be_bytes();
let mut address = [0u8; 20];
let mut i = 16;
while i < address.len() {
address[i] = suffix[i - 16];
i = i + 1;
}
address
}
pub const fn highest_address(&self) -> [u8; 20] {
let mut address = self.base_address();
match self {
Self::Fixed(_) => (),
Self::Prefix(_) => {
address[0] = 0xFF;
address[1] = 0xFF;
address[2] = 0xFF;
address[3] = 0xFF;
},
}
address
}
pub const fn matches(&self, address: &[u8; 20]) -> bool {
let base_address = self.base_address();
let mut i = match self {
Self::Fixed(_) => 0,
Self::Prefix(_) => 4,
};
while i < base_address.len() {
if address[i] != base_address[i] {
return false
}
i = i + 1;
}
true
}
const fn suffix(&self) -> u32 {
match self {
Self::Fixed(i) => i.get(),
Self::Prefix(i) => i.get(),
}
}
const fn has_duplicates(nums: &[Self]) -> bool {
let len = nums.len();
let mut i = 0;
while i < len {
let mut j = i + 1;
while j < len {
if nums[i].suffix() == nums[j].suffix() {
return true;
}
j += 1;
}
i += 1;
}
false
}
const fn is_fixed(&self) -> bool {
matches!(self, Self::Fixed(_))
}
}
#[cfg(any(test, feature = "runtime-benchmarks"))]
pub mod run {
pub use crate::{
call_builder::{CallSetup, Contract, WasmModule},
BalanceOf, MomentOf,
};
pub use sp_core::{H256, U256};
use super::*;
pub fn precompile<P, E>(
ext: &mut E,
address: &[u8; 20],
input: &P::Interface,
) -> Result<Vec<u8>, Error>
where
P: Precompile<T = E::T>,
E: ExtWithInfo,
BalanceOf<E::T>: Into<U256> + TryFrom<U256>,
MomentOf<E::T>: Into<U256>,
<<E as Ext>::T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
{
assert!(P::MATCHER.into_builtin().matches(address));
if P::HAS_CONTRACT_INFO {
P::call_with_info(address, input, ext)
} else {
P::call(address, input, ext)
}
}
#[cfg(feature = "runtime-benchmarks")]
pub(crate) fn builtin<E>(ext: &mut E, address: &[u8; 20], input: Vec<u8>) -> ExecResult
where
E: ExtWithInfo,
BalanceOf<E::T>: Into<U256> + TryFrom<U256>,
MomentOf<E::T>: Into<U256>,
<<E as Ext>::T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
{
let precompile = <Builtin<E::T>>::get(address)
.ok_or(DispatchError::from("No pre-compile at address"))?;
precompile.call(input, ext)
}
}