1use std::sync::Arc;
2
3use alloy_consensus::{SignableTransaction, TxEip1559, TxEnvelope};
4use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
5use alloy_primitives::{Address, Bytes, ChainId, Signature, U256, keccak256};
6use alloy_sol_types::SolInterface;
7use arb_context::ArbPrecompileCtx;
8use arb_storage::ARBOS_STATE_ADDRESS;
9use revm::precompile::{PrecompileId, PrecompileOutput, PrecompileResult};
10
11use crate::{ArbPrecompileError, interfaces::INodeInterface};
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 SLOAD_GAS: u64 = 800;
20const COPY_GAS: u64 = 3;
21
22pub fn create_nodeinterface_precompile(ctx: Arc<ArbPrecompileCtx>) -> DynPrecompile {
23 DynPrecompile::new_stateful(PrecompileId::custom("nodeinterface"), move |input| {
24 handler(input, &ctx)
25 })
26}
27
28fn handler(mut input: PrecompileInput<'_>, ctx: &ArbPrecompileCtx) -> PrecompileResult {
29 let mut gas_used = 0u64;
30 let gas_limit = input.gas;
31 crate::init_precompile_gas(&mut gas_used, ctx, input.data.len());
32
33 let call = match INodeInterface::NodeInterfaceCalls::abi_decode(input.data) {
34 Ok(c) => c,
35 Err(_) => return crate::burn_all_revert(gas_limit),
36 };
37
38 use INodeInterface::NodeInterfaceCalls as Calls;
39 let result = match call {
40 Calls::gasEstimateComponents(_) => handle_gas_estimate_components(&mut input, ctx),
41 Calls::gasEstimateL1Component(_) => handle_gas_estimate_l1_component(&mut input, ctx),
42 Calls::nitroGenesisBlock(_) => handle_nitro_genesis_block(&mut input, ctx),
43 Calls::blockL1Num(c) => handle_block_l1_num(&input, ctx, c.l2BlockNum),
44 Calls::getL1Confirmations(_) => handle_zero_u64(&input),
45 Calls::findBatchContainingBlock(_) => handle_zero_u64(&input),
46 Calls::legacyLookupMessageBatchProof(_) => handle_legacy_lookup_empty(&input),
47 Calls::l2BlockRangeForL1(_)
48 | Calls::estimateRetryableTicket(_)
49 | Calls::constructOutboxProof(_) => Err(ArbPrecompileError::empty_revert(gas_used).into()),
50 };
51 crate::gas_check(ctx, gas_limit, gas_used, result)
52}
53
54fn handle_gas_estimate_components(
60 input: &mut PrecompileInput<'_>,
61 ctx: &ArbPrecompileCtx,
62) -> PrecompileResult {
63 let gas_limit = input.gas;
64 load_arbos(input)?;
65
66 let (l1_price, basefee, min_basefee, chain_id, brotli_level) =
67 read_estimate_fields(input, ctx)?;
68 let gas_for_l1 = estimate_l1_gas(
69 input,
70 l1_price,
71 basefee,
72 min_basefee,
73 chain_id,
74 brotli_level,
75 );
76
77 let mut out = Vec::with_capacity(128);
78 out.extend_from_slice(&U256::ZERO.to_be_bytes::<32>());
79 out.extend_from_slice(&U256::from(gas_for_l1).to_be_bytes::<32>());
80 out.extend_from_slice(&basefee.to_be_bytes::<32>());
81 out.extend_from_slice(&l1_price.to_be_bytes::<32>());
82
83 Ok(PrecompileOutput::new(
84 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
85 out.into(),
86 ))
87}
88
89fn handle_gas_estimate_l1_component(
93 input: &mut PrecompileInput<'_>,
94 ctx: &ArbPrecompileCtx,
95) -> PrecompileResult {
96 let gas_limit = input.gas;
97 load_arbos(input)?;
98
99 let (l1_price, basefee, min_basefee, chain_id, brotli_level) =
100 read_estimate_fields(input, ctx)?;
101 let gas_for_l1 = estimate_l1_gas(
102 input,
103 l1_price,
104 basefee,
105 min_basefee,
106 chain_id,
107 brotli_level,
108 );
109
110 let mut out = Vec::with_capacity(96);
111 out.extend_from_slice(&U256::from(gas_for_l1).to_be_bytes::<32>());
112 out.extend_from_slice(&basefee.to_be_bytes::<32>());
113 out.extend_from_slice(&l1_price.to_be_bytes::<32>());
114
115 Ok(PrecompileOutput::new(
116 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
117 out.into(),
118 ))
119}
120
121fn handle_nitro_genesis_block(
123 input: &mut PrecompileInput<'_>,
124 ctx: &ArbPrecompileCtx,
125) -> PrecompileResult {
126 let gas_limit = input.gas;
127 load_arbos(input)?;
128
129 let internals = input.internals_mut();
130 let arb_state = ctx
131 .block
132 .arbos_state(internals)
133 .map_err(ArbPrecompileError::fatal)?;
134 let genesis_block_num = arb_state
135 .genesis_block_num
136 .get(internals)
137 .map_err(ArbPrecompileError::fatal)?;
138
139 Ok(PrecompileOutput::new(
140 (SLOAD_GAS + COPY_GAS).min(gas_limit),
141 U256::from(genesis_block_num)
142 .to_be_bytes::<32>()
143 .to_vec()
144 .into(),
145 ))
146}
147
148fn handle_block_l1_num(
149 input: &PrecompileInput<'_>,
150 ctx: &ArbPrecompileCtx,
151 block_num: u64,
152) -> PrecompileResult {
153 let l1_block = ctx.block.cached_l1_block_number(block_num).unwrap_or(0);
154 Ok(PrecompileOutput::new(
155 COPY_GAS.min(input.gas),
156 U256::from(l1_block).to_be_bytes::<32>().to_vec().into(),
157 ))
158}
159
160fn handle_zero_u64(input: &PrecompileInput<'_>) -> PrecompileResult {
161 Ok(PrecompileOutput::new(
162 COPY_GAS.min(input.gas),
163 U256::ZERO.to_be_bytes::<32>().to_vec().into(),
164 ))
165}
166
167fn handle_legacy_lookup_empty(input: &PrecompileInput<'_>) -> PrecompileResult {
175 let mut out = vec![0u8; 0x160];
176 U256::from(0x140u64)
177 .to_be_bytes::<32>()
178 .iter()
179 .enumerate()
180 .for_each(|(i, b)| out[i] = *b);
181 U256::from(0x160u64)
182 .to_be_bytes::<32>()
183 .iter()
184 .enumerate()
185 .for_each(|(i, b)| out[0x100 + i] = *b);
186 Ok(PrecompileOutput::new(COPY_GAS.min(input.gas), out.into()))
187}
188
189fn read_estimate_fields(
190 input: &mut PrecompileInput<'_>,
191 ctx: &ArbPrecompileCtx,
192) -> Result<(U256, U256, U256, ChainId, u64), ArbPrecompileError> {
193 let internals = input.internals_mut();
194 let arb_state = ctx
195 .block
196 .arbos_state(internals)
197 .map_err(ArbPrecompileError::fatal)?;
198
199 let l1_price = arb_state
200 .l1_pricing_state
201 .price_per_unit(internals)
202 .map_err(ArbPrecompileError::fatal)?;
203 let basefee = arb_state
204 .l2_pricing_state
205 .base_fee_wei(internals)
206 .map_err(ArbPrecompileError::fatal)?;
207 let min_basefee = arb_state
208 .l2_pricing_state
209 .min_base_fee_wei(internals)
210 .map_err(ArbPrecompileError::fatal)?;
211 let chain_id_u256 = arb_state
212 .chain_id
213 .get(internals)
214 .map_err(ArbPrecompileError::fatal)?;
215 let chain_id: ChainId = chain_id_u256.try_into().unwrap_or(0);
216 let brotli_level = arb_state
217 .brotli_compression_level
218 .get(internals)
219 .map_err(ArbPrecompileError::fatal)?;
220
221 Ok((l1_price, basefee, min_basefee, chain_id, brotli_level))
222}
223
224fn estimate_l1_gas(
225 input: &PrecompileInput<'_>,
226 l1_price: U256,
227 basefee: U256,
228 min_basefee: U256,
229 chain_id: ChainId,
230 brotli_level: u64,
231) -> u64 {
232 let (to_addr, contract_creation, data) = match decode_estimate_args(input.data) {
233 Some(v) => v,
234 None => return 0,
235 };
236 compute_l1_gas_for_estimate(
237 chain_id,
238 to_addr,
239 contract_creation,
240 U256::ZERO,
241 data,
242 l1_price,
243 basefee,
244 min_basefee,
245 brotli_level,
246 )
247}
248
249pub fn compute_l1_gas_for_estimate(
253 chain_id: ChainId,
254 to: Address,
255 contract_creation: bool,
256 value: U256,
257 data: Bytes,
258 l1_price: U256,
259 basefee: U256,
260 min_basefee: U256,
261 brotli_level: u64,
262) -> u64 {
263 if basefee.is_zero() || l1_price.is_zero() {
264 return 0;
265 }
266 let tx_bytes = build_fake_tx_bytes(chain_id, to, contract_creation, value, data);
267 let raw_units = arbos::l1_pricing::poster_units_from_bytes(&tx_bytes, brotli_level);
268 let padded_units = raw_units
269 .saturating_add(arbos::l1_pricing::ESTIMATION_PADDING_UNITS)
270 .saturating_mul(10_000 + arbos::l1_pricing::ESTIMATION_PADDING_BASIS_POINTS)
271 / 10_000;
272 let poster_cost = l1_price.saturating_mul(U256::from(padded_units));
273 let posting_padded = poster_cost.saturating_mul(U256::from(11_000u64)) / U256::from(10_000u64);
274 let adjusted = basefee.saturating_mul(U256::from(7u64)) / U256::from(8u64);
275 let gas_price = if adjusted < min_basefee {
276 min_basefee
277 } else {
278 adjusted
279 };
280 if gas_price.is_zero() {
281 return 0;
282 }
283 (posting_padded / gas_price).try_into().unwrap_or(u64::MAX)
284}
285
286pub fn decode_estimate_args(data: &[u8]) -> Option<(Address, bool, Bytes)> {
289 if data.len() < 4 + 4 * 32 {
290 return None;
291 }
292 let to = Address::from_slice(&data[16..36]);
293 let creation = data[4 + 32 + 31] != 0;
294 let bytes_offset: usize = U256::from_be_slice(&data[4 + 64..4 + 96]).try_into().ok()?;
295 let bytes_pos = 4usize.checked_add(bytes_offset)?;
296 if data.len() < bytes_pos + 32 {
297 return None;
298 }
299 let bytes_len: usize = U256::from_be_slice(&data[bytes_pos..bytes_pos + 32])
300 .try_into()
301 .ok()?;
302 let data_start = bytes_pos + 32;
303 if data.len() < data_start + bytes_len {
304 return None;
305 }
306 Some((
307 to,
308 creation,
309 Bytes::copy_from_slice(&data[data_start..data_start + bytes_len]),
310 ))
311}
312
313fn hash_prefix_u64(input: &[u8]) -> u64 {
314 let [b0, b1, b2, b3, b4, b5, b6, b7, ..] = keccak256(input).0;
315 u64::from_be_bytes([b0, b1, b2, b3, b4, b5, b6, b7])
316}
317
318fn hash_prefix_u32(input: &[u8]) -> u32 {
319 let [b0, b1, b2, b3, ..] = keccak256(input).0;
320 u32::from_be_bytes([b0, b1, b2, b3])
321}
322
323pub fn build_fake_tx_bytes(
327 chain_id: ChainId,
328 to: Address,
329 contract_creation: bool,
330 value: U256,
331 data: Bytes,
332) -> Vec<u8> {
333 let nonce = hash_prefix_u64(b"Nonce");
334 let max_priority = u128::from(hash_prefix_u32(b"GasTipCap"));
335 let max_fee = u128::from(hash_prefix_u32(b"GasFeeCap"));
336 let gas_limit = u64::from(hash_prefix_u32(b"Gas"));
337 let r = U256::from_be_bytes(keccak256(b"R").0);
338 let s = U256::from_be_bytes(keccak256(b"S").0);
339
340 let kind = if contract_creation {
341 revm::primitives::TxKind::Create
342 } else {
343 revm::primitives::TxKind::Call(to)
344 };
345
346 let tx = TxEip1559 {
347 chain_id,
348 nonce,
349 gas_limit,
350 max_fee_per_gas: max_fee,
351 max_priority_fee_per_gas: max_priority,
352 to: kind,
353 value,
354 access_list: Default::default(),
355 input: data,
356 };
357
358 let signature = Signature::new(r, s, false);
359 let signed = tx.into_signed(signature);
360 use alloy_eips::eip2718::Encodable2718;
361 let envelope = TxEnvelope::Eip1559(signed);
362 envelope.encoded_2718()
363}
364
365fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), ArbPrecompileError> {
366 input
367 .internals_mut()
368 .load_account(ARBOS_STATE_ADDRESS)
369 .map_err(ArbPrecompileError::fatal)?;
370 Ok(())
371}