Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/precompile registry #199

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
488 changes: 122 additions & 366 deletions Cargo.lock

Large diffs are not rendered by default.

151 changes: 82 additions & 69 deletions Cargo.toml

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions precompiles/registry/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[package]
name = "pallet-evm-precompile-registry"
authors = { workspace = true }
description = "Registry of active precompiles"
edition = "2021"
version = "0.1.0"

[dependencies]
log = { workspace = true }

# Substrate
frame-support = { workspace = true }
frame-system = { workspace = true }
parity-scale-codec = { version = "3.6.1", default-features = false, features = ["max-encoded-len"] }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-std = { workspace = true }

# Frontier
fp-evm = { workspace = true }
pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] }
precompile-utils = { workspace = true }

[dev-dependencies]
derive_more = { workspace = true }
hex-literal = { workspace = true }
serde = { workspace = true }


precompile-utils = { workspace = true, features = ["std", "testing"] }

# Substrate
pallet-balances = { workspace = true, features = ["insecure_zero_ed", "std"] }
pallet-scheduler = { workspace = true }
pallet-timestamp = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-runtime = { workspace = true }

[features]
default = ["std"]
std = [
"fp-evm/std",
"frame-support/std",
"frame-system/std",
"pallet-evm/std",
"parity-scale-codec/std",
"precompile-utils/std",
"sp-core/std",
"sp-io/std",
"sp-std/std",
]
42 changes: 42 additions & 0 deletions precompiles/registry/src/Registry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @dev The PrecompileRegistry contract's address.
address constant PRECOMPILE_REGISTRY_ADDRESS = 0x0000000000000000000000000000000000001001;

/// @dev The PrecompileRegistry contract's instance.
PrecompileRegistry constant PRECOMPILE_REGISTRY_CONTRACT = PrecompileRegistry(
PRECOMPILE_REGISTRY_ADDRESS
);

/// @title Precompile Registry
/// @dev Interface to the set of available precompiles.
/// @custom:address 0x0000000000000000000000000000000000001001
interface PrecompileRegistry {
/// @dev Query if the given address is a precompile. Note that deactivated precompiles
/// are still considered precompiles and will return `true`.
/// @param a: Address to query
/// @return output Is this address a precompile?
/// @custom:selector 446b450e
function isPrecompile(address a) external view returns (bool);

/// @dev Query if the given address is an active precompile. Will return false if the
/// address is not a precompile or if this precompile is deactivated.
/// @param a: Address to query
/// @return output Is this address an active precompile?
/// @custom:selector 6f5e23cf
function isActivePrecompile(address a) external view returns (bool);

/// @dev Update the account code of a precompile address.
/// As precompiles are implemented inside the Runtime, they don't have a bytecode, and
/// their account code is empty by default. However in Solidity calling a function of a
/// contract often automatically adds a check that the contract bytecode is non-empty.
/// For that reason a dummy code (0x60006000fd) can be inserted at the precompile address
/// to pass that check. This function allows any user to insert that code to precompile address
/// if they need it.
/// @param a: Address of the precompile.
/// @custom:selector 48ceb1b4
function updateAccountCode(address a) external;
}


77 changes: 77 additions & 0 deletions precompiles/registry/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#![cfg_attr(not(feature = "std"), no_std)]

use core::marker::PhantomData;
use fp_evm::{ExitError, IsPrecompileResult, PrecompileFailure, PrecompileHandle};
use precompile_utils::{
precompile_set::{is_precompile_or_fail, IsActivePrecompile},
prelude::*,
};
use sp_core::Get;

const DUMMY_CODE: [u8; 5] = [0x60, 0x00, 0x60, 0x00, 0xfd];

pub struct PrecompileRegistry<Runtime>(PhantomData<Runtime>);

