1use std::sync::Arc;
2
3use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
4use alloy_primitives::{Address, U256};
5use alloy_sol_types::SolInterface;
6use arb_context::ArbPrecompileCtx;
7use arb_storage::{
8 ARBOS_STATE_ADDRESS, STORAGE_READ_GAS, STORAGE_WRITE_GAS, STORAGE_WRITE_ZERO_GAS, write_cost,
9};
10use arbos::types::BATCH_POSTER_ADDRESS;
11use revm::precompile::{PrecompileId, PrecompileOutput, PrecompileResult};
12
13use crate::{ArbPrecompileError, interfaces::IArbAggregator};
14
15pub const ARBAGGREGATOR_ADDRESS: Address = Address::new([
17 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
18 0x00, 0x00, 0x00, 0x6d,
19]);
20
21const SLOAD_GAS: u64 = STORAGE_READ_GAS;
22const SSTORE_GAS: u64 = STORAGE_WRITE_GAS;
23const SSTORE_ZERO_GAS: u64 = STORAGE_WRITE_ZERO_GAS;
24const COPY_GAS: u64 = 3;
25
26pub fn create_arbaggregator_precompile(ctx: Arc<ArbPrecompileCtx>) -> DynPrecompile {
27 DynPrecompile::new_stateful(PrecompileId::custom("arbaggregator"), move |input| {
28 handler(input, &ctx)
29 })
30}
31
32fn handler(mut input: PrecompileInput<'_>, ctx: &ArbPrecompileCtx) -> PrecompileResult {
33 let mut gas_used = 0u64;
34 let gas_limit = input.gas;
35 crate::init_precompile_gas(&mut gas_used, ctx, input.data.len());
36
37 let call = match IArbAggregator::ArbAggregatorCalls::abi_decode(input.data) {
38 Ok(c) => c,
39 Err(_) => return crate::burn_all_revert(gas_limit),
40 };
41 if let Some(r) = crate::reject_nonpayable_value(input.value, input.data, gas_limit, &[]) {
42 return r;
43 }
44 if let Some(r) = crate::reject_static_write(
45 input.is_static,
46 input.data,
47 gas_limit,
48 &[
49 [0xdf, 0x41, 0xe1, 0xe2],
50 [0x29, 0x14, 0x97, 0x99],
51 [0x5b, 0xe6, 0x88, 0x8b],
52 ],
53 ) {
54 return r;
55 }
56 if let Some(r) = crate::reject_delegate_nonpure(
57 input.target_address != input.bytecode_address,
58 input.data,
59 gas_limit,
60 &[],
61 ) {
62 return r;
63 }
64
65 use IArbAggregator::ArbAggregatorCalls as Calls;
66 let result = match call {
67 Calls::getPreferredAggregator(_) => {
68 let mut out = Vec::with_capacity(64);
69 let mut addr_word = [0u8; 32];
70 addr_word[12..32].copy_from_slice(BATCH_POSTER_ADDRESS.as_slice());
71 out.extend_from_slice(&addr_word);
72 out.extend_from_slice(&U256::from(1u64).to_be_bytes::<32>());
73 crate::charge_computation(&mut gas_used, ctx, 2 * COPY_GAS);
74 Ok(PrecompileOutput::new(gas_used.min(gas_limit), out.into()))
75 }
76 Calls::getDefaultAggregator(_) => {
77 let mut out = [0u8; 32];
78 out[12..32].copy_from_slice(BATCH_POSTER_ADDRESS.as_slice());
79 crate::charge_computation(&mut gas_used, ctx, COPY_GAS);
80 Ok(PrecompileOutput::new(
81 gas_used.min(gas_limit),
82 out.to_vec().into(),
83 ))
84 }
85 Calls::getTxBaseFee(_) => {
86 crate::charge_computation(&mut gas_used, ctx, COPY_GAS);
89 Ok(PrecompileOutput::new(
90 gas_used.min(gas_limit),
91 U256::ZERO.to_be_bytes::<32>().to_vec().into(),
92 ))
93 }
94 Calls::setTxBaseFee(_) => {
95 Ok(PrecompileOutput::new(
97 gas_used.min(gas_limit),
98 vec![].into(),
99 ))
100 }
101 Calls::getFeeCollector(c) => {
102 handle_get_fee_collector(&mut input, &mut gas_used, c.batchPoster, ctx)
103 }
104 Calls::setFeeCollector(c) => handle_set_fee_collector(
105 &mut input,
106 &mut gas_used,
107 c.batchPoster,
108 c.newFeeCollector,
109 ctx,
110 ),
111 Calls::getBatchPosters(_) => handle_get_batch_posters(&mut input, &mut gas_used, ctx),
112 Calls::addBatchPoster(c) => {
113 handle_add_batch_poster(&mut input, &mut gas_used, c.newBatchPoster, ctx)
114 }
115 };
116 crate::gas_check(ctx, gas_limit, gas_used, result)
117}
118
119fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), ArbPrecompileError> {
120 input
121 .internals_mut()
122 .load_account(ARBOS_STATE_ADDRESS)
123 .map_err(ArbPrecompileError::fatal)?;
124 Ok(())
125}
126
127fn handle_get_fee_collector(
128 input: &mut PrecompileInput<'_>,
129 gas_used: &mut u64,
130 poster: Address,
131 ctx: &ArbPrecompileCtx,
132) -> PrecompileResult {
133 let gas_limit = input.gas;
134 load_arbos(input)?;
135 let internals = input.internals_mut();
136 let arb_state = ctx
137 .block
138 .arbos_state(internals)
139 .map_err(ArbPrecompileError::fatal)?;
140 let bpt = arb_state.l1_pricing_state.batch_poster_table();
141
142 let poster_state = match bpt.open_poster(internals, poster, false) {
143 Ok(state) => state,
144 Err(_) => {
145 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
146 return Err(ArbPrecompileError::empty_revert(*gas_used).into());
147 }
148 };
149 let pay_to = poster_state
150 .pay_to(internals)
151 .map_err(ArbPrecompileError::fatal)?;
152 crate::charge_storage_read(gas_used, ctx, 2 * SLOAD_GAS);
153 crate::charge_computation(gas_used, ctx, COPY_GAS);
154 Ok(PrecompileOutput::new(
155 (*gas_used).min(gas_limit),
156 U256::from_be_slice(pay_to.as_slice())
157 .to_be_bytes::<32>()
158 .to_vec()
159 .into(),
160 ))
161}
162
163fn handle_set_fee_collector(
165 input: &mut PrecompileInput<'_>,
166 gas_used: &mut u64,
167 poster: Address,
168 new_collector: Address,
169 ctx: &ArbPrecompileCtx,
170) -> PrecompileResult {
171 let gas_limit = input.gas;
172 let caller = input.caller;
173 load_arbos(input)?;
174 let internals = input.internals_mut();
175 let arb_state = ctx
176 .block
177 .arbos_state(internals)
178 .map_err(ArbPrecompileError::fatal)?;
179 let bpt = arb_state.l1_pricing_state.batch_poster_table();
180
181 let poster_state = match bpt.open_poster(internals, poster, false) {
182 Ok(state) => state,
183 Err(_) => {
184 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
185 return Err(ArbPrecompileError::empty_revert(*gas_used).into());
186 }
187 };
188 let old_collector = poster_state
189 .pay_to(internals)
190 .map_err(ArbPrecompileError::fatal)?;
191 crate::charge_storage_read(gas_used, ctx, 2 * SLOAD_GAS);
192
193 if caller != poster && caller != old_collector {
194 let is_owner = arb_state
195 .chain_owners
196 .is_member(internals, caller)
197 .map_err(ArbPrecompileError::fatal)?;
198 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
199 if !is_owner {
200 return Err(ArbPrecompileError::empty_revert(*gas_used).into());
201 }
202 }
203
204 poster_state
205 .set_pay_to(internals, new_collector)
206 .map_err(ArbPrecompileError::fatal)?;
207 crate::charge_storage_write(gas_used, ctx, write_cost(new_collector.is_zero()));
208
209 Ok(PrecompileOutput::new(
210 (*gas_used).min(gas_limit),
211 vec![].into(),
212 ))
213}
214
215fn handle_get_batch_posters(
216 input: &mut PrecompileInput<'_>,
217 gas_used: &mut u64,
218 ctx: &ArbPrecompileCtx,
219) -> PrecompileResult {
220 let gas_limit = input.gas;
221 load_arbos(input)?;
222 let internals = input.internals_mut();
223 let arb_state = ctx
224 .block
225 .arbos_state(internals)
226 .map_err(ArbPrecompileError::fatal)?;
227 let bpt = arb_state.l1_pricing_state.batch_poster_table();
228
229 const MAX_MEMBERS: u64 = 65_536;
230 let posters = bpt
231 .all_posters_capped(internals, MAX_MEMBERS)
232 .map_err(ArbPrecompileError::fatal)?;
233 let count = posters.len() as u64;
234
235 let mut out = Vec::with_capacity(64 + posters.len() * 32);
236 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
237 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
238 for addr in &posters {
239 let mut word = [0u8; 32];
240 word[12..32].copy_from_slice(addr.as_slice());
241 out.extend_from_slice(&word);
242 }
243
244 crate::charge_storage_read(gas_used, ctx, (1 + count) * SLOAD_GAS);
245 crate::charge_computation(gas_used, ctx, (2 + count) * COPY_GAS);
246 Ok(PrecompileOutput::new(
247 (*gas_used).min(gas_limit),
248 out.into(),
249 ))
250}
251
252fn handle_add_batch_poster(
254 input: &mut PrecompileInput<'_>,
255 gas_used: &mut u64,
256 new_poster: Address,
257 ctx: &ArbPrecompileCtx,
258) -> PrecompileResult {
259 let gas_limit = input.gas;
260 let caller = input.caller;
261 load_arbos(input)?;
262 let internals = input.internals_mut();
263 let arb_state = ctx
264 .block
265 .arbos_state(internals)
266 .map_err(ArbPrecompileError::fatal)?;
267
268 let is_owner = arb_state
269 .chain_owners
270 .is_member(internals, caller)
271 .map_err(ArbPrecompileError::fatal)?;
272 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
273 if !is_owner {
274 return Err(ArbPrecompileError::empty_revert(*gas_used).into());
275 }
276
277 let bpt = arb_state.l1_pricing_state.batch_poster_table();
278 let already = bpt
279 .contains_poster(internals, new_poster)
280 .map_err(ArbPrecompileError::fatal)?;
281 crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
282
283 if already {
284 return Ok(PrecompileOutput::new(
285 (*gas_used).min(gas_limit),
286 vec![].into(),
287 ));
288 }
289
290 bpt.add_poster(internals, new_poster, new_poster)
291 .map_err(ArbPrecompileError::fatal)?;
292
293 let addr_write = write_cost(new_poster.is_zero());
294 crate::charge_storage_read(gas_used, ctx, 4 * SLOAD_GAS);
295 crate::charge_storage_write(
296 gas_used,
297 ctx,
298 SSTORE_ZERO_GAS + addr_write + SSTORE_GAS + addr_write + SSTORE_GAS,
299 );
300 Ok(PrecompileOutput::new(
301 (*gas_used).min(gas_limit),
302 vec![].into(),
303 ))
304}