arb_precompiles/
arbaggregator.rs

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
15/// ArbAggregator precompile address (0x6d).
16pub 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            // 1-arg + 1-result-word: init covered the arg copy; body adds the
87            // result-copy as computation.
88            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            // 2-arg no-op returning empty: init already charged both arg words.
96            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
163/// Caller must be the batch poster, its current fee collector, or a chain owner.
164fn 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
252/// Caller must be a chain owner.
253fn 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}