#[precompile_utils::precompile]
impl<Runtime> PrecompileRegistry<Runtime>
where
Runtime: pallet_evm::Config,
Runtime::PrecompilesType: IsActivePrecompile,
{
#[precompile::public("isPrecompile(address)")]
#[precompile::view]
fn is_precompile(handle: &mut impl PrecompileHandle, address: Address) -> EvmResult<bool> {
// We consider the precompile set is optimized to do at most one storage read.
// In the case of moonbeam, the storage item that can be read is pallet_asset::Asset
// (TODO make it more generic, maybe add a const generic on PrecompileRegistry type)
// Storage item: Asset:
// Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15)
handle.record_db_read::<Runtime>(175)?;
is_precompile_or_fail::<Runtime>(address.0, handle.remaining_gas())
}

#[precompile::public("isActivePrecompile(address)")]
#[precompile::view]
fn is_active_precompile(
handle: &mut impl PrecompileHandle,
address: Address,
) -> EvmResult<bool> {
// We consider the precompile set is optimized to do at most one storage read.
// In the case of moonbeam, the storage item that can be read is pallet_asset::Asset
// (TODO make it more generic, maybe add a const generic on PrecompileRegistry type)
// Storage item: Asset:
// Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15)
handle.record_db_read::<Runtime>(175)?;
match <Runtime::PrecompilesValue>::get()
.is_active_precompile(address.0, handle.remaining_gas())
{
IsPrecompileResult::Answer { is_precompile, .. } => Ok(is_precompile),
IsPrecompileResult::OutOfGas =>
Err(PrecompileFailure::Error { exit_status: ExitError::OutOfGas }),
}
}

#[precompile::public("updateAccountCode(address)")]
fn update_account_code(handle: &mut impl PrecompileHandle, address: Address) -> EvmResult<()> {
// Prevent touching addresses that are not precompiles.
//
// We consider the precompile set is optimized to do at most one storage read.
// In the case of moonbeam, the storage item that can be read is pallet_asset::Asset
// (TODO make it more generic, maybe add a const generic on PrecompileRegistry type)
// Storage item: Asset:
// Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15)
handle.record_db_read::<Runtime>(175)?;
if !is_precompile_or_fail::<Runtime>(address.0, handle.remaining_gas())? {
return Err(revert("provided address is not a precompile"));
}

// pallet_evm::create_account read storage item pallet_evm::AccountCodes
//
// AccountCodes: Blake2128(16) + H160(20) + Vec(5)
// We asume an existing precompile can hold at most 5 bytes worth of dummy code.
handle.record_db_read::<Runtime>(41)?;
pallet_evm::Pallet::<Runtime>::create_account(address.0, DUMMY_CODE.to_vec());

Ok(())
}
}
6 changes: 6 additions & 0 deletions runtime/firechain-qa-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ primitive-types = { version = "0.12.0", default-features = false, features = [
"scale-info",
"num-traits",
] }
num_enum = { workspace = true }

firechain-runtime-core-primitives = { path = "../core-primitives", default-features = false }
# primitives
sp-authority-discovery = { workspace = true }
Expand Down Expand Up @@ -131,10 +133,13 @@ pallet-evm-precompile-modexp = { workspace = true }
pallet-evm-precompile-sha3fips = { workspace = true }
pallet-evm-precompile-simple = { workspace = true }
pallet-evm-precompile-batch = {workspace = true }
pallet-evm-precompile-registry = {workspace = true }
pallet-hotfix-sufficients = { workspace = true }
precompile-utils = { workspace = true }

[build-dependencies]
substrate-wasm-builder = { workspace = true, optional = true }
precompile-utils = { workspace = true, features = ["std", "testing"] }

