arb_rpc/
nodeinterface_rpc.rs1use alloy_primitives::{address, Address, Bytes, B256, U256};
11
12pub const NODE_INTERFACE_ADDRESS: Address = address!("00000000000000000000000000000000000000c8");
14
15pub const SEL_GAS_ESTIMATE_COMPONENTS: [u8; 4] = [0xc9, 0x4e, 0x6e, 0xeb];
17pub const SEL_GAS_ESTIMATE_L1_COMPONENT: [u8; 4] = [0x77, 0xd4, 0x88, 0xa2];
18pub const SEL_L2_BLOCK_RANGE_FOR_L1: [u8; 4] = [0x48, 0xe7, 0xf8, 0x11];
19pub const SEL_GET_L1_CONFIRMATIONS: [u8; 4] = [0xe5, 0xca, 0x23, 0x8c];
20pub const SEL_FIND_BATCH_CONTAINING_BLOCK: [u8; 4] = [0x81, 0xf1, 0xad, 0xaf];
21pub const SEL_CONSTRUCT_OUTBOX_PROOF: [u8; 4] = [0x42, 0x69, 0x63, 0x50];
22
23pub fn unpack_mix_hash(mix: B256) -> (u64, u64, u64) {
26 let b = mix.0;
27 let send_count = u64::from_be_bytes(b[0..8].try_into().unwrap_or_default());
28 let l1_block = u64::from_be_bytes(b[8..16].try_into().unwrap_or_default());
29 let arbos_version = u64::from_be_bytes(b[16..24].try_into().unwrap_or_default());
30 (send_count, l1_block, arbos_version)
31}
32
33pub fn gas_estimate_data_len(input: &[u8]) -> u64 {
39 if input.len() < 4 + 32 * 4 {
40 return 0;
41 }
42 let len_start = 4 + 32 * 3;
43 let len_bytes = &input[len_start..len_start + 32];
44 U256::from_be_slice(len_bytes).try_into().unwrap_or(0u64)
45}
46
47pub fn encode_l2_block_range(first: u64, last: u64) -> Bytes {
49 let mut out = vec![0u8; 64];
50 out[24..32].copy_from_slice(&first.to_be_bytes());
51 out[56..64].copy_from_slice(&last.to_be_bytes());
52 Bytes::from(out)
53}
54
55pub fn encode_gas_estimate_components(
59 gas_total: u64,
60 gas_for_l1: u64,
61 basefee: U256,
62 l1_base_fee: U256,
63) -> Bytes {
64 let mut out = vec![0u8; 128];
65 out[24..32].copy_from_slice(&gas_total.to_be_bytes());
66 out[56..64].copy_from_slice(&gas_for_l1.to_be_bytes());
67 out[64..96].copy_from_slice(&basefee.to_be_bytes::<32>());
68 out[96..128].copy_from_slice(&l1_base_fee.to_be_bytes::<32>());
69 Bytes::from(out)
70}
71
72pub fn decode_single_u64_arg(input: &[u8]) -> Option<u64> {
76 if input.len() < 4 + 32 {
77 return None;
78 }
79 U256::from_be_slice(&input[4..36]).try_into().ok()
80}
81
82pub fn find_l2_block_range<F>(target_l1_block: u64, best: u64, mix_hash_of: F) -> Option<(u64, u64)>
89where
90 F: Fn(u64) -> Option<B256>,
91{
92 if best == 0 {
93 return None;
94 }
95
96 let mut lo = 0u64;
98 let mut hi = best;
99 while lo < hi {
100 let mid = lo + (hi - lo) / 2;
101 let mix = mix_hash_of(mid)?;
102 let (_, l1_bn, _) = unpack_mix_hash(mix);
103 if l1_bn < target_l1_block {
104 lo = mid + 1;
105 } else {
106 hi = mid;
107 }
108 }
109 let first = lo;
110 let first_mix = mix_hash_of(first)?;
112 let (_, first_l1, _) = unpack_mix_hash(first_mix);
113 if first_l1 != target_l1_block {
114 return None;
115 }
116
117 let mut lo = first;
119 let mut hi = best;
120 while lo < hi {
121 let mid = lo + (hi - lo).div_ceil(2);
122 let mix = mix_hash_of(mid)?;
123 let (_, l1_bn, _) = unpack_mix_hash(mix);
124 if l1_bn > target_l1_block {
125 hi = mid - 1;
126 } else {
127 lo = mid;
128 }
129 }
130 Some((first, lo))
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn unpack_mix_hash_layout() {
139 let mut mix = [0u8; 32];
140 mix[0..8].copy_from_slice(&42u64.to_be_bytes());
141 mix[8..16].copy_from_slice(&100u64.to_be_bytes());
142 mix[16..24].copy_from_slice(&30u64.to_be_bytes());
143 let (sc, l1, v) = unpack_mix_hash(B256::from(mix));
144 assert_eq!(sc, 42);
145 assert_eq!(l1, 100);
146 assert_eq!(v, 30);
147 }
148
149 #[test]
150 fn gas_estimate_data_len_parses_abi_length() {
151 let mut input = vec![0u8; 4 + 32 * 4 + 10];
152 input[0..4].copy_from_slice(&SEL_GAS_ESTIMATE_COMPONENTS);
153 let len_start = 4 + 96;
155 input[len_start + 24..len_start + 32].copy_from_slice(&10u64.to_be_bytes());
156 assert_eq!(gas_estimate_data_len(&input), 10);
157 }
158
159 #[test]
160 fn gas_estimate_data_len_short_input_zero() {
161 assert_eq!(gas_estimate_data_len(&[]), 0);
162 assert_eq!(gas_estimate_data_len(&[0u8; 100]), 0);
163 }
164
165 #[test]
166 fn encode_l2_block_range_pads_correctly() {
167 let out = encode_l2_block_range(5, 10);
168 assert_eq!(out.len(), 64);
169 assert_eq!(U256::from_be_slice(&out[0..32]), U256::from(5u64));
170 assert_eq!(U256::from_be_slice(&out[32..64]), U256::from(10u64));
171 }
172
173 #[test]
174 fn encode_gas_estimate_components_layout() {
175 let out = encode_gas_estimate_components(
176 100_000,
177 5_000,
178 U256::from(1_000_000u64),
179 U256::from(50_000_000u64),
180 );
181 assert_eq!(out.len(), 128);
182 assert_eq!(U256::from_be_slice(&out[0..32]), U256::from(100_000u64));
183 assert_eq!(U256::from_be_slice(&out[32..64]), U256::from(5_000u64));
184 assert_eq!(U256::from_be_slice(&out[64..96]), U256::from(1_000_000u64));
185 assert_eq!(
186 U256::from_be_slice(&out[96..128]),
187 U256::from(50_000_000u64)
188 );
189 }
190
191 #[test]
192 fn decode_single_u64_arg_reads_last_8_bytes() {
193 let mut input = vec![0u8; 4 + 32];
194 input[4 + 24..4 + 32].copy_from_slice(&12345u64.to_be_bytes());
195 assert_eq!(decode_single_u64_arg(&input), Some(12345));
196 }
197
198 #[test]
199 fn decode_single_u64_arg_rejects_short_input() {
200 assert_eq!(decode_single_u64_arg(&[0u8; 10]), None);
201 }
202
203 fn mix_with_l1_block(l1: u64) -> B256 {
204 let mut m = [0u8; 32];
205 m[8..16].copy_from_slice(&l1.to_be_bytes());
206 B256::from(m)
207 }
208
209 #[test]
210 fn find_l2_block_range_hits_exact_l1() {
211 let mix_hash_of = |l2: u64| Some(mix_with_l1_block(1000 + l2 / 3));
213 let range = find_l2_block_range(1001, 10, mix_hash_of);
214 assert_eq!(range, Some((3, 5)));
216 }
217
218 #[test]
219 fn find_l2_block_range_miss_returns_none() {
220 let mix_hash_of = |l2: u64| Some(mix_with_l1_block(1000 + l2 / 3));
221 assert_eq!(find_l2_block_range(9999, 10, mix_hash_of), None);
223 }
224
225 #[test]
226 fn find_l2_block_range_empty_chain() {
227 assert_eq!(find_l2_block_range(1, 0, |_| None), None);
228 }
229
230 #[test]
231 fn find_l2_block_range_first_block_match() {
232 let mix_hash_of = |l2: u64| Some(mix_with_l1_block(1000 + l2));
233 assert_eq!(find_l2_block_range(1000, 5, mix_hash_of), Some((0, 0)));
234 }
235
236 #[test]
237 fn find_l2_block_range_all_same_l1() {
238 let mix_hash_of = |_: u64| Some(mix_with_l1_block(42));
239 assert_eq!(find_l2_block_range(42, 5, mix_hash_of), Some((0, 5)));
240 }
241}