Skip to content
Draft
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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,36 @@ How it works:

This design ensures safe upgrades for existing networks: contracts that were previously rejected due to size limits won't suddenly become deployable until the network explicitly activates the new limit at a specific block height.

### Restricting Contract Deployment

If you want a permissioned chain where only specific EOAs can deploy contracts, configure a deploy allowlist in the chainspec:

```json
"config": {
...,
"evolve": {
"deployAllowlist": [
"0xYourDeployerAddressHere",
"0xAnotherDeployerAddressHere"
],
"deployAllowlistActivationHeight": 0
}
}
```

How it works:

- The allowlist is enforced at the EVM handler before execution.
- Only top-level `CREATE` transactions from allowlisted callers are accepted.
- Contract-to-contract `CREATE/CREATE2` is still allowed (by design).
- If `deployAllowlistActivationHeight` is omitted, it defaults to `0` when the list is non-empty.
- If the list is empty or missing, contract deployment remains unrestricted.

Operational notes:

- The allowlist is static and must be changed via a chainspec update.
- Duplicate entries or the zero address are rejected at startup.

### Payload Builder Configuration

The payload builder can be configured with:
Expand Down
9 changes: 7 additions & 2 deletions crates/ev-revm/src/api/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,19 @@ where
self,
redirect: Option<BaseFeeRedirect>,
) -> DefaultEvEvm<<Self as MainBuilder>::Context> {
EvEvm::from_inner(self.build_mainnet(), redirect, false)
EvEvm::from_inner(self.build_mainnet(), redirect, None, false)
}

fn build_ev_with_inspector<INSP>(
self,
inspector: INSP,
redirect: Option<BaseFeeRedirect>,
) -> EvEvm<<Self as MainBuilder>::Context, INSP> {
EvEvm::from_inner(self.build_mainnet_with_inspector(inspector), redirect, true)
EvEvm::from_inner(
self.build_mainnet_with_inspector(inspector),
redirect,
None,
true,
)
}
}
20 changes: 15 additions & 5 deletions crates/ev-revm/src/api/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ where

fn transact_one(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
let redirect = self.redirect();
let deploy_allowlist = self.deploy_allowlist();
let inner = self.inner_mut();
inner.ctx.set_tx(tx);
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
let mut handler =
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
handler.run(inner)
}

Expand All @@ -58,8 +60,10 @@ where
&mut self,
) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
let redirect = self.redirect();
let deploy_allowlist = self.deploy_allowlist();
let inner = self.inner_mut();
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
let mut handler =
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
handler.run(inner).map(|result| {
let state = inner.journal_mut().finalize();
ExecResultAndState::new(result, state)
Expand Down Expand Up @@ -91,9 +95,11 @@ where

fn inspect_one_tx(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
let redirect = self.redirect();
let deploy_allowlist = self.deploy_allowlist();
let inner = self.inner_mut();
inner.ctx.set_tx(tx);
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
let mut handler =
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
handler.inspect_run(inner)
}
}
Expand All @@ -119,6 +125,7 @@ where
data: Bytes,
) -> Result<Self::ExecutionResult, Self::Error> {
let redirect = self.redirect();
let deploy_allowlist = self.deploy_allowlist();
let inner = self.inner_mut();
inner
.ctx
Expand All @@ -127,7 +134,8 @@ where
system_contract_address,
data,
));
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
let mut handler =
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
handler.run_system_call(inner)
}
}
Expand All @@ -146,6 +154,7 @@ where
data: Bytes,
) -> Result<Self::ExecutionResult, Self::Error> {
let redirect = self.redirect();
let deploy_allowlist = self.deploy_allowlist();
let inner = self.inner_mut();
inner
.ctx
Expand All @@ -154,7 +163,8 @@ where
system_contract_address,
data,
));
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
let mut handler =
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
handler.inspect_run_system_call(inner)
}
}
44 changes: 44 additions & 0 deletions crates/ev-revm/src/deploy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Deploy allowlist settings for contract creation control.

use alloy_primitives::Address;
use std::sync::Arc;

/// Settings for gating contract deployment by caller allowlist.
#[derive(Debug, Clone)]
pub struct DeployAllowlistSettings {
allowlist: Arc<[Address]>,
activation_height: u64,
}

