1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, U256};
3use alloy_sol_types::SolInterface;
4use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
5
6use crate::{
7 interfaces::IArbOwnerPublic,
8 storage_slot::{
9 derive_subspace_key, map_slot, map_slot_b256, root_slot, subspace_slot,
10 ARBOS_STATE_ADDRESS, CHAIN_OWNER_SUBSPACE, FEATURES_SUBSPACE,
11 FILTERED_FUNDS_RECIPIENT_OFFSET, L1_PRICING_SUBSPACE,
12 NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET, NATIVE_TOKEN_SUBSPACE, PROGRAMS_SUBSPACE,
13 ROOT_STORAGE_KEY, TRANSACTION_FILTERER_SUBSPACE, TX_FILTERING_ENABLED_FROM_TIME_OFFSET,
14 },
15};
16
17pub const ARBOWNERPUBLIC_ADDRESS: Address = Address::new([
19 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
20 0x00, 0x00, 0x00, 0x6b,
21]);
22
23const INITIAL_MAX_FRAGMENT_COUNT: u8 = 2;
24const ARBOS_VERSION_STYLUS_CONTRACT_LIMIT: u64 = 60;
26const ARBOS_VERSION_COLLECT_TIPS: u64 = 60;
28const COLLECT_TIPS_OFFSET: u64 = 11;
30
31const NETWORK_FEE_ACCOUNT_OFFSET: u64 = 3;
33const INFRA_FEE_ACCOUNT_OFFSET: u64 = 6;
34const BROTLI_COMPRESSION_LEVEL_OFFSET: u64 = 7;
35const UPGRADE_VERSION_OFFSET: u64 = 1;
36const UPGRADE_TIMESTAMP_OFFSET: u64 = 2;
37
38const L1_GAS_FLOOR_PER_TOKEN: u64 = 12;
40
41const SLOAD_GAS: u64 = 800;
42const SSTORE_GAS: u64 = 20_000;
43const COPY_GAS: u64 = 3;
44
45pub fn create_arbownerpublic_precompile() -> DynPrecompile {
46 DynPrecompile::new_stateful(PrecompileId::custom("arbownerpublic"), handler)
47}
48
49fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
50 let gas_limit = input.gas;
51 crate::init_precompile_gas(input.data.len());
52
53 let call = match IArbOwnerPublic::ArbOwnerPublicCalls::abi_decode(input.data) {
54 Ok(c) => c,
55 Err(_) => return crate::burn_all_revert(gas_limit),
56 };
57
58 use IArbOwnerPublic::ArbOwnerPublicCalls as Calls;
59 let result = match call {
60 Calls::getNetworkFeeAccount(_) => read_state_field(&mut input, NETWORK_FEE_ACCOUNT_OFFSET),
61 Calls::getInfraFeeAccount(_) => {
62 if crate::get_arbos_version() < 6 {
63 read_state_field(&mut input, NETWORK_FEE_ACCOUNT_OFFSET)
64 } else {
65 read_state_field(&mut input, INFRA_FEE_ACCOUNT_OFFSET)
66 }
67 }
68 Calls::getBrotliCompressionLevel(_) => {
69 read_state_field(&mut input, BROTLI_COMPRESSION_LEVEL_OFFSET)
70 }
71 Calls::getScheduledUpgrade(_) => handle_scheduled_upgrade(&mut input),
72 Calls::isChainOwner(c) => handle_is_chain_owner(&mut input, c.addr),
73 Calls::getAllChainOwners(_) => handle_get_all_members(&mut input),
74 Calls::rectifyChainOwner(c) => handle_rectify_chain_owner(&mut input, c.ownerToRectify),
75 Calls::isNativeTokenOwner(c) => {
76 handle_is_set_member(&mut input, NATIVE_TOKEN_SUBSPACE, c.addr)
77 }
78 Calls::isTransactionFilterer(c) => {
79 handle_is_set_member(&mut input, TRANSACTION_FILTERER_SUBSPACE, c.filterer)
80 }
81 Calls::getAllNativeTokenOwners(_) => {
82 handle_get_all_set_members(&mut input, NATIVE_TOKEN_SUBSPACE)
83 }
84 Calls::getAllTransactionFilterers(_) => {
85 handle_get_all_set_members(&mut input, TRANSACTION_FILTERER_SUBSPACE)
86 }
87 Calls::getNativeTokenManagementFrom(_) => {
88 read_state_field(&mut input, NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET)
89 }
90 Calls::getTransactionFilteringFrom(_) => {
91 read_state_field(&mut input, TX_FILTERING_ENABLED_FROM_TIME_OFFSET)
92 }
93 Calls::getFilteredFundsRecipient(_) => {
94 read_state_field(&mut input, FILTERED_FUNDS_RECIPIENT_OFFSET)
95 }
96 Calls::isCalldataPriceIncreaseEnabled(_) => {
97 load_arbos(&mut input)?;
98 let features_key = derive_subspace_key(ROOT_STORAGE_KEY, FEATURES_SUBSPACE);
99 let features_slot = map_slot(features_key.as_slice(), 0);
100 let features = sload_field(&mut input, features_slot)?;
101 let enabled = features & U256::from(1);
102 Ok(PrecompileOutput::new(
103 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
104 enabled.to_be_bytes::<32>().to_vec().into(),
105 ))
106 }
107 Calls::getParentGasFloorPerToken(_) => {
108 load_arbos(&mut input)?;
109 let field_slot = subspace_slot(L1_PRICING_SUBSPACE, L1_GAS_FLOOR_PER_TOKEN);
110 let value = sload_field(&mut input, field_slot)?;
111 Ok(PrecompileOutput::new(
112 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
113 value.to_be_bytes::<32>().to_vec().into(),
114 ))
115 }
116 Calls::getMaxStylusContractFragments(_) => handle_max_stylus_fragments(&mut input),
117 Calls::getCollectTips(_) => handle_get_collect_tips(&mut input),
118 };
119 crate::gas_check(gas_limit, result)
120}
121
122fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
125 input
126 .internals_mut()
127 .load_account(ARBOS_STATE_ADDRESS)
128 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
129 Ok(())
130}
131
132fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
133 let val = input
134 .internals_mut()
135 .sload(ARBOS_STATE_ADDRESS, slot)
136 .map_err(|_| PrecompileError::other("sload failed"))?;
137 crate::charge_precompile_gas(SLOAD_GAS);
138 Ok(val.data)
139}
140
141fn sstore_field(
142 input: &mut PrecompileInput<'_>,
143 slot: U256,
144 value: U256,
145) -> Result<(), PrecompileError> {
146 input
147 .internals_mut()
148 .sstore(ARBOS_STATE_ADDRESS, slot, value)
149 .map_err(|_| PrecompileError::other("sstore failed"))?;
150 crate::charge_precompile_gas(SSTORE_GAS);
151 Ok(())
152}
153
154fn read_state_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
155 let gas_limit = input.gas;
156 load_arbos(input)?;
157
158 let value = sload_field(input, root_slot(offset))?;
159 Ok(PrecompileOutput::new(
160 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
161 value.to_be_bytes::<32>().to_vec().into(),
162 ))
163}
164
165fn handle_scheduled_upgrade(input: &mut PrecompileInput<'_>) -> PrecompileResult {
166 let gas_limit = input.gas;
167 load_arbos(input)?;
168
169 let version = sload_field(input, root_slot(UPGRADE_VERSION_OFFSET))?;
170 let timestamp = sload_field(input, root_slot(UPGRADE_TIMESTAMP_OFFSET))?;
171
172 let mut out = Vec::with_capacity(64);
173 out.extend_from_slice(&version.to_be_bytes::<32>());
174 out.extend_from_slice(×tamp.to_be_bytes::<32>());
175
176 Ok(PrecompileOutput::new(
178 (3 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
179 out.into(),
180 ))
181}
182
183fn handle_rectify_chain_owner(input: &mut PrecompileInput<'_>, addr: Address) -> PrecompileResult {
184 let gas_limit = input.gas;
185 load_arbos(input)?;
186
187 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_OWNER_SUBSPACE);
188 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
189 let addr_hash = alloy_primitives::B256::left_padding_from(addr.as_slice());
190
191 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
193 let slot_val = sload_field(input, member_slot)?;
194 if slot_val == U256::ZERO {
195 return Err(PrecompileError::other("not an owner"));
196 }
197
198 let slot_idx: u64 = slot_val
200 .try_into()
201 .map_err(|_| PrecompileError::other("invalid slot"))?;
202 let at_slot_key = map_slot(set_key.as_slice(), slot_idx);
203 let at_slot_val = sload_field(input, at_slot_key)?;
204 let size_slot = map_slot(set_key.as_slice(), 0);
205 let size: u64 = sload_field(input, size_slot)?
206 .try_into()
207 .map_err(|_| PrecompileError::other("invalid size"))?;
208
209 let addr_as_u256 = U256::from_be_slice(addr.as_slice());
211 if at_slot_val == addr_as_u256 && slot_idx <= size {
212 return Err(PrecompileError::other("already correctly mapped"));
213 }
214
215 sstore_field(input, member_slot, U256::ZERO)?;
217
218 let new_size = size + 1;
220 let new_pos_slot = map_slot(set_key.as_slice(), new_size);
221 sstore_field(input, new_pos_slot, addr_as_u256)?;
222 sstore_field(input, member_slot, U256::from(new_size))?;
223 sstore_field(input, size_slot, U256::from(new_size))?;
224
225 let topic0 = alloy_primitives::keccak256("ChainOwnerRectified(address)");
227 input
228 .internals_mut()
229 .log(alloy_primitives::Log::new_unchecked(
230 ARBOWNERPUBLIC_ADDRESS,
231 vec![topic0],
232 addr_hash.0.to_vec().into(),
233 ));
234
235 const SSTORE_ZERO_GAS: u64 = 5_000;
236 const RECTIFY_EVENT_GAS: u64 = 1_006; let gas_used =
238 SLOAD_GAS + 7 * SLOAD_GAS + SSTORE_ZERO_GAS + 3 * SSTORE_GAS + RECTIFY_EVENT_GAS + COPY_GAS;
239 Ok(PrecompileOutput::new(
240 gas_used.min(gas_limit),
241 Vec::new().into(),
242 ))
243}
244
245fn handle_is_chain_owner(input: &mut PrecompileInput<'_>, addr: Address) -> PrecompileResult {
246 let gas_limit = input.gas;
247 load_arbos(input)?;
248
249 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_OWNER_SUBSPACE);
251 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
252
253 let addr_as_b256 = alloy_primitives::B256::left_padding_from(addr.as_slice());
254 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_as_b256);
255
256 let value = sload_field(input, member_slot)?;
257 let is_owner = value != U256::ZERO;
258
259 let result = if is_owner {
260 U256::from(1u64)
261 } else {
262 U256::ZERO
263 };
264
265 Ok(PrecompileOutput::new(
267 (2 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
268 result.to_be_bytes::<32>().to_vec().into(),
269 ))
270}
271
272fn handle_get_all_members(input: &mut PrecompileInput<'_>) -> PrecompileResult {
273 let gas_limit = input.gas;
274 load_arbos(input)?;
275
276 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_OWNER_SUBSPACE);
278 let size_slot = map_slot(set_key.as_slice(), 0);
279 let size = sload_field(input, size_slot)?;
280 let count: u64 = size.try_into().unwrap_or(0);
281
282 let max_members = count.min(256); let mut out = Vec::with_capacity(64 + max_members as usize * 32);
285 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
286 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
287
288 for i in 0..max_members {
289 let member_slot = map_slot(set_key.as_slice(), i + 1);
290 let addr_val = sload_field(input, member_slot)?;
291 out.extend_from_slice(&addr_val.to_be_bytes::<32>());
292 }
293
294 Ok(PrecompileOutput::new(
296 ((2 + max_members) * SLOAD_GAS + (2 + max_members) * COPY_GAS).min(gas_limit),
297 out.into(),
298 ))
299}
300
301fn handle_is_set_member(
302 input: &mut PrecompileInput<'_>,
303 subspace: &[u8],
304 addr: Address,
305) -> PrecompileResult {
306 let gas_limit = input.gas;
307 load_arbos(input)?;
308
309 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, subspace);
310 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
311 let addr_hash = alloy_primitives::B256::left_padding_from(addr.as_slice());
312 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
313 let value = sload_field(input, member_slot)?;
314 let is_member = if value != U256::ZERO {
315 U256::from(1u64)
316 } else {
317 U256::ZERO
318 };
319
320 Ok(PrecompileOutput::new(
322 (2 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
323 is_member.to_be_bytes::<32>().to_vec().into(),
324 ))
325}
326
327fn handle_get_all_set_members(
328 input: &mut PrecompileInput<'_>,
329 subspace: &[u8],
330) -> PrecompileResult {
331 let gas_limit = input.gas;
332 load_arbos(input)?;
333
334 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, subspace);
335 let size_slot = map_slot(set_key.as_slice(), 0);
336 let size = sload_field(input, size_slot)?;
337 let count: u64 = size.try_into().unwrap_or(0);
338 let max_members = count.min(65536);
339
340 let mut out = Vec::with_capacity(64 + max_members as usize * 32);
341 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
342 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
343
344 for i in 0..max_members {
345 let member_slot = map_slot(set_key.as_slice(), i + 1);
346 let addr_val = sload_field(input, member_slot)?;
347 out.extend_from_slice(&addr_val.to_be_bytes::<32>());
348 }
349
350 Ok(PrecompileOutput::new(
352 ((2 + max_members) * SLOAD_GAS + (2 + max_members) * COPY_GAS).min(gas_limit),
353 out.into(),
354 ))
355}
356
357fn handle_max_stylus_fragments(input: &mut PrecompileInput<'_>) -> PrecompileResult {
358 let gas_limit = input.gas;
359 if crate::get_arbos_version() < ARBOS_VERSION_STYLUS_CONTRACT_LIMIT {
360 return Ok(PrecompileOutput::new(
361 (SLOAD_GAS + COPY_GAS).min(gas_limit),
362 vec![0u8; 32].into(),
363 ));
364 }
365 load_arbos(input)?;
366 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
367 let params_key = derive_subspace_key(programs_key.as_slice(), &[0]);
368 let params_slot = map_slot(params_key.as_slice(), 0);
369 let val = sload_field(input, params_slot)?;
370 let bytes = val.to_be_bytes::<32>();
371 let mut count = bytes[29];
372 if count == 0 {
373 count = INITIAL_MAX_FRAGMENT_COUNT;
374 }
375 let mut out = [0u8; 32];
376 out[31] = count;
377 Ok(PrecompileOutput::new(
378 (SLOAD_GAS + COPY_GAS).min(gas_limit),
379 out.to_vec().into(),
380 ))
381}
382
383fn handle_get_collect_tips(input: &mut PrecompileInput<'_>) -> PrecompileResult {
384 let gas_limit = input.gas;
385 if crate::get_arbos_version() < ARBOS_VERSION_COLLECT_TIPS {
386 return Ok(PrecompileOutput::new(
387 COPY_GAS.min(gas_limit),
388 vec![0u8; 32].into(),
389 ));
390 }
391 load_arbos(input)?;
392 let value = sload_field(input, root_slot(COLLECT_TIPS_OFFSET))?;
393 let mut out = [0u8; 32];
394 if !value.is_zero() {
395 out[31] = 1;
396 }
397 Ok(PrecompileOutput::new(
398 (SLOAD_GAS + COPY_GAS).min(gas_limit),
399 out.to_vec().into(),
400 ))
401}