[features]
default = ["std"]
Expand Down Expand Up @@ -235,6 +240,7 @@ std = [
'pallet-evm-precompile-sha3fips/std',
"firechain-runtime-core-primitives/std",
"pallet-evm-precompile-batch/std",
"pallet-evm-precompile-registry/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
Expand Down
38 changes: 20 additions & 18 deletions runtime/firechain-qa-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ use pallet_ethereum::{
};
use pallet_evm::{Account as EVMAccount, FeeCalculator, Runner};
mod precompiles;
use precompiles::FrontierPrecompiles;
pub use precompiles::{FirePrecompiles, PrecompileName};

use sp_runtime::{
create_runtime_str, generic, impl_opaque_keys,
Expand Down Expand Up @@ -331,18 +331,18 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
ProxyType::Any => true,
ProxyType::NonTransfer => !matches!(
c,
RuntimeCall::Balances(..) |
RuntimeCall::Assets(..) |
RuntimeCall::Vesting(pallet_vesting::Call::vested_transfer { .. }) |
RuntimeCall::Indices(pallet_indices::Call::transfer { .. })
RuntimeCall::Balances(..)
| RuntimeCall::Assets(..)
| RuntimeCall::Vesting(pallet_vesting::Call::vested_transfer { .. })
| RuntimeCall::Indices(pallet_indices::Call::transfer { .. })
),
ProxyType::Governance => matches!(
c,
RuntimeCall::Democracy(..) |
RuntimeCall::Council(..) |
RuntimeCall::TechnicalCommittee(..) |
RuntimeCall::Elections(..) |
RuntimeCall::Treasury(..)
RuntimeCall::Democracy(..)
| RuntimeCall::Council(..)
| RuntimeCall::TechnicalCommittee(..)
| RuntimeCall::Elections(..)
| RuntimeCall::Treasury(..)
),
ProxyType::Staking => {
matches!(c, RuntimeCall::Staking(..) | RuntimeCall::FastUnstake(..))
Expand Down Expand Up @@ -709,8 +709,8 @@ impl Get<Option<BalancingConfig>> for OffchainRandomBalancing {
max => {
let seed = sp_io::offchain::random_seed();
let random = <u32>::decode(&mut TrailingZeroInput::new(&seed))
.expect("input is padded with zeroes; qed") %
max.saturating_add(1);
.expect("input is padded with zeroes; qed")
% max.saturating_add(1);
random as usize
},
};
Expand Down Expand Up @@ -1633,7 +1633,7 @@ parameter_types! {
pub const ChainId: u64 = 997;
pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time()/ WEIGHT_PER_GAS);
pub const GasLimitPovSizeRatio: u64 = 4;
pub PrecompilesValue: FrontierPrecompiles<Runtime> = FrontierPrecompiles::<_>::new();
pub PrecompilesValue: FirePrecompiles<Runtime> = FirePrecompiles::<_>::new();
pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0);
}

Expand Down Expand Up @@ -1681,7 +1681,7 @@ impl pallet_evm::Config for Runtime {
type AddressMapping = pallet_evm::IdentityAddressMapping;
type Currency = Balances;
type RuntimeEvent = RuntimeEvent;
type PrecompilesType = FrontierPrecompiles<Self>;
type PrecompilesType = FirePrecompiles<Self>;
type PrecompilesValue = PrecompilesValue;
type ChainId = ChainId;
type BlockGasLimit = BlockGasLimit;
Expand Down Expand Up @@ -2592,8 +2592,9 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall {
len: usize,
) -> Option<Result<(), TransactionValidityError>> {
match self {
RuntimeCall::Ethereum(call) =>
call.pre_dispatch_self_contained(info, dispatch_info, len),
RuntimeCall::Ethereum(call) => {
call.pre_dispatch_self_contained(info, dispatch_info, len)
},
_ => None,
}
}
Expand All @@ -2603,10 +2604,11 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall {
info: Self::SignedInfo,
) -> Option<sp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
match self {
call @ RuntimeCall::Ethereum(pallet_ethereum::Call::transact { .. }) =>
call @ RuntimeCall::Ethereum(pallet_ethereum::Call::transact { .. }) => {
Some(call.dispatch(RuntimeOrigin::from(
pallet_ethereum::RawOrigin::EthereumTransaction(info),
))),
)))
},
_ => None,
}
}
Expand Down
Loading