Skip to content

Commit c3c193a

Browse files
committed
changes
1 parent 1453be0 commit c3c193a

9 files changed

Lines changed: 147 additions & 104 deletions

File tree

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ reth-testing-utils = { git = "https://github.com/paradigmxyz/reth.git", tag = "v
7878
reth-db = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.7.0", default-features = false }
7979
reth-tasks = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.7.0", default-features = false }
8080

81+
revm = { version = "29.0.0", default-features = false }
82+
8183
# Alloy dependencies
8284
alloy = { version = "1.0.30", features = [
8385
"contract",

bin/ev-reth/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ ev-node = { path = "../../crates/node" }
1818
ev-common = { path = "../../crates/common" }
1919
evolve-ev-reth = { path = "../../crates/evolve" }
2020

21+
ev-precompiles = { path = "../../crates/ev-precompiles" }
22+
2123
# Reth CLI and core dependencies
2224
reth-cli-util.workspace = true
2325
reth-ethereum-cli.workspace = true

bin/ev-reth/src/builder.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ use reth_ethereum::{
2020
use reth_payload_builder::{EthBuiltPayload, PayloadBuilderError};
2121
use reth_provider::HeaderProvider;
2222
use reth_revm::cached::CachedReads;
23-
use serde::{Deserialize, Serialize};
2423
use std::sync::Arc;
2524
use tracing::info;
2625

2726
use crate::{attributes::EvolveEnginePayloadBuilderAttributes, EvolveEngineTypes};
2827
use evolve_ev_reth::config::set_current_block_gas_limit;
2928

30-
/// Evolve-specific command line arguments
31-
#[derive(Debug, Clone, Parser, PartialEq, Eq, Serialize, Deserialize, Default)]
29+
#[derive(Debug, Clone, Default, Parser)]
3230
pub struct EvolveArgs {
3331
/// Enable Evolve mode for the node (enabled by default)
3432
#[arg(
@@ -37,6 +35,10 @@ pub struct EvolveArgs {
3735
help = "Enable Evolve integration for transaction processing via Engine API"
3836
)]
3937
pub enable_evolve: bool,
38+
39+
/// Enable the native mint precompile.
40+
#[arg(long)]
41+
pub enable_mint_precompile: bool,
4042
}
4143

4244
/// Evolve payload service builder that integrates with the evolve payload builder

crates/ev-precompiles/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ license = "MIT"
88
[dependencies]
99
# Reth
1010
reth-primitives = { workspace = true }
11+
reth-revm = { workspace = true }
1112

1213
# Revm
13-
revm = { workspace = true, features = ["ethersdb", "optimism"] }
14+
revm = { workspace = true }
1415

1516
# EVM
1617
alloy-primitives = { workspace = true }
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
use alloy_primitives::{Address, U256};
3+
use std::collections::HashSet;
4+
use std::str::FromStr;
5+
6+
/// Configuration for the native mint precompile.
7+
#[derive(Clone, Debug)]
8+
pub struct MintConfig {
9+
/// The address of the native mint precompile.
10+
pub precompile_address: Address,
11+
/// A static list of addresses that are allowed to call the precompile.
12+
pub allow_list: HashSet<Address>,
13+
/// The maximum amount that can be minted in a single call.
14+
pub per_call_cap: U256,
15+
/// The maximum amount that can be minted in a single block.
16+
pub per_block_cap: Option<U256>,
17+
}
18+
19+
impl MintConfig {
20+
/// Creates a new `MintConfig` from environment variables.
21+
pub fn from_env() -> eyre::Result<Self> {
22+
let precompile_address = std::env::var("EV_MINT_PRECOMPILE_ADDR")
23+
.map(|s| Address::from_str(&s))??;
24+
25+
let allow_list = std::env::var("EV_MINT_ALLOWLIST")?
26+
.split(',')
27+
.map(|s| Address::from_str(s.trim()))
28+
.collect::<Result<HashSet<_>, _>>()?;
29+
30+
let per_call_cap = std::env::var("EV_MINT_PER_CALL_CAP")
31+
.map(|s| U256::from_str(&s))??;
32+
33+
let per_block_cap = std::env::var("EV_MINT_PER_BLOCK_CAP")
34+
.ok()
35+
.map(|s| U256::from_str(&s))
36+
.transpose()?;
37+
38+
Ok(Self {
39+
precompile_address,
40+
allow_list,
41+
per_call_cap,
42+
per_block_cap,
43+
})
44+
}
45+
}
Lines changed: 73 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,105 @@
1-
use crate::mint_precompile::NATIVE_MINT_PRECOMPILE_ADDRESS;
1+
use crate::config::MintConfig;
22
use alloy_primitives::{Address, U256};
3-
use bytes::Bytes;
4-
use revm::db::Database;
5-
use revm::primitives::{
6-
AccountInfo, CallInputs, CallOutcome, ExecutionResult, Output, TransactTo, B160,
3+
use revm::{
4+
context_interface::{ContextTr, JournalTr},
5+
inspector::Inspector,
6+
interpreter::{CallInputs, CallOutcome, Gas, InstructionResult, InterpreterResult},
77
};
8-
use revm::{DatabaseCommit, Evm, Inspector, JournaledState};
9-
use std::collections::HashSet;
10-
11-
/// An inspector that gates calls to the native mint precompile.
12-
///
13-
/// This inspector enforces an allowlist and various limits on the minting of the native token.
14-
/// It also performs the state mutation (crediting the balance) when the precompile call is
15-
/// successful.
8+
169
#[derive(Clone, Debug)]
1710
pub struct MintInspector {
18-
/// The address of the native mint precompile.
1911
precompile: Address,
20-
/// A static list of addresses that are allowed to call the precompile.
21-
allow: HashSet<Address>,
22-
/// The maximum amount that can be minted in a single call.
12+
allow: std::collections::HashSet<Address>,
2313
per_call_cap: U256,
24-
/// The maximum amount that can be minted in a single block.
2514
per_block_cap: Option<U256>,
26-
/// The total amount minted in the current block.
2715
minted_this_block: U256,
2816
}
2917

30-
impl<DB: Database> Inspector<DB> for MintInspector {
31-
fn call(
32-
&mut self,
33-
context: &mut revm::InnerEvmContext<DB>,
34-
inputs: &mut CallInputs,
35-
) -> Option<CallOutcome> {
36-
// Intercept calls to the native mint precompile.
37-
if inputs.contract != self.precompile {
18+
impl MintInspector {
19+
pub fn new(config: MintConfig) -> Self {
20+
Self {
21+
precompile: config.precompile_address,
22+
allow: config.allow_list,
23+
per_call_cap: config.per_call_cap,
24+
per_block_cap: config.per_block_cap,
25+
minted_this_block: U256::ZERO,
26+
}
27+
}
28+
29+
const MINT_CALLDATA_LEN: usize = 20 + 32;
30+
31+
fn revert_outcome(message: &str, inputs: &CallInputs) -> CallOutcome {
32+
CallOutcome::new(
33+
Self::revert_result(message),
34+
inputs.return_memory_offset.clone(),
35+
)
36+
}
37+
38+
fn revert_result(message: &str) -> InterpreterResult {
39+
InterpreterResult {
40+
result: InstructionResult::Revert,
41+
output: message.as_bytes().to_vec().into(),
42+
gas: Gas::new(0),
43+
}
44+
}
45+
}
46+
47+
impl<CTX> Inspector<CTX> for MintInspector
48+
where
49+
CTX: ContextTr,
50+
{
51+
fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
52+
if inputs.target_address != self.precompile {
3853
return None;
3954
}
4055

41-
// Check if the caller is authorized.
4256
if !self.allow.contains(&inputs.caller) {
43-
// Return a revert outcome if the caller is not authorized.
44-
let outcome = CallOutcome::new().with_revert(Bytes::from_static(b"unauthorized"));
45-
return Some(outcome);
57+
return Some(Self::revert_outcome("unauthorized", inputs));
4658
}
4759

48-
// Decode the amount from the input.
49-
let amount = U256::from_be_slice(&inputs.input[20..52]);
60+
let calldata = inputs.input.bytes(context);
61+
if calldata.len() != Self::MINT_CALLDATA_LEN {
62+
return Some(Self::revert_outcome("invalid input length", inputs));
63+
}
5064

51-
// Check if the amount exceeds the per-call cap.
65+
let amount = U256::from_be_slice(&calldata[20..Self::MINT_CALLDATA_LEN]);
5266
if amount > self.per_call_cap {
53-
let outcome = CallOutcome::new().with_revert(Bytes::from_static(b"over per-call cap"));
54-
return Some(outcome);
67+
return Some(Self::revert_outcome("over per-call cap", inputs));
5568
}
5669

57-
// Check if the amount exceeds the per-block cap.
58-
if let Some(per_block_cap) = self.per_block_cap {
59-
if self.minted_this_block + amount > per_block_cap {
60-
let outcome =
61-
CallOutcome::new().with_revert(Bytes::from_static(b"over per-block cap"));
62-
return Some(outcome);
70+
if let Some(cap) = self.per_block_cap {
71+
if self.minted_this_block + amount > cap {
72+
return Some(Self::revert_outcome("over per-block cap", inputs));
6373
}
6474
}
6575

6676
None
6777
}
6878

69-
fn call_end(
70-
&mut self,
71-
context: &mut revm::InnerEvmContext<DB>,
72-
inputs: &CallInputs,
73-
outcome: CallOutcome,
74-
) -> CallOutcome {
75-
// Only handle successful calls to the native mint precompile.
76-
if inputs.contract != self.precompile || outcome.is_revert() {
77-
return outcome;
79+
fn call_end(&mut self, context: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) {
80+
if inputs.target_address != self.precompile || !outcome.result.is_ok() {
81+
return;
7882
}
7983

80-
// Decode the recipient and amount from the input.
81-
let to = Address::from_slice(&inputs.input[0..20]);
82-
let amount = U256::from_be_slice(&inputs.input[20..52]);
84+
let calldata = inputs.input.bytes(context);
85+
if calldata.len() != Self::MINT_CALLDATA_LEN {
86+
outcome.result = Self::revert_result("invalid input length");
87+
return;
88+
}
8389

84-
// Credit the recipient's balance.
85-
// This is the core state mutation logic.
86-
let (account, _) = context.journaled_state.load_account(to, context.db).unwrap();
87-
account.info.balance += amount;
88-
self.minted_this_block += amount;
90+
let to = Address::from_slice(&calldata[..20]);
91+
let amount = U256::from_be_slice(&calldata[20..Self::MINT_CALLDATA_LEN]);
8992

90-
// Mark the account as touched so the state change is persisted.
91-
context.journaled_state.touch(&to);
93+
match context.journal_mut().load_account(to) {
94+
Ok(mut account_load) => {
95+
account_load.info.balance += amount;
96+
}
97+
Err(_) => {
98+
outcome.result = Self::revert_result("account load failed");
99+
return;
100+
}
101+
}
92102

93-
outcome
103+
self.minted_this_block += amount;
94104
}
95-
}
105+
}

crates/ev-precompiles/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
pub mod builder_ext;
21
pub mod config;
32
pub mod inspector_gate;
43
pub mod mint_precompile;

crates/ev-precompiles/src/mint_precompile.rs

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,19 @@
1-
2-
use alloy_primitives::{Address, U256};
3-
use bytes::Bytes;
4-
use revm::precompile::{Precompile, PrecompileError, PrecompileOutput};
5-
use std::str;
6-
7-
/// The address of the native mint precompile.
8-
pub const NATIVE_MINT_PRECOMPILE_ADDRESS: Address = Address::new([
9-
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
10-
0x00, 0x00, 0x00, 0xEF,
11-
]);
12-
13-
/// The ABI for the native mint precompile.
14-
///
15-
/// The precompile accepts a 52-byte buffer, structured as follows:
16-
/// - The first 20 bytes represent the recipient's address (`to`).
17-
/// - The next 32 bytes represent the amount to mint (`amount`).
18-
///
19-
/// ```solidity
20-
/// interface INativeMint {
21-
/// function mint(address to, uint256 amount) external;
22-
/// }
23-
/// ```
24-
pub const NATIVE_MINT_PRECOMPILE_ABI: &str = r#"[{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#;
1+
use alloy_primitives::Bytes;
2+
use revm::precompile::{PrecompileError, PrecompileOutput, PrecompileResult};
253

264
/// The gas cost for the native mint precompile.
275
pub const NATIVE_MINT_PRECOMPILE_GAS_COST: u64 = 5_000;
286

297
/// A stateless precompile for minting the native token.
30-
///
31-
/// This precompile is responsible for parsing the input, validating the gas, and returning a
32-
/// success or error. The actual state mutation (crediting the balance) is handled by the
33-
/// `MintInspector`.
34-
pub fn native_mint(input: &Bytes, gas_limit: u64) -> Result<PrecompileOutput, PrecompileError> {
35-
// Check if the gas limit is sufficient.
8+
pub fn native_mint(input: &[u8], gas_limit: u64) -> PrecompileResult {
369
if gas_limit < NATIVE_MINT_PRECOMPILE_GAS_COST {
3710
return Err(PrecompileError::OutOfGas);
3811
}
3912

40-
// The input should be exactly 52 bytes long: 20 bytes for the address and 32 bytes for the
41-
// amount.
4213
if input.len() != 52 {
43-
return Err(PrecompileError::Other(
44-
"invalid input length".to_string().into(),
45-
));
14+
return Err(PrecompileError::Other("invalid input length".to_string()));
4615
}
4716

48-
// Return success with the gas used. The actual minting is handled by the inspector.
4917
Ok(PrecompileOutput::new(
5018
NATIVE_MINT_PRECOMPILE_GAS_COST,
5119
Bytes::new(),

0 commit comments

Comments
 (0)