impl DeployAllowlistSettings {
/// Creates a new deploy allowlist configuration.
pub fn new(allowlist: Vec<Address>, activation_height: u64) -> Self {
let mut allowlist = allowlist;
debug_assert!(!allowlist.is_empty(), "deploy allowlist must not be empty");
allowlist.sort_unstable();
Self {
allowlist: Arc::from(allowlist),
activation_height,
}
}

/// Returns the activation height for deploy allowlist enforcement.
pub const fn activation_height(&self) -> u64 {
self.activation_height
}

/// Returns the allowlisted caller addresses.
pub fn allowlist(&self) -> &[Address] {
&self.allowlist
}

/// Returns true if the allowlist is active at the given block number.
pub const fn is_active(&self, block_number: u64) -> bool {
block_number >= self.activation_height
}

/// Returns true if the caller is in the allowlist.
pub fn is_allowed(&self, caller: Address) -> bool {
self.allowlist.binary_search(&caller).is_ok()
}
}
19 changes: 17 additions & 2 deletions crates/ev-revm/src/evm.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! EV-specific EVM wrapper that installs the base-fee redirect handler.

use crate::base_fee::BaseFeeRedirect;
use crate::{base_fee::BaseFeeRedirect, deploy::DeployAllowlistSettings};
use alloy_evm::{Evm as AlloyEvm, EvmEnv};
use alloy_primitives::{Address, Bytes};
use reth_revm::{
Expand Down Expand Up @@ -33,6 +33,7 @@ pub type DefaultEvEvm<CTX, INSP = ()> = EvEvm<CTX, INSP, EthPrecompiles>;
pub struct EvEvm<CTX, INSP, PRECOMP = EthPrecompiles> {
inner: Evm<CTX, INSP, EthInstructions<EthInterpreter, CTX>, PRECOMP, EthFrame<EthInterpreter>>,
redirect: Option<BaseFeeRedirect>,
deploy_allowlist: Option<DeployAllowlistSettings>,
inspect: bool,
}

Expand All @@ -52,20 +53,27 @@ where
frame_stack: FrameStack::new(),
},
redirect,
deploy_allowlist: None,
inspect: false,
}
}
}

