1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, U256};
3use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
4
5use crate::{
6 arbsys::get_cached_l1_block_number,
7 storage_slot::{
8 root_slot, subspace_slot, ARBOS_STATE_ADDRESS, GENESIS_BLOCK_NUM_OFFSET,
9 L1_PRICING_SUBSPACE, L2_PRICING_SUBSPACE,
10 },
11};
12
13pub const NODE_INTERFACE_ADDRESS: Address = Address::new([
15 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16 0x00, 0x00, 0x00, 0xc8,
17]);
18
19const GAS_ESTIMATE_COMPONENTS: [u8; 4] = [0xc9, 0x4e, 0x6e, 0xeb];
21const GAS_ESTIMATE_L1_COMPONENT: [u8; 4] = [0x77, 0xd4, 0x88, 0xa2];
22const NITRO_GENESIS_BLOCK: [u8; 4] = [0x93, 0xa2, 0xfe, 0x21];
23const BLOCK_L1_NUM: [u8; 4] = [0x6f, 0x27, 0x5e, 0xf2];
24const L2_BLOCK_RANGE_FOR_L1: [u8; 4] = [0x48, 0xe7, 0xf8, 0x11];
25const ESTIMATE_RETRYABLE_TICKET: [u8; 4] = [0xc3, 0xdc, 0x58, 0x79];
26const CONSTRUCT_OUTBOX_PROOF: [u8; 4] = [0x42, 0x69, 0x63, 0x50];
27const FIND_BATCH_CONTAINING_BLOCK: [u8; 4] = [0x81, 0xf1, 0xad, 0xaf];
28const GET_L1_CONFIRMATIONS: [u8; 4] = [0xe5, 0xca, 0x23, 0x8c];
29const LEGACY_LOOKUP_MESSAGE_BATCH_PROOF: [u8; 4] = [0x89, 0x49, 0x62, 0x70];
30
31const L1_PRICE_PER_UNIT: u64 = 7;
33
34const L2_BASE_FEE: u64 = 2;
36
37const SLOAD_GAS: u64 = 800;
39const COPY_GAS: u64 = 3;
40
41const TX_DATA_NON_ZERO_GAS: u64 = 16;
43
44const GAS_ESTIMATION_L1_PRICE_PADDING_BIPS: u64 = 11000;
46
47pub fn create_nodeinterface_precompile() -> DynPrecompile {
48 DynPrecompile::new_stateful(PrecompileId::custom("nodeinterface"), handler)
49}
50
51fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
52 let gas_limit = input.gas;
53 let data = input.data;
54 if data.len() < 4 {
55 return Err(PrecompileError::other("input too short"));
56 }
57
58 let selector: [u8; 4] = [data[0], data[1], data[2], data[3]];
59
60 let result = match selector {
61 GAS_ESTIMATE_COMPONENTS => handle_gas_estimate_components(&mut input),
62 GAS_ESTIMATE_L1_COMPONENT => handle_gas_estimate_l1_component(&mut input),
63 NITRO_GENESIS_BLOCK => handle_nitro_genesis_block(&mut input),
64 BLOCK_L1_NUM => handle_block_l1_num(&mut input),
65 L2_BLOCK_RANGE_FOR_L1
69 | ESTIMATE_RETRYABLE_TICKET
70 | CONSTRUCT_OUTBOX_PROOF
71 | FIND_BATCH_CONTAINING_BLOCK
72 | GET_L1_CONFIRMATIONS
73 | LEGACY_LOOKUP_MESSAGE_BATCH_PROOF => {
74 Err(PrecompileError::other("method only available via RPC"))
75 }
76 _ => Err(PrecompileError::other("unknown selector")),
77 };
78 crate::gas_check(gas_limit, result)
79}
80
81fn handle_gas_estimate_components(input: &mut PrecompileInput<'_>) -> PrecompileResult {
90 let gas_limit = input.gas;
91 load_arbos(input)?;
92
93 let l1_price = sload_field(input, subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT))?;
94 let basefee = sload_field(input, subspace_slot(L2_PRICING_SUBSPACE, L2_BASE_FEE))?;
95
96 let gas_for_l1 = estimate_l1_gas(input, l1_price, basefee);
100
101 let mut out = Vec::with_capacity(128);
102 out.extend_from_slice(&U256::ZERO.to_be_bytes::<32>());
104 out.extend_from_slice(&U256::from(gas_for_l1).to_be_bytes::<32>());
106 out.extend_from_slice(&basefee.to_be_bytes::<32>());
108 out.extend_from_slice(&l1_price.to_be_bytes::<32>());
110
111 Ok(PrecompileOutput::new(
112 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
113 out.into(),
114 ))
115}
116
117fn handle_gas_estimate_l1_component(input: &mut PrecompileInput<'_>) -> PrecompileResult {
121 let gas_limit = input.gas;
122 load_arbos(input)?;
123
124 let l1_price = sload_field(input, subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT))?;
125 let basefee = sload_field(input, subspace_slot(L2_PRICING_SUBSPACE, L2_BASE_FEE))?;
126
127 let gas_for_l1 = estimate_l1_gas(input, l1_price, basefee);
128
129 let mut out = Vec::with_capacity(96);
130 out.extend_from_slice(&U256::from(gas_for_l1).to_be_bytes::<32>());
132 out.extend_from_slice(&basefee.to_be_bytes::<32>());
134 out.extend_from_slice(&l1_price.to_be_bytes::<32>());
136
137 Ok(PrecompileOutput::new(
138 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
139 out.into(),
140 ))
141}
142
143fn handle_nitro_genesis_block(input: &mut PrecompileInput<'_>) -> PrecompileResult {
145 let gas_limit = input.gas;
146 load_arbos(input)?;
147
148 let genesis_block_num = sload_field(input, root_slot(GENESIS_BLOCK_NUM_OFFSET))?;
149
150 Ok(PrecompileOutput::new(
151 (SLOAD_GAS + COPY_GAS).min(gas_limit),
152 genesis_block_num.to_be_bytes::<32>().to_vec().into(),
153 ))
154}
155
156fn handle_block_l1_num(input: &mut PrecompileInput<'_>) -> PrecompileResult {
161 let data = input.data;
162 if data.len() < 4 + 32 {
163 return Err(PrecompileError::other("input too short"));
164 }
165
166 let block_num: u64 = U256::from_be_slice(&data[4..36])
167 .try_into()
168 .unwrap_or(u64::MAX);
169
170 let l1_block = get_cached_l1_block_number(block_num).unwrap_or(0);
171
172 Ok(PrecompileOutput::new(
173 COPY_GAS.min(input.gas),
174 U256::from(l1_block).to_be_bytes::<32>().to_vec().into(),
175 ))
176}
177
178fn estimate_l1_gas(input: &PrecompileInput<'_>, l1_price: U256, basefee: U256) -> u64 {
183 let calldata_len = if input.data.len() > 4 + 32 + 32 + 32 + 32 {
186 let len_offset = 4 + 32 + 32 + 32;
187 let len_bytes = &input.data[len_offset..len_offset + 32];
188 U256::from_be_slice(len_bytes).try_into().unwrap_or(0u64)
189 } else {
190 0u64
191 };
192
193 if basefee.is_zero() || l1_price.is_zero() {
194 return 0;
195 }
196
197 let l1_fee = l1_price
199 .saturating_mul(U256::from(TX_DATA_NON_ZERO_GAS))
200 .saturating_mul(U256::from(calldata_len));
201
202 let padded = l1_fee.saturating_mul(U256::from(GAS_ESTIMATION_L1_PRICE_PADDING_BIPS))
204 / U256::from(10000u64);
205
206 let gas_for_l1 = padded / basefee;
208
209 gas_for_l1.try_into().unwrap_or(u64::MAX)
210}
211
212fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
213 input
214 .internals_mut()
215 .load_account(ARBOS_STATE_ADDRESS)
216 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
217 Ok(())
218}
219
220fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
221 let val = input
222 .internals_mut()
223 .sload(ARBOS_STATE_ADDRESS, slot)
224 .map_err(|_| PrecompileError::other("sload failed"))?;
225 Ok(val.data)
226}