1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, B256, U256};
3use alloy_sol_types::SolInterface;
4use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
5
6use crate::{
7 interfaces::IArbAggregator,
8 storage_slot::{
9 derive_subspace_key, map_slot, map_slot_b256, ARBOS_STATE_ADDRESS, CHAIN_OWNER_SUBSPACE,
10 L1_PRICING_SUBSPACE, ROOT_STORAGE_KEY,
11 },
12};
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 = 800;
27const SSTORE_GAS: u64 = 20_000;
28const SSTORE_ZERO_GAS: u64 = 5_000;
29const COPY_GAS: u64 = 3;
30
31const BATCH_POSTER_TABLE_KEY: &[u8] = &[0];
33const POSTER_ADDRS_KEY: &[u8] = &[0];
34const POSTER_INFO_KEY: &[u8] = &[1];
35const PAY_TO_OFFSET: u64 = 1;
36
37pub fn create_arbaggregator_precompile() -> DynPrecompile {
38 DynPrecompile::new_stateful(PrecompileId::custom("arbaggregator"), handler)
39}
40
41fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
42 let gas_limit = input.gas;
43 crate::init_precompile_gas(input.data.len());
44
45 let call = match IArbAggregator::ArbAggregatorCalls::abi_decode(input.data) {
46 Ok(c) => c,
47 Err(_) => return crate::burn_all_revert(gas_limit),
48 };
49
50 use IArbAggregator::ArbAggregatorCalls as Calls;
51 let result = match call {
52 Calls::getPreferredAggregator(_) => {
53 let mut out = Vec::with_capacity(64);
54 let mut addr_word = [0u8; 32];
55 addr_word[12..32].copy_from_slice(BATCH_POSTER_ADDRESS.as_slice());
56 out.extend_from_slice(&addr_word);
57 out.extend_from_slice(&U256::from(1u64).to_be_bytes::<32>());
58 Ok(PrecompileOutput::new(
59 (SLOAD_GAS + 6).min(gas_limit),
60 out.into(),
61 ))
62 }
63 Calls::getDefaultAggregator(_) => {
64 let mut out = [0u8; 32];
65 out[12..32].copy_from_slice(BATCH_POSTER_ADDRESS.as_slice());
66 Ok(PrecompileOutput::new(
67 (SLOAD_GAS + COPY_GAS).min(gas_limit),
68 out.to_vec().into(),
69 ))
70 }
71 Calls::getTxBaseFee(_) => Ok(PrecompileOutput::new(
72 (SLOAD_GAS + 6).min(gas_limit),
73 U256::ZERO.to_be_bytes::<32>().to_vec().into(),
74 )),
75 Calls::setTxBaseFee(_) => Ok(PrecompileOutput::new(
76 (SLOAD_GAS + 6).min(gas_limit),
77 vec![].into(),
78 )),
79 Calls::getFeeCollector(c) => handle_get_fee_collector(&mut input, c.batchPoster),
80 Calls::setFeeCollector(c) => {
81 handle_set_fee_collector(&mut input, c.batchPoster, c.newFeeCollector)
82 }
83 Calls::getBatchPosters(_) => handle_get_batch_posters(&mut input),
84 Calls::addBatchPoster(c) => handle_add_batch_poster(&mut input, c.newBatchPoster),
85 };
86 crate::gas_check(gas_limit, result)
87}
88
89fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
92 input
93 .internals_mut()
94 .load_account(ARBOS_STATE_ADDRESS)
95 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
96 Ok(())
97}
98
99fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
100 let val = input
101 .internals_mut()
102 .sload(ARBOS_STATE_ADDRESS, slot)
103 .map_err(|_| PrecompileError::other("sload failed"))?;
104 crate::charge_precompile_gas(SLOAD_GAS);
105 Ok(val.data)
106}
107
108fn sstore_field(
109 input: &mut PrecompileInput<'_>,
110 slot: U256,
111 value: U256,
112) -> Result<(), PrecompileError> {
113 input
114 .internals_mut()
115 .sstore(ARBOS_STATE_ADDRESS, slot, value)
116 .map_err(|_| PrecompileError::other("sstore failed"))?;
117 crate::charge_precompile_gas(SSTORE_GAS);
118 Ok(())
119}
120
121fn batch_poster_table_key() -> B256 {
123 let l1_pricing_key = derive_subspace_key(ROOT_STORAGE_KEY, L1_PRICING_SUBSPACE);
124 derive_subspace_key(l1_pricing_key.as_slice(), BATCH_POSTER_TABLE_KEY)
125}
126
127fn poster_addrs_key() -> B256 {
129 let bpt_key = batch_poster_table_key();
130 derive_subspace_key(bpt_key.as_slice(), POSTER_ADDRS_KEY)
131}
132
133fn poster_info_key(poster: Address) -> B256 {
135 let bpt_key = batch_poster_table_key();
136 let poster_info = derive_subspace_key(bpt_key.as_slice(), POSTER_INFO_KEY);
137 derive_subspace_key(poster_info.as_slice(), poster.as_slice())
138}
139
140fn is_chain_owner(input: &mut PrecompileInput<'_>, addr: Address) -> Result<bool, PrecompileError> {
142 let owner_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_OWNER_SUBSPACE);
143 let by_address_key = derive_subspace_key(owner_key.as_slice(), &[0]);
144 let addr_b256 = B256::left_padding_from(addr.as_slice());
145 let slot = map_slot_b256(by_address_key.as_slice(), &addr_b256);
146 let val = sload_field(input, slot)?;
147 Ok(val != U256::ZERO)
148}
149
150fn handle_get_fee_collector(input: &mut PrecompileInput<'_>, poster: Address) -> PrecompileResult {
151 let gas_limit = input.gas;
152 load_arbos(input)?;
153
154 let info_key = poster_info_key(poster);
155 let pay_to_slot = map_slot(info_key.as_slice(), PAY_TO_OFFSET);
156 let pay_to = sload_field(input, pay_to_slot)?;
157
158 Ok(PrecompileOutput::new(
160 (3 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
161 pay_to.to_be_bytes::<32>().to_vec().into(),
162 ))
163}
164
165fn handle_set_fee_collector(
167 input: &mut PrecompileInput<'_>,
168 poster: Address,
169 new_collector: Address,
170) -> PrecompileResult {
171 let gas_limit = input.gas;
172 let caller = input.caller;
173 load_arbos(input)?;
174
175 let info_key = poster_info_key(poster);
177 let pay_to_slot = map_slot(info_key.as_slice(), PAY_TO_OFFSET);
178 let old_collector_u256 = sload_field(input, pay_to_slot)?;
179 let old_collector_bytes = old_collector_u256.to_be_bytes::<32>();
180 let old_collector = Address::from_slice(&old_collector_bytes[12..32]);
181
182 if caller != poster && caller != old_collector {
184 let is_owner = is_chain_owner(input, caller)?;
185 if !is_owner {
186 return Err(PrecompileError::other(
187 "only a batch poster, its fee collector, or chain owner may change the fee collector",
188 ));
189 }
190 }
191
192 let new_val = U256::from_be_slice(new_collector.as_slice());
194 sstore_field(input, pay_to_slot, new_val)?;
195
196 let mut gas_used = 3 * SLOAD_GAS + SSTORE_GAS + 2 * COPY_GAS;
199 if caller != poster && caller != old_collector {
200 gas_used += SLOAD_GAS;
201 }
202 Ok(PrecompileOutput::new(
203 gas_used.min(gas_limit),
204 vec![].into(),
205 ))
206}
207
208fn handle_get_batch_posters(input: &mut PrecompileInput<'_>) -> PrecompileResult {
210 let gas_limit = input.gas;
211 load_arbos(input)?;
212
213 let addrs_key = poster_addrs_key();
214 let size_slot = map_slot(addrs_key.as_slice(), 0);
216 let size = sload_field(input, size_slot)?;
217 let count: u64 = size
218 .try_into()
219 .map_err(|_| PrecompileError::other("invalid address set size"))?;
220
221 const MAX_MEMBERS: u64 = 1024;
222 let count = count.min(MAX_MEMBERS);
223
224 let mut addresses = Vec::with_capacity(count as usize);
226 for i in 1..=count {
227 let member_slot = map_slot(addrs_key.as_slice(), i);
228 let val = sload_field(input, member_slot)?;
229 addresses.push(val);
230 }
231
232 let mut out = Vec::with_capacity(64 + 32 * addresses.len());
234 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
235 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
236 for addr_val in &addresses {
237 out.extend_from_slice(&addr_val.to_be_bytes::<32>());
238 }
239
240 let gas_used = (2 + count) * SLOAD_GAS + (2 + count) * COPY_GAS;
242 Ok(PrecompileOutput::new(gas_used.min(gas_limit), out.into()))
243}
244
245fn handle_add_batch_poster(
247 input: &mut PrecompileInput<'_>,
248 new_poster: Address,
249) -> PrecompileResult {
250 let gas_limit = input.gas;
251 let caller = input.caller;
252 load_arbos(input)?;
253
254 if !is_chain_owner(input, caller)? {
256 return Err(PrecompileError::other("must be called by chain owner"));
257 }
258
259 let addrs_key = poster_addrs_key();
260
261 let by_address_key = derive_subspace_key(addrs_key.as_slice(), &[0]);
263 let addr_hash = B256::left_padding_from(new_poster.as_slice());
264 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
265 let existing = sload_field(input, member_slot)?;
266
267 if existing != U256::ZERO {
268 return Ok(PrecompileOutput::new(
270 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
271 vec![].into(),
272 ));
273 }
274
275 let size_slot = map_slot(addrs_key.as_slice(), 0);
277 let size = sload_field(input, size_slot)?;
278 let size_u64: u64 = size
279 .try_into()
280 .map_err(|_| PrecompileError::other("invalid address set size"))?;
281 let new_size = size_u64 + 1;
282
283 let new_pos_slot = map_slot(addrs_key.as_slice(), new_size);
285 let addr_as_u256 = U256::from_be_slice(new_poster.as_slice());
286 sstore_field(input, new_pos_slot, addr_as_u256)?;
287
288 let slot_value = U256::from(new_size);
290 sstore_field(input, member_slot, slot_value)?;
291
292 sstore_field(input, size_slot, U256::from(new_size))?;
294
295 let info_key = poster_info_key(new_poster);
297 let pay_to_slot = map_slot(info_key.as_slice(), PAY_TO_OFFSET);
298 sstore_field(input, pay_to_slot, addr_as_u256)?;
299
300 let gas_used = 6 * SLOAD_GAS + SSTORE_ZERO_GAS + 4 * SSTORE_GAS + COPY_GAS;
305 Ok(PrecompileOutput::new(
306 gas_used.min(gas_limit),
307 vec![].into(),
308 ))
309}