impl<CTX, INSP, P> EvEvm<CTX, INSP, P> {
/// Wraps an existing EVM instance with the redirect policy.
pub fn from_inner<T>(inner: T, redirect: Option<BaseFeeRedirect>, inspect: bool) -> Self
pub fn from_inner<T>(
inner: T,
redirect: Option<BaseFeeRedirect>,
deploy_allowlist: Option<DeployAllowlistSettings>,
inspect: bool,
) -> Self
where
T: IntoRevmEvm<CTX, INSP, P>,
{
Self {
inner: inner.into_revm_evm(),
redirect,
deploy_allowlist,
inspect,
}
}
Expand All @@ -82,11 +90,17 @@ impl<CTX, INSP, P> EvEvm<CTX, INSP, P> {
self.redirect
}

/// Returns the configured deploy allowlist settings, if any.
pub fn deploy_allowlist(&self) -> Option<DeployAllowlistSettings> {
self.deploy_allowlist.clone()
}

/// Allows adjusting the precompiles map while preserving redirect configuration.
pub fn with_precompiles<OP>(self, precompiles: OP) -> EvEvm<CTX, INSP, OP> {
EvEvm {
inner: self.inner.with_precompiles(precompiles),
redirect: self.redirect,
deploy_allowlist: self.deploy_allowlist,
inspect: self.inspect,
}
}
Expand All @@ -96,6 +110,7 @@ impl<CTX, INSP, P> EvEvm<CTX, INSP, P> {
EvEvm {
inner: self.inner.with_inspector(inspector),
redirect: self.redirect,
deploy_allowlist: self.deploy_allowlist,
inspect: self.inspect,
}
}
Expand Down
25 changes: 22 additions & 3 deletions crates/ev-revm/src/factory.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Helpers for wrapping Reth EVM factories with the EV handler.

use crate::{base_fee::BaseFeeRedirect, evm::EvEvm};
use crate::{base_fee::BaseFeeRedirect, deploy::DeployAllowlistSettings, evm::EvEvm};
use alloy_evm::{
eth::{EthBlockExecutorFactory, EthEvmContext, EthEvmFactory},
precompiles::{DynPrecompile, Precompile, PrecompilesMap},
Expand Down Expand Up @@ -104,6 +104,7 @@ pub struct EvEvmFactory<F> {
inner: F,
redirect: Option<BaseFeeRedirectSettings>,
mint_precompile: Option<MintPrecompileSettings>,
deploy_allowlist: Option<DeployAllowlistSettings>,
contract_size_limit: Option<ContractSizeLimitSettings>,
}

Expand All @@ -113,12 +114,14 @@ impl<F> EvEvmFactory<F> {
inner: F,
redirect: Option<BaseFeeRedirectSettings>,
mint_precompile: Option<MintPrecompileSettings>,
deploy_allowlist: Option<DeployAllowlistSettings>,
contract_size_limit: Option<ContractSizeLimitSettings>,
) -> Self {
Self {
inner,
redirect,
mint_precompile,
deploy_allowlist,
contract_size_limit,
}
}
Expand Down Expand Up @@ -186,7 +189,12 @@ impl EvmFactory for EvEvmFactory<EthEvmFactory> {
evm_env.cfg_env.limit_contract_code_size = Some(limit);
}
let inner = self.inner.create_evm(db, evm_env);
let mut evm = EvEvm::from_inner(inner, self.redirect_for_block(block_number), false);
let mut evm = EvEvm::from_inner(
inner,
self.redirect_for_block(block_number),
self.deploy_allowlist.clone(),
false,
);
{
let inner = evm.inner_mut();
self.install_mint_precompile(&mut inner.precompiles, block_number);
Expand All @@ -206,7 +214,12 @@ impl EvmFactory for EvEvmFactory<EthEvmFactory> {
input.cfg_env.limit_contract_code_size = Some(limit);
}
let inner = self.inner.create_evm_with_inspector(db, input, inspector);
let mut evm = EvEvm::from_inner(inner, self.redirect_for_block(block_number), true);
let mut evm = EvEvm::from_inner(
inner,
self.redirect_for_block(block_number),
self.deploy_allowlist.clone(),
true,
);
{
let inner = evm.inner_mut();
self.install_mint_precompile(&mut inner.precompiles, block_number);
Expand All @@ -220,6 +233,7 @@ pub fn with_ev_handler<ChainSpec>(
config: EthEvmConfig<ChainSpec, EthEvmFactory>,
redirect: Option<BaseFeeRedirectSettings>,
mint_precompile: Option<MintPrecompileSettings>,
deploy_allowlist: Option<DeployAllowlistSettings>,
contract_size_limit: Option<ContractSizeLimitSettings>,
) -> EthEvmConfig<ChainSpec, EvEvmFactory<EthEvmFactory>> {
let EthEvmConfig {
Expand All @@ -230,6 +244,7 @@ pub fn with_ev_handler<ChainSpec>(
*executor_factory.evm_factory(),
redirect,
mint_precompile,
deploy_allowlist,
contract_size_limit,
);
let new_executor_factory = EthBlockExecutorFactory::new(
Expand Down Expand Up @@ -316,6 +331,7 @@ mod tests {
Some(BaseFeeRedirectSettings::new(redirect, 0)),
None,
None,
None,
)
.create_evm(state, evm_env.clone());

Expand Down Expand Up @@ -407,6 +423,7 @@ mod tests {
None,
Some(MintPrecompileSettings::new(contract, 0)),
None,
None,
)
.create_evm(state, evm_env);

Expand Down Expand Up @@ -448,6 +465,7 @@ mod tests {
Some(BaseFeeRedirectSettings::new(BaseFeeRedirect::new(sink), 5)),
None,
None,
None,
);

let mut before_env: alloy_evm::EvmEnv<SpecId> = EvmEnv::default();
Expand Down Expand Up @@ -515,6 +533,7 @@ mod tests {
None,
Some(MintPrecompileSettings::new(contract, 3)),
None,
None,
);

let tx_env = || crate::factory::TxEnv {
Expand Down
Loading