Skip to content

Commit f427b5f

Browse files
Merge pull request #3 from 0gfoundation/precompiles
feat: stateful precompiles
2 parents 028ede9 + b2c2f9c commit f427b5f

3 files changed

Lines changed: 76 additions & 51 deletions

File tree

Cargo.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ thiserror = { version = "2.0.0", default-features = false }
6464
serde_json = "1"
6565
test-case = "3"
6666

67-
#[patch.crates-io]
68-
#revm = { git = "https://github.com/bluealloy/revm", rev = "11b16259" }
69-
#op-revm = { git = "https://github.com/bluealloy/revm", rev = "11b16259" }
67+
[patch.crates-io]
68+
revm = { git = "https://github.com/0gfoundation/revm", rev = "71cbb675f562a6c1ac1fd4eda8618b8a921426b6" }
69+
op-revm = { git = "https://github.com/0gfoundation/revm", rev = "71cbb675f562a6c1ac1fd4eda8618b8a921426b6", package = "op-revm" }
70+
71+
# revm = { path = "../revm/crates/revm" }
72+
# op-revm = { path = "../revm/crates/op-revm" }

crates/evm/src/eth/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ mod tests {
317317

318318
// precompile should NOT be available in early spec
319319
assert!(
320-
early_evm.precompiles_mut().get(&precompile_addr).is_none(),
320+
early_evm.precompiles_mut().get_stateless(&precompile_addr).is_none(),
321321
"{name} precompile at {precompile_addr:?} should NOT be available for early spec {early_spec:?}"
322322
);
323323

@@ -330,7 +330,7 @@ mod tests {
330330

331331
// precompile should be available in later spec
332332
assert!(
333-
later_evm.precompiles_mut().get(&precompile_addr).is_some(),
333+
later_evm.precompiles_mut().get_stateless(&precompile_addr).is_some(),
334334
"{name} precompile at {precompile_addr:?} should be available for later spec {later_spec:?}"
335335
);
336336
}

crates/evm/src/precompiles.rs

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use revm::{
1212
context::LocalContextTr,
1313
handler::{EthPrecompiles, PrecompileProvider},
1414
interpreter::{CallInput, Gas, InputsImpl, InstructionResult, InterpreterResult},
15-
precompile::{PrecompileError, PrecompileFn, PrecompileId, PrecompileResult, Precompiles},
15+
precompile::{stateful_precompiles::run_stateful_precompile, PrecompileError, PrecompileFn, PrecompileId, PrecompileResult, Precompiles},
1616
Context, Journal,
1717
};
1818

@@ -46,13 +46,13 @@ impl PrecompilesMap {
4646
{
4747
let dyn_precompiles = self.ensure_dynamic_precompiles();
4848

49-
// get the current precompile at the address
50-
if let Some(dyn_precompile) = dyn_precompiles.inner.remove(address) {
49+
// get the current stateless precompile at the address
50+
if let Some(dyn_precompile) = dyn_precompiles.stateless.remove(address) {
5151
// apply the transformation function
5252
let transformed = f(dyn_precompile);
5353

5454
// update the precompile at the address
55-
dyn_precompiles.inner.insert(*address, transformed);
55+
dyn_precompiles.stateless.insert(*address, transformed);
5656
}
5757
}
5858

@@ -87,8 +87,8 @@ impl PrecompilesMap {
8787
{
8888
let dyn_precompiles = self.ensure_dynamic_precompiles();
8989

90-
// apply the transformation to each precompile
91-
let entries = dyn_precompiles.inner.drain();
90+
// apply the transformation to each stateless precompile
91+
let entries = dyn_precompiles.stateless.drain();
9292
let mut new_map =
9393
HashMap::with_capacity_and_hasher(entries.size_hint().0, Default::default());
9494
for (addr, precompile) in entries {
@@ -100,7 +100,7 @@ impl PrecompilesMap {
100100
}
101101
}
102102

103-
dyn_precompiles.inner = new_map;
103+
dyn_precompiles.stateless = new_map;
104104
}
105105

106106
/// Applies a transformation to the precompile at the given address.
@@ -146,20 +146,20 @@ impl PrecompilesMap {
146146
F: FnOnce(Option<DynPrecompile>) -> Option<DynPrecompile>,
147147
{
148148
let dyn_precompiles = self.ensure_dynamic_precompiles();
149-
let current = dyn_precompiles.inner.get(address).cloned();
149+
let current = dyn_precompiles.stateless.get(address).cloned();
150150

151151
// apply the transformation function
152152
let result = f(current);
153153

154154
match result {
155155
Some(transformed) => {
156156
// insert the transformed precompile
157-
dyn_precompiles.inner.insert(*address, transformed);
157+
dyn_precompiles.stateless.insert(*address, transformed);
158158
dyn_precompiles.addresses.insert(*address);
159159
}
160160
None => {
161161
// remove the precompile if the transformation returned None
162-
dyn_precompiles.inner.remove(address);
162+
dyn_precompiles.stateless.remove(address);
163163
dyn_precompiles.addresses.remove(address);
164164
}
165165
}
@@ -280,11 +280,16 @@ impl PrecompilesMap {
280280
Cow::Owned(owned) => owned,
281281
};
282282

283-
for (&addr, pc) in static_precompiles.inner().iter() {
284-
dynamic.inner.insert(addr, DynPrecompile::from(*pc.precompile()));
283+
for (&addr, pc) in static_precompiles.stateless().iter() {
284+
dynamic.stateless.insert(addr, DynPrecompile::from(*pc.precompile()));
285285
dynamic.addresses.insert(addr);
286286
}
287287

288+
for addr in static_precompiles.stateful().iter() {
289+
dynamic.stateful.insert(*addr);
290+
dynamic.addresses.insert(*addr);
291+
}
292+
288293
self.precompiles = PrecompilesKind::Dynamic(dynamic);
289294
}
290295

@@ -308,12 +313,12 @@ impl PrecompilesMap {
308313
///
309314
/// This method first checks the static precompile map, and if not found,
310315
/// falls back to the dynamic lookup function (if set).
311-
pub fn get(&self, address: &Address) -> Option<impl Precompile + '_> {
316+
pub fn get_stateless(&self, address: &Address) -> Option<impl Precompile + '_> {
312317
// First check static precompiles
313318
let static_result = match &self.precompiles {
314-
PrecompilesKind::Builtin(precompiles) => precompiles.get(address).map(Either::Left),
319+
PrecompilesKind::Builtin(precompiles) => precompiles.get_stateless(address).map(Either::Left),
315320
PrecompilesKind::Dynamic(dyn_precompiles) => {
316-
dyn_precompiles.inner.get(address).map(Either::Right)
321+
dyn_precompiles.stateless.get(address).map(Either::Right)
317322
}
318323
};
319324

@@ -326,6 +331,14 @@ impl PrecompilesMap {
326331
let lookup = self.lookup.as_ref()?;
327332
lookup.lookup(address).map(Either::Right)
328333
}
334+
335+
/// Check if the given address is stateful precompile.
336+
pub fn is_stateful(&self, address: &Address) -> bool {
337+
match &self.precompiles {
338+
PrecompilesKind::Builtin(precompiles) => precompiles.is_stateful(address),
339+
PrecompilesKind::Dynamic(dyn_precompiles) => dyn_precompiles.stateful.contains(address),
340+
}
341+
}
329342
}
330343

331344
impl From<EthPrecompiles> for PrecompilesMap {
@@ -365,49 +378,56 @@ where
365378
context: &mut Context<BlockEnv, TxEnv, CfgEnv, DB, Journal<DB>, Chain>,
366379
address: &Address,
367380
inputs: &InputsImpl,
368-
_is_static: bool,
381+
is_static: bool,
369382
gas_limit: u64,
370383
) -> Result<Option<InterpreterResult>, String> {
371384
// Get the precompile at the address
372-
let Some(precompile) = self.get(address) else {
385+
let maybe_stateless = self.get_stateless(address);
386+
if maybe_stateless.is_none() && !self.is_stateful(address) {
373387
return Ok(None);
374-
};
388+
}
375389

376390
let mut result = InterpreterResult {
377391
result: InstructionResult::Return,
378392
gas: Gas::new(gas_limit),
379393
output: Bytes::new(),
380394
};
381395

382-
let (local, journal) = (&context.local, &mut context.journaled_state);
383-
384396
// Execute the precompile
385-
let r;
386397
let input_bytes = match &inputs.input {
387398
CallInput::SharedBuffer(range) => {
388399
// `map_or` does not work here as we use `r` to extend lifetime of the slice
389400
// and return it.
390401
#[allow(clippy::option_if_let_else)]
391-
if let Some(slice) = local.shared_memory_buffer_slice(range.clone()) {
392-
r = slice;
393-
&*r
402+
if let Some(slice) = context.local.shared_memory_buffer_slice(range.clone()) {
403+
slice.to_vec()
394404
} else {
395-
&[]
405+
vec![]
396406
}
397407
}
398-
CallInput::Bytes(bytes) => bytes.as_ref(),
408+
CallInput::Bytes(bytes) => bytes.0.to_vec(),
399409
};
400410

401-
let precompile_result = precompile.call(PrecompileInput {
402-
data: input_bytes,
403-
gas: gas_limit,
404-
caller: inputs.caller_address,
405-
value: inputs.call_value,
406-
internals: EvmInternals::new(journal, &context.block),
411+
let precompile_result = match maybe_stateless {
412+
Some(precompile) => precompile.call(PrecompileInput {
413+
data: &input_bytes,
414+
gas: gas_limit,
415+
caller: inputs.caller_address,
416+
value: inputs.call_value,
417+
internals: EvmInternals::new(&mut context.journaled_state, &context.block),
407418
target_address: inputs.target_address,
408419
bytecode_address: inputs.bytecode_address.expect("always set for precompile calls"),
409-
});
410-
420+
}),
421+
None => run_stateful_precompile(
422+
*address,
423+
&input_bytes,
424+
gas_limit,
425+
inputs.caller_address,
426+
inputs.call_value,
427+
is_static,
428+
context,
429+
),
430+
};
411431
match precompile_result {
412432
Ok(output) => {
413433
let underflow = result.gas.record_cost(output.gas_used);
@@ -437,7 +457,7 @@ where
437457
}
438458

439459
fn contains(&self, address: &Address) -> bool {
440-
self.get(address).is_some()
460+
self.get_stateless(address).is_some() || self.is_stateful(address)
441461
}
442462
}
443463

@@ -493,8 +513,10 @@ impl core::fmt::Debug for DynPrecompile {
493513
/// unlike the static `Precompiles` struct from revm.
494514
#[derive(Clone, Default)]
495515
pub struct DynPrecompiles {
496-
/// Precompiles
497-
inner: HashMap<Address, DynPrecompile>,
516+
/// Stateless Precompiles
517+
stateless: HashMap<Address, DynPrecompile>,
518+
/// Stateful Precompiles
519+
stateful: HashSet<Address>,
498520
/// Addresses of precompile
499521
addresses: HashSet<Address>,
500522
}
@@ -503,7 +525,7 @@ impl DynPrecompiles {
503525
/// Consumes the type and returns an iterator over the addresses and the corresponding
504526
/// precompile.
505527
pub fn into_precompiles(self) -> impl Iterator<Item = (Address, DynPrecompile)> {
506-
self.inner.into_iter()
528+
self.stateless.into_iter()
507529
}
508530
}
509531

@@ -793,7 +815,7 @@ mod tests {
793815
// using the dynamic precompiles interface
794816
let dyn_precompile = match &spec_precompiles.precompiles {
795817
PrecompilesKind::Dynamic(dyn_precompiles) => {
796-
dyn_precompiles.inner.get(&identity_address).unwrap()
818+
dyn_precompiles.stateless.get(&identity_address).unwrap()
797819
}
798820
_ => panic!("Expected dynamic precompiles"),
799821
};
@@ -827,7 +849,7 @@ mod tests {
827849
// get the modified precompile and check it
828850
let dyn_precompile = match &spec_precompiles.precompiles {
829851
PrecompilesKind::Dynamic(dyn_precompiles) => {
830-
dyn_precompiles.inner.get(&identity_address).unwrap()
852+
dyn_precompiles.stateless.get(&identity_address).unwrap()
831853
}
832854
_ => panic!("Expected dynamic precompiles"),
833855
};
@@ -931,11 +953,11 @@ mod tests {
931953

932954
// Test that static precompiles still work
933955
let identity_address = address!("0x0000000000000000000000000000000000000004");
934-
assert!(spec_precompiles.get(&identity_address).is_some());
956+
assert!(spec_precompiles.get_stateless(&identity_address).is_some());
935957

936958
// Test dynamic lookup for matching address
937959
let dynamic_address = address!("0xDEAD000000000000000000000000000000000001");
938-
let dynamic_precompile = spec_precompiles.get(&dynamic_address);
960+
let dynamic_precompile = spec_precompiles.get_stateless(&dynamic_address);
939961
assert!(dynamic_precompile.is_some(), "Dynamic precompile should be found");
940962

941963
// Execute the dynamic precompile
@@ -956,7 +978,7 @@ mod tests {
956978

957979
// Test non-matching address returns None
958980
let non_matching_address = address!("0x1234000000000000000000000000000000000001");
959-
assert!(spec_precompiles.get(&non_matching_address).is_none());
981+
assert!(spec_precompiles.get_stateless(&non_matching_address).is_none());
960982
}
961983

962984
#[test]
@@ -970,7 +992,7 @@ mod tests {
970992
let test_input = Bytes::from_static(b"test data");
971993
let gas_limit = 1000;
972994

973-
let precompile = spec_precompiles.get(&identity_address);
995+
let precompile = spec_precompiles.get_stateless(&identity_address);
974996
assert!(precompile.is_some(), "Identity precompile should exist");
975997

976998
let result = precompile
@@ -989,14 +1011,14 @@ mod tests {
9891011

9901012
let nonexistent_address = address!("0x0000000000000000000000000000000000000099");
9911013
assert!(
992-
spec_precompiles.get(&nonexistent_address).is_none(),
1014+
spec_precompiles.get_stateless(&nonexistent_address).is_none(),
9931015
"Non-existent precompile should not be found"
9941016
);
9951017

9961018
let mut dynamic_precompiles = spec_precompiles;
9971019
dynamic_precompiles.ensure_dynamic_precompiles();
9981020

999-
let dyn_precompile = dynamic_precompiles.get(&identity_address);
1021+
let dyn_precompile = dynamic_precompiles.get_stateless(&identity_address);
10001022
assert!(
10011023
dyn_precompile.is_some(),
10021024
"Identity precompile should exist after conversion to dynamic"

0 commit comments

Comments
 (0)