Skip to content

Commit d8c57b6

Browse files
committed
permissioned
1 parent 8962307 commit d8c57b6

File tree

13 files changed

+547
-27
lines changed

13 files changed

+547
-27
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,36 @@ How it works:
299299

300300
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.
301301

302+
### Restricting Contract Deployment
303+
304+
If you want a permissioned chain where only specific EOAs can deploy contracts, configure a deploy allowlist in the chainspec:
305+
306+
```json
307+
"config": {
308+
...,
309+
"evolve": {
310+
"deployAllowlist": [
311+
"0xYourDeployerAddressHere",
312+
"0xAnotherDeployerAddressHere"
313+
],
314+
"deployAllowlistActivationHeight": 0
315+
}
316+
}
317+
```
318+
319+
How it works:
320+
321+
- The allowlist is enforced at the EVM handler before execution.
322+
- Only top-level `CREATE` transactions from allowlisted callers are accepted.
323+
- Contract-to-contract `CREATE/CREATE2` is still allowed (by design).
324+
- If `deployAllowlistActivationHeight` is omitted, it defaults to `0` when the list is non-empty.
325+
- If the list is empty or missing, contract deployment remains unrestricted.
326+
327+
Operational notes:
328+
329+
- The allowlist is static and must be changed via a chainspec update.
330+
- Duplicate entries or the zero address are rejected at startup.
331+
302332
### Payload Builder Configuration
303333

304334
The payload builder can be configured with:

crates/ev-revm/src/api/builder.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,19 @@ where
3030
self,
3131
redirect: Option<BaseFeeRedirect>,
3232
) -> DefaultEvEvm<<Self as MainBuilder>::Context> {
33-
EvEvm::from_inner(self.build_mainnet(), redirect, false)
33+
EvEvm::from_inner(self.build_mainnet(), redirect, None, false)
3434
}
3535

3636
fn build_ev_with_inspector<INSP>(
3737
self,
3838
inspector: INSP,
3939
redirect: Option<BaseFeeRedirect>,
4040
) -> EvEvm<<Self as MainBuilder>::Context, INSP> {
41-
EvEvm::from_inner(self.build_mainnet_with_inspector(inspector), redirect, true)
41+
EvEvm::from_inner(
42+
self.build_mainnet_with_inspector(inspector),
43+
redirect,
44+
None,
45+
true,
46+
)
4247
}
4348
}

crates/ev-revm/src/api/exec.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@ where
4444

4545
fn transact_one(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
4646
let redirect = self.redirect();
47+
let deploy_allowlist = self.deploy_allowlist();
4748
let inner = self.inner_mut();
4849
inner.ctx.set_tx(tx);
49-
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
50+
let mut handler =
51+
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
5052
handler.run(inner)
5153
}
5254

@@ -58,8 +60,10 @@ where
5860
&mut self,
5961
) -> Result<ExecResultAndState<Self::ExecutionResult, Self::State>, Self::Error> {
6062
let redirect = self.redirect();
63+
let deploy_allowlist = self.deploy_allowlist();
6164
let inner = self.inner_mut();
62-
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
65+
let mut handler =
66+
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
6367
handler.run(inner).map(|result| {
6468
let state = inner.journal_mut().finalize();
6569
ExecResultAndState::new(result, state)
@@ -91,9 +95,11 @@ where
9195

9296
fn inspect_one_tx(&mut self, tx: Self::Tx) -> Result<Self::ExecutionResult, Self::Error> {
9397
let redirect = self.redirect();
98+
let deploy_allowlist = self.deploy_allowlist();
9499
let inner = self.inner_mut();
95100
inner.ctx.set_tx(tx);
96-
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
101+
let mut handler =
102+
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
97103
handler.inspect_run(inner)
98104
}
99105
}
@@ -119,6 +125,7 @@ where
119125
data: Bytes,
120126
) -> Result<Self::ExecutionResult, Self::Error> {
121127
let redirect = self.redirect();
128+
let deploy_allowlist = self.deploy_allowlist();
122129
let inner = self.inner_mut();
123130
inner
124131
.ctx
@@ -127,7 +134,8 @@ where
127134
system_contract_address,
128135
data,
129136
));
130-
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
137+
let mut handler =
138+
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
131139
handler.run_system_call(inner)
132140
}
133141
}
@@ -146,6 +154,7 @@ where
146154
data: Bytes,
147155
) -> Result<Self::ExecutionResult, Self::Error> {
148156
let redirect = self.redirect();
157+
let deploy_allowlist = self.deploy_allowlist();
149158
let inner = self.inner_mut();
150159
inner
151160
.ctx
@@ -154,7 +163,8 @@ where
154163
system_contract_address,
155164
data,
156165
));
157-
let mut handler = EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect);
166+
let mut handler =
167+
EvHandler::<_, _, EthFrame<EthInterpreter>>::new(redirect, deploy_allowlist);
158168
handler.inspect_run_system_call(inner)
159169
}
160170
}

