Skip to content

Commit c3a6036

Browse files
authored
feat: permissioned evm (#100)
* permissioned * produce document * comments * switch to allow empty to allow everyone * wording fix
1 parent 550d7b9 commit c3a6036

14 files changed

Lines changed: 608 additions & 27 deletions

File tree

README.md

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

330330
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.
331331

332+
### Restricting Contract Deployment
333+
334+
If you want a permissioned chain where only specific EOAs can deploy contracts, configure a deploy allowlist in the chainspec:
335+
336+
```json
337+
"config": {
338+
...,
339+
"evolve": {
340+
"deployAllowlist": [
341+
"0xYourDeployerAddressHere",
342+
"0xAnotherDeployerAddressHere"
343+
],
344+
"deployAllowlistActivationHeight": 0
345+
}
346+
}
347+
```
348+
349+
How it works:
350+
351+
- The allowlist is enforced at the EVM handler before execution.
352+
- Only top-level `CREATE` transactions from allowlisted callers are accepted.
353+
- Contract-to-contract `CREATE/CREATE2` is still allowed (by design).
354+
- If `deployAllowlistActivationHeight` is omitted, it defaults to `0` when the list is non-empty.
355+
- If the list is empty or missing, contract deployment remains unrestricted (treated as disabled).
356+
357+
Operational notes:
358+
359+
- The allowlist is static and must be changed via a chainspec update.
360+
- Duplicate entries or the zero address are rejected at startup.
361+
332362
### Payload Builder Configuration
333363

334364
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: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
/// An empty allowlist disables gating and allows all callers.
16+
pub fn new(allowlist: Vec<Address>, activation_height: u64) -> Self {
17+
let mut allowlist = allowlist;
18+
allowlist.sort_unstable();
19+
Self {
20+
allowlist: Arc::from(allowlist),
21+
activation_height,
22+
}
23+
}
24+
25+
/// Returns the activation height for deploy allowlist enforcement.
26+
pub const fn activation_height(&self) -> u64 {
27+
self.activation_height
28+
}
29+
30+
/// Returns the allowlisted caller addresses.
31+
pub fn allowlist(&self) -> &[Address] {
32+
&self.allowlist
33+
}
34+
35+
/// Returns true if the allowlist is active at the given block number.
36+
pub const fn is_active(&self, block_number: u64) -> bool {
37+
block_number >= self.activation_height
38+
}
39+
40+
/// Returns true if the caller is in the allowlist.
41+
pub fn is_allowed(&self, caller: Address) -> bool {
42+
if self.allowlist.is_empty() {
43+
return true;
44+
}
45+
self.allowlist.binary_search(&caller).is_ok()
46+
}
47+
}

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)