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

Update precompiles and runtime variables #214

Merged
merged 3 commits into from
Jan 23, 2025
Merged
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
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
log = { workspace = true }
num_enum = { workspace = true}
parity-scale-codec = { workspace = true }
scale-info = { workspace = true }
hex-literal = { workspace = true }
Expand Down Expand Up @@ -93,6 +94,7 @@ fp-account = { workspace = true, features = ["serde"] }
fp-evm = { workspace = true, features = ["serde"] }
fp-rpc = { workspace = true }
fp-self-contained = { workspace = true, features = ["serde"] }
precompile-utils = { workspace = true }
# Frontier FRAME
pallet-base-fee = { workspace = true }
pallet-dynamic-fee = { workspace = true }
Expand Down Expand Up @@ -252,6 +254,7 @@ std = [
"fp-evm/std",
"fp-rpc/std",
"fp-self-contained/std",
"precompile-utils/std",
# Frontier FRAME
"pallet-base-fee/std",
"pallet-dynamic-fee/std",
Expand Down
8 changes: 4 additions & 4 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ use static_assertions::const_assert;

// Local imports
use constants::{currency::*, time::*};
use precompiles::FrontierPrecompiles;
use precompiles::AtletaPrecompiles;

// A few exports that help ease life for downstream crates.
pub use frame_system::{limits::BlockWeights, Call as SystemCall, EnsureRoot, EnsureSigned};
Expand Down Expand Up @@ -217,7 +217,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("atleta"),
impl_name: create_runtime_str!("atleta"),
authoring_version: 1,
spec_version: 8,
spec_version: 10,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 3,
Expand Down Expand Up @@ -1335,7 +1335,7 @@ const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
parameter_types! {
pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT);
pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE);
pub PrecompilesValue: FrontierPrecompiles<Runtime> = FrontierPrecompiles::<_>::new();
pub PrecompilesValue: AtletaPrecompiles<Runtime> = AtletaPrecompiles::<_>::new();
pub WeightPerGas: Weight = Weight::from_parts(weight_per_gas(BLOCK_GAS_LIMIT, NORMAL_DISPATCH_RATIO, WEIGHT_MILLISECS_PER_BLOCK), 0);
pub SuicideQuickClearLimit: u32 = 0;
}
Expand All @@ -1350,7 +1350,7 @@ impl pallet_evm::Config for Runtime {
type AddressMapping = IdentityAddressMapping;
type Currency = Balances;
type RuntimeEvent = RuntimeEvent;
type PrecompilesType = FrontierPrecompiles<Self>;
type PrecompilesType = AtletaPrecompiles<Self>;
type PrecompilesValue = PrecompilesValue;
type ChainId = EVMChainId;
type BlockGasLimit = BlockGasLimit;
Expand Down
179 changes: 94 additions & 85 deletions runtime/src/precompiles.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
use frame_support::dispatch::{GetDispatchInfo, Pays};
use pallet_evm::{
IsPrecompileResult, Precompile, PrecompileHandle, PrecompileResult, PrecompileSet,
};
use sp_core::H160;
use sp_std::marker::PhantomData;

use pallet_evm::ExitError;
Expand All @@ -12,109 +8,122 @@ use pallet_evm_precompile_sha3fips::Sha3FIPS256;
use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256};

use pallet_evm_precompile_babe::BabePrecompile;
// use pallet_evm_precompile_faucet::FaucetPrecompile;
use pallet_evm_precompile_governance::GovernancePrecompile;
use pallet_evm_precompile_nomination_pools::NominationPoolsPrecompile;
use pallet_evm_precompile_preimage::PreimagePrecompile;
use pallet_evm_precompile_staking::StakingPrecompile;
use pallet_evm_precompile_treasury::TreasuryPrecompile;

use frame_support::traits::Contains;
use precompile_utils::precompile_set::*;

use crate::*;

pub struct FrontierPrecompiles<R>(PhantomData<R>);
/// Precompile checks for ethereum spec precompiles
/// We allow DELEGATECALL to stay compliant with Ethereum behavior.
type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, CallableByPrecompile);

impl<R> FrontierPrecompiles<R>
where
R: pallet_evm::Config,
{
pub fn new() -> Self {
Self(Default::default())
}
pub fn used_addresses() -> [H160; 8] {
[hash(1), hash(2), hash(3), hash(4), hash(5), hash(1024), hash(1025), hash(1026)]
}
}
impl<R> PrecompileSet for FrontierPrecompiles<R>
where
R: pallet_evm::Config,
{
fn execute(&self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
match handle.code_address() {
// Ethereum precompiles :
a if a == hash(1) => Some(ECRecover::execute(handle)),
a if a == hash(2) => Some(Sha256::execute(handle)),
a if a == hash(3) => Some(Ripemd160::execute(handle)),
a if a == hash(4) => Some(Identity::execute(handle)),
a if a == hash(5) => Some(Modexp::execute(handle)),
// Non-Frontier specific nor Ethereum precompiles :
a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)),
a if a == hash(1025) => Some(ECRecoverPublicKey::execute(handle)),
a if a == hash(1026) => Some(Dispatch::<Runtime, DispatchCallFilter>::execute(handle)),
// Atleta Precompiles
a if a == hash(2001) => Some(GovernancePrecompile::<Runtime>::execute(handle)),
a if a == hash(2002) => Some(TreasuryPrecompile::<Runtime>::execute(handle)),
a if a == hash(2003) => Some(PreimagePrecompile::<Runtime>::execute(handle)),
a if a == hash(2004) => Some(StakingPrecompile::<Runtime>::execute(handle)),
// a if a == hash(2005) => Some(FaucetPrecompile::<Runtime>::execute(handle)),
a if a == hash(2006) => Some(NominationPoolsPrecompile::<Runtime>::execute(handle)),
a if a == hash(2007) => Some(BabePrecompile::<Runtime>::execute(handle)),
_ => None,
}
}
/// Filter that only allows whitelisted runtime call to pass through dispatch precompile
pub struct WhitelistedCalls;