crates/ev-revm/src/deploy.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//! Deploy allowlist settings for contract creation control.
2+
3+
use alloy_primitives::Address;
4+
use std::sync::Arc;
5+
6+
/// Settings for gating contract deployment by caller allowlist.
7+
#[derive(Debug, Clone)]
8+
pub struct DeployAllowlistSettings {
9+
allowlist: Arc<[Address]>,
10+
activation_height: u64,
11+
}
12+
13+
impl DeployAllowlistSettings {
14+
/// Creates a new deploy allowlist configuration.
15+
pub fn new(allowlist: Vec<Address>, activation_height: u64) -> Self {
16+
debug_assert!(!allowlist.is_empty(), "deploy allowlist must not be empty");
17+
Self {
18+
allowlist: Arc::from(allowlist),
19+
activation_height,
20+
}
21+
}
22+
23+
/// Returns the activation height for deploy allowlist enforcement.
24+
pub const fn activation_height(&self) -> u64 {
25+
self.activation_height
26+
}
27+
28+
/// Returns the allowlisted caller addresses.
29+
pub fn allowlist(&self) -> &[Address] {
30+
&self.allowlist
31+
}
32+
33+
/// Returns true if the allowlist is active at the given block number.
34+
pub const fn is_active(&self, block_number: u64) -> bool {
35+
block_number >= self.activation_height
36+
}
37+
38+
/// Returns true if the caller is in the allowlist.
39+
pub fn is_allowed(&self, caller: Address) -> bool {
40+
self.allowlist.contains(&caller)
41+
}
42+
}

crates/ev-revm/src/evm.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! EV-specific EVM wrapper that installs the base-fee redirect handler.
22
3-
use crate::base_fee::BaseFeeRedirect;
3+
use crate::{base_fee::BaseFeeRedirect, deploy::DeployAllowlistSettings};
44
use alloy_evm::{Evm as AlloyEvm, EvmEnv};
55
use alloy_primitives::{Address, Bytes};
66
use reth_revm::{
@@ -33,6 +33,7 @@ pub type DefaultEvEvm<CTX, INSP = ()> = EvEvm<CTX, INSP, EthPrecompiles>;
3333
pub struct EvEvm<CTX, INSP, PRECOMP = EthPrecompiles> {
3434
inner: Evm<CTX, INSP, EthInstructions<EthInterpreter, CTX>, PRECOMP, EthFrame<EthInterpreter>>,
3535
redirect: Option<BaseFeeRedirect>,
36+
deploy_allowlist: Option<DeployAllowlistSettings>,
3637
inspect: bool,
3738
}
3839

@@ -52,20 +53,27 @@ where
5253
frame_stack: FrameStack::new(),
5354
},
5455
redirect,
56+
deploy_allowlist: None,
5557
inspect: false,
5658
}
5759
}
5860
}
5961

