Skip to content

Commit 07bdc39

Browse files
committed
++
1 parent c3c193a commit 07bdc39

2 files changed

Lines changed: 170 additions & 35 deletions

File tree

crates/ev-precompiles/src/inspector_gate.rs

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::config::MintConfig;
1+
use crate::{
2+
config::MintConfig,
3+
mint_precompile::{parse_operation, Operation},
4+
};
25
use alloy_primitives::{Address, U256};
36
use revm::{
47
context_interface::{ContextTr, JournalTr},
@@ -13,6 +16,7 @@ pub struct MintInspector {
1316
per_call_cap: U256,
1417
per_block_cap: Option<U256>,
1518
minted_this_block: U256,
19+
burned_this_block: U256,
1620
}
1721

1822
impl MintInspector {
@@ -23,10 +27,12 @@ impl MintInspector {
2327
per_call_cap: config.per_call_cap,
2428
per_block_cap: config.per_block_cap,
2529
minted_this_block: U256::ZERO,
30+
burned_this_block: U256::ZERO,
2631
}
2732
}
2833

29-
const MINT_CALLDATA_LEN: usize = 20 + 32;
34+
const MINT_CALLDATA_LEN: usize = 4 + 20 + 32; // selector + address + amount
35+
const BURN_CALLDATA_LEN: usize = 4 + 32; // selector + amount
3036

3137
fn revert_outcome(message: &str, inputs: &CallInputs) -> CallOutcome {
3238
CallOutcome::new(
@@ -58,18 +64,42 @@ where
5864
}
5965

6066
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-
}
64-
65-
let amount = U256::from_be_slice(&calldata[20..Self::MINT_CALLDATA_LEN]);
66-
if amount > self.per_call_cap {
67-
return Some(Self::revert_outcome("over per-call cap", inputs));
68-
}
6967

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));
68+
// Parse operation type
69+
let operation = match parse_operation(&calldata) {
70+
Ok(op) => op,
71+
Err(_) => return Some(Self::revert_outcome("invalid operation", inputs)),
72+
};
73+
74+
// Validate length based on operation
75+
match operation {
76+
Operation::Mint => {
77+
if calldata.len() != Self::MINT_CALLDATA_LEN {
78+
return Some(Self::revert_outcome("invalid mint input length", inputs));
79+
}
80+
let amount = U256::from_be_slice(&calldata[24..Self::MINT_CALLDATA_LEN]);
81+
if amount > self.per_call_cap {
82+
return Some(Self::revert_outcome("over per-call cap", inputs));
83+
}
84+
if let Some(cap) = self.per_block_cap {
85+
if self.minted_this_block + amount > cap {
86+
return Some(Self::revert_outcome("over per-block cap", inputs));
87+
}
88+
}
89+
}
90+
Operation::Burn => {
91+
if calldata.len() != Self::BURN_CALLDATA_LEN {
92+
return Some(Self::revert_outcome("invalid burn input length", inputs));
93+
}
94+
let amount = U256::from_be_slice(&calldata[4..Self::BURN_CALLDATA_LEN]);
95+
if amount > self.per_call_cap {
96+
return Some(Self::revert_outcome("over per-call cap", inputs));
97+
}
98+
if let Some(cap) = self.per_block_cap {
99+
if self.burned_this_block + amount > cap {
100+
return Some(Self::revert_outcome("over per-block cap", inputs));
101+
}
102+
}
73103
}
74104
}
75105

@@ -82,24 +112,63 @@ where
82112
}
83113

84114
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-
}
89-
90-
let to = Address::from_slice(&calldata[..20]);
91-
let amount = U256::from_be_slice(&calldata[20..Self::MINT_CALLDATA_LEN]);
92115

93-
match context.journal_mut().load_account(to) {
94-
Ok(mut account_load) => {
95-
account_load.info.balance += amount;
96-
}
116+
// Parse operation type
117+
let operation = match parse_operation(&calldata) {
118+
Ok(op) => op,
97119
Err(_) => {
98-
outcome.result = Self::revert_result("account load failed");
120+
outcome.result = Self::revert_result("invalid operation");
99121
return;
100122
}
123+
};
124+
125+
match operation {
126+
Operation::Mint => {
127+
if calldata.len() != Self::MINT_CALLDATA_LEN {
128+
outcome.result = Self::revert_result("invalid mint input length");
129+
return;
130+
}
131+
132+
let to = Address::from_slice(&calldata[4..24]);
133+
let amount = U256::from_be_slice(&calldata[24..Self::MINT_CALLDATA_LEN]);
134+
135+
match context.journal_mut().load_account(to) {
136+
Ok(mut account_load) => {
137+
account_load.info.balance += amount;
138+
}
139+
Err(_) => {
140+
outcome.result = Self::revert_result("account load failed");
141+
return;
142+
}
143+
}
144+
145+
self.minted_this_block += amount;
146+
}
147+
Operation::Burn => {
148+
if calldata.len() != Self::BURN_CALLDATA_LEN {
149+
outcome.result = Self::revert_result("invalid burn input length");
150+
return;
151+
}
152+
153+
let amount = U256::from_be_slice(&calldata[4..Self::BURN_CALLDATA_LEN]);
154+
155+
// Burn from caller's balance
156+
match context.journal_mut().load_account(inputs.caller) {
157+
Ok(mut account_load) => {
158+
if account_load.info.balance < amount {
159+
outcome.result = Self::revert_result("insufficient balance");
160+
return;
161+
}
162+
account_load.info.balance -= amount;
163+
}
164+
Err(_) => {
165+
outcome.result = Self::revert_result("account load failed");
166+
return;
167+
}
168+
}
169+
170+
self.burned_this_block += amount;
171+
}
101172
}
102-
103-
self.minted_this_block += amount;
104173
}
105174
}
Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,87 @@
11
use alloy_primitives::Bytes;
22
use revm::precompile::{PrecompileError, PrecompileOutput, PrecompileResult};
33

