1- use crate :: mint_precompile :: NATIVE_MINT_PRECOMPILE_ADDRESS ;
1+ use crate :: config :: MintConfig ;
22use 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 ) ]
1710pub 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+ }
0 commit comments