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