6062
impl<CTX, INSP, P> EvEvm<CTX, INSP, P> {
6163
/// Wraps an existing EVM instance with the redirect policy.
62-
pub fn from_inner<T>(inner: T, redirect: Option<BaseFeeRedirect>, inspect: bool) -> Self
64+
pub fn from_inner<T>(
65+
inner: T,
66+
redirect: Option<BaseFeeRedirect>,
67+
deploy_allowlist: Option<DeployAllowlistSettings>,
68+
inspect: bool,
69+
) -> Self
6370
where
6471
T: IntoRevmEvm<CTX, INSP, P>,
6572
{
6673
Self {
6774
inner: inner.into_revm_evm(),
6875
redirect,
76+
deploy_allowlist,
6977
inspect,
7078
}
7179
}
@@ -82,11 +90,17 @@ impl<CTX, INSP, P> EvEvm<CTX, INSP, P> {
8290
self.redirect
8391
}
8492

93+
/// Returns the configured deploy allowlist settings, if any.
94+
pub fn deploy_allowlist(&self) -> Option<DeployAllowlistSettings> {
95+
self.deploy_allowlist.clone()
96+
}
97+
8598
/// Allows adjusting the precompiles map while preserving redirect configuration.
8699
pub fn with_precompiles<OP>(self, precompiles: OP) -> EvEvm<CTX, INSP, OP> {
87100
EvEvm {
88101
inner: self.inner.with_precompiles(precompiles),
89102
redirect: self.redirect,
103+
deploy_allowlist: self.deploy_allowlist,
90104
inspect: self.inspect,
91105
}
92106
}
@@ -96,6 +110,7 @@ impl<CTX, INSP, P> EvEvm<CTX, INSP, P> {
96110
EvEvm {
97111
inner: self.inner.with_inspector(inspector),
98112
redirect: self.redirect,
113+
deploy_allowlist: self.deploy_allowlist,
99114
inspect: self.inspect,
100115
}
101116
}

crates/ev-revm/src/factory.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Helpers for wrapping Reth EVM factories with the EV handler.
22
3-
use crate::{base_fee::BaseFeeRedirect, evm::EvEvm};
3+
use crate::{base_fee::BaseFeeRedirect, deploy::DeployAllowlistSettings, evm::EvEvm};
44
use alloy_evm::{
55
eth::{EthBlockExecutorFactory, EthEvmContext, EthEvmFactory},
66
precompiles::{DynPrecompile, Precompile, PrecompilesMap},
@@ -104,6 +104,7 @@ pub struct EvEvmFactory<F> {
104104
inner: F,
105105
redirect: Option<BaseFeeRedirectSettings>,
106106
mint_precompile: Option<MintPrecompileSettings>,
107+
deploy_allowlist: Option<DeployAllowlistSettings>,
107108
contract_size_limit: Option<ContractSizeLimitSettings>,
108109
}
109110

@@ -113,12 +114,14 @@ impl<F> EvEvmFactory<F> {
113114
inner: F,
114115
redirect: Option<BaseFeeRedirectSettings>,
115116
mint_precompile: Option<MintPrecompileSettings>,
117+
deploy_allowlist: Option<DeployAllowlistSettings>,
116118
contract_size_limit: Option<ContractSizeLimitSettings>,
117119
) -> Self {
118120
Self {
119121
inner,
120122
redirect,
121123
mint_precompile,
124+
deploy_allowlist,
122125
contract_size_limit,
123126
}
124127
}
@@ -186,7 +189,12 @@ impl EvmFactory for EvEvmFactory<EthEvmFactory> {
186189
evm_env.cfg_env.limit_contract_code_size = Some(limit);
187190
}
188191
let inner = self.inner.create_evm(db, evm_env);
189-
let mut evm = EvEvm::from_inner(inner, self.redirect_for_block(block_number), false);
192+
let mut evm = EvEvm::from_inner(
193+
inner,
194+
self.redirect_for_block(block_number),
195+
self.deploy_allowlist.clone(),
196+
false,
197+
);
190198
{
191199
let inner = evm.inner_mut();
192200
self.install_mint_precompile(&mut inner.precompiles, block_number);
@@ -206,7 +214,12 @@ impl EvmFactory for EvEvmFactory<EthEvmFactory> {
206214
input.cfg_env.limit_contract_code_size = Some(limit);
207215
}
208216
let inner = self.inner.create_evm_with_inspector(db, input, inspector);
209-
let mut evm = EvEvm::from_inner(inner, self.redirect_for_block(block_number), true);
217+
let mut evm = EvEvm::from_inner(
218+
inner,
219+
self.redirect_for_block(block_number),
220+
self.deploy_allowlist.clone(),
221+
true,
222+
);
210223
{
211224
let inner = evm.inner_mut();
212225
self.install_mint_precompile(&mut inner.precompiles, block_number);
@@ -220,6 +233,7 @@ pub fn with_ev_handler<ChainSpec>(
220233
config: EthEvmConfig<ChainSpec, EthEvmFactory>,
221234
redirect: Option<BaseFeeRedirectSettings>,
222235
mint_precompile: Option<MintPrecompileSettings>,
236+
deploy_allowlist: Option<DeployAllowlistSettings>,
223237
contract_size_limit: Option<ContractSizeLimitSettings>,
224238
) -> EthEvmConfig<ChainSpec, EvEvmFactory<EthEvmFactory>> {
225239
let EthEvmConfig {
@@ -230,6 +244,7 @@ pub fn with_ev_handler<ChainSpec>(
230244
*executor_factory.evm_factory(),
231245
redirect,
232246
mint_precompile,
247+
deploy_allowlist,
233248
contract_size_limit,
234249
);
235250
let new_executor_factory = EthBlockExecutorFactory::new(
@@ -316,6 +331,7 @@ mod tests {
316331
Some(BaseFeeRedirectSettings::new(redirect, 0)),
317332
None,
318333
None,
334+
None,
319335
)
320336
.create_evm(state, evm_env.clone());
321337

@@ -407,6 +423,7 @@ mod tests {
407423
None,
408424
Some(MintPrecompileSettings::new(contract, 0)),
409425
None,
426+
None,
410427
)
411428
.create_evm(state, evm_env);
412429

@@ -448,6 +465,7 @@ mod tests {
448465
Some(BaseFeeRedirectSettings::new(BaseFeeRedirect::new(sink), 5)),
449466
None,
450467
None,
468+
None,
451469
);
452470

453471
let mut before_env: alloy_evm::EvmEnv<SpecId> = EvmEnv::default();
@@ -515,6 +533,7 @@ mod tests {
515533
None,
516534
Some(MintPrecompileSettings::new(contract, 3)),
517535
None,
536+
None,
518537
);
519538

520539
let tx_env = || crate::factory::TxEnv {

0 commit comments

Comments
 (0)