4-
/// The gas cost for the native mint precompile.
5-
pub const NATIVE_MINT_PRECOMPILE_GAS_COST: u64 = 5_000;
4+
/// The gas cost for the native mint/burn precompile.
5+
pub const NATIVE_MINT_BURN_PRECOMPILE_GAS_COST: u64 = 5_000;
66

7-
/// A stateless precompile for minting the native token.
8-
pub fn native_mint(input: &[u8], gas_limit: u64) -> PrecompileResult {
9-
if gas_limit < NATIVE_MINT_PRECOMPILE_GAS_COST {
7+
/// Function selectors (first 4 bytes of keccak256 hash)
8+
/// mint(address,uint256) -> 0x40c10f19
9+
const MINT_SELECTOR: [u8; 4] = [0x40, 0xc1, 0x0f, 0x19];
10+
/// burn(uint256) -> 0x42966c68
11+
const BURN_SELECTOR: [u8; 4] = [0x42, 0x96, 0x6c, 0x68];
12+
13+
#[derive(Debug, Clone, Copy, PartialEq)]
14+
pub enum Operation {
15+
Mint,
16+
Burn,
17+
}
18+
19+
/// A stateless precompile for minting and burning the native token.
20+
///
21+
/// Supports two operations via function selectors:
22+
///
23+
/// **mint(address,uint256)** - Mints tokens to a specified address
24+
/// - Selector: 0x40c10f19
25+
/// - Input: 4 bytes selector + 20 bytes address + 32 bytes amount (56 bytes total)
26+
/// - Effect: Increases balance of target address by amount
27+
///
28+
/// **burn(uint256)** - Burns tokens from the caller's balance
29+
/// - Selector: 0x42966c68
30+
/// - Input: 4 bytes selector + 32 bytes amount (36 bytes total)
31+
/// - Effect: Decreases caller's balance by amount
32+
pub fn native_mint_burn(input: &[u8], gas_limit: u64) -> PrecompileResult {
33+
if gas_limit < NATIVE_MINT_BURN_PRECOMPILE_GAS_COST {
1034
return Err(PrecompileError::OutOfGas);
1135
}
1236

13-
if input.len() != 52 {
14-
return Err(PrecompileError::Other("invalid input length".to_string()));
37+
if input.len() < 4 {
38+
return Err(PrecompileError::Other("input too short".to_string()));
39+
}
40+
41+
let selector = &input[0..4];
42+
43+
match selector {
44+
s if s == MINT_SELECTOR => {
45+
// mint(address,uint256) - expects 56 bytes total
46+
if input.len() != 56 {
47+
return Err(PrecompileError::Other(
48+
"invalid mint input length".to_string(),
49+
));
50+
}
51+
}
52+
s if s == BURN_SELECTOR => {
53+
// burn(uint256) - expects 36 bytes total
54+
if input.len() != 36 {
55+
return Err(PrecompileError::Other(
56+
"invalid burn input length".to_string(),
57+
));
58+
}
59+
}
60+
_ => {
61+
return Err(PrecompileError::Other(
62+
"unknown function selector".to_string(),
63+
));
64+
}
1565
}
1666

1767
Ok(PrecompileOutput::new(
18-
NATIVE_MINT_PRECOMPILE_GAS_COST,
68+
NATIVE_MINT_BURN_PRECOMPILE_GAS_COST,
1969
Bytes::new(),
2070
))
2171
}
72+
73+
/// Parse the operation type from input
74+
pub fn parse_operation(input: &[u8]) -> Result<Operation, PrecompileError> {
75+
if input.len() < 4 {
76+
return Err(PrecompileError::Other("input too short".to_string()));
77+
}
78+
79+
let selector = &input[0..4];
80+
match selector {
81+
s if s == MINT_SELECTOR => Ok(Operation::Mint),
82+
s if s == BURN_SELECTOR => Ok(Operation::Burn),
83+
_ => Err(PrecompileError::Other(
84+
"unknown function selector".to_string(),
85+
)),
86+
}
87+
}

0 commit comments

Comments
 (0)