fn is_precompile(&self, address: H160, _gas: u64) -> IsPrecompileResult {
IsPrecompileResult::Answer {
is_precompile: Self::used_addresses().contains(&address),
extra_cost: 0,
impl Contains<RuntimeCall> for WhitelistedCalls {
fn contains(t: &RuntimeCall) -> bool {
match t {
RuntimeCall::Utility(pallet_utility::Call::batch { calls })
| RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => {
calls.iter().all(WhitelistedCalls::contains)
},
RuntimeCall::Democracy(..) => true,
RuntimeCall::Staking(..) => true,
RuntimeCall::Elections(..) => true,
RuntimeCall::Preimage(..) => true,
RuntimeCall::NominationPools(..) => true,
RuntimeCall::Treasury(..) => true,
_ => false,
}
}
}

fn hash(a: u64) -> H160 {
H160::from_low_u64_be(a)
}
#[precompile_utils::precompile_name_from_address]
type AtletaPrecompilesAt<R> = (
// Ethereum precompiles:
PrecompileAt<AddressU64<1>, ECRecover, EthereumPrecompilesChecks>,
PrecompileAt<AddressU64<2>, Sha256, EthereumPrecompilesChecks>,
PrecompileAt<AddressU64<3>, Ripemd160, EthereumPrecompilesChecks>,
PrecompileAt<AddressU64<4>, Identity, EthereumPrecompilesChecks>,
PrecompileAt<AddressU64<5>, Modexp, EthereumPrecompilesChecks>,
// Non-Frontier specific nor Ethereum precompiles:
PrecompileAt<AddressU64<1024>, Sha3FIPS256, (CallableByContract, CallableByPrecompile)>,
PrecompileAt<AddressU64<1025>, ECRecoverPublicKey, (CallableByContract, CallableByPrecompile)>,
PrecompileAt<
AddressU64<1026>,
Dispatch<R, DispatchFilterValidate<RuntimeCall, WhitelistedCalls>>,
// Not callable from smart contract nor precompiles, only EOA accounts
(),
>,
// Atleta Precompiles
PrecompileAt<
AddressU64<2001>,
GovernancePrecompile<R>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2002>,
TreasuryPrecompile<R>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2003>,
PreimagePrecompile<R>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2004>,
StakingPrecompile<R>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2006>,
NominationPoolsPrecompile<R>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<AddressU64<2007>, BabePrecompile<R>, (CallableByContract, CallableByPrecompile)>,
);

/// The PrecompileSet installed in the Atleta runtime.
pub type AtletaPrecompiles<R> = PrecompileSetBuilder<
R,
(
// Skip precompiles if out of range.
PrecompilesInRangeInclusive<(AddressU64<1>, AddressU64<5094>), AtletaPrecompilesAt<R>>,
),
>;

struct DispatchCallFilter;
/// Struct that allows only calls based on `Filter` to pass through.
pub struct DispatchFilterValidate<RuntimeCall, Filter: Contains<RuntimeCall>>(
PhantomData<(RuntimeCall, Filter)>,
);

impl DispatchValidateT<AccountId, RuntimeCall> for DispatchCallFilter {
impl<AccountId, RuntimeCall: GetDispatchInfo, Filter: Contains<RuntimeCall>>
DispatchValidateT<AccountId, RuntimeCall> for DispatchFilterValidate<RuntimeCall, Filter>
{
fn validate_before_dispatch(
_origin: &AccountId,
call: &RuntimeCall,
) -> Option<fp_evm::PrecompileFailure> {
let info = call.get_dispatch_info();

// we ALLOW dispatching these calls
const fn is_allowed(call: &RuntimeCall) -> bool {
matches!(
call,
RuntimeCall::Staking(..)
| RuntimeCall::Democracy(..)
| RuntimeCall::Elections(..)
| RuntimeCall::Preimage(..)
| RuntimeCall::NominationPools(..)
| RuntimeCall::Treasury(..)
)
let paid_normal_call = info.pays_fee == Pays::Yes && info.class == DispatchClass::Normal;
if !paid_normal_call {
return Some(fp_evm::PrecompileFailure::Error {
exit_status: ExitError::Other("invalid call".into()),
});
}

match call {
_ if is_allowed(call) => None,

RuntimeCall::Utility(
pallet_utility::Call::batch { calls } | pallet_utility::Call::batch_all { calls },
) if calls.iter().all(is_allowed) => None,

// forbid feeless and heavy calls to prevent spaming
_ if info.pays_fee == Pays::No || info.class == DispatchClass::Mandatory => {
Some(fp_evm::PrecompileFailure::Error {
exit_status: ExitError::Other("Permission denied calls".into()),
})
},

_ => Some(fp_evm::PrecompileFailure::Error {
exit_status: ExitError::Other(
"The call is not allowed to be dispatched via precompile.".into(),
),
}),
if Filter::contains(call) {
None
} else {
Some(fp_evm::PrecompileFailure::Error {
exit_status: ExitError::Other("call filtered out".into()),
})
}
}
}