1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, U256};
3use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
4
5use crate::storage_slot::{
6 derive_subspace_key, map_slot, map_slot_b256, root_slot, subspace_slot, ARBOS_STATE_ADDRESS,
7 CHAIN_OWNER_SUBSPACE, FEATURES_SUBSPACE, FILTERED_FUNDS_RECIPIENT_OFFSET, L1_PRICING_SUBSPACE,
8 NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET, NATIVE_TOKEN_SUBSPACE, ROOT_STORAGE_KEY,
9 TRANSACTION_FILTERER_SUBSPACE, TX_FILTERING_ENABLED_FROM_TIME_OFFSET,
10};
11
12pub const ARBOWNERPUBLIC_ADDRESS: Address = Address::new([
14 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
15 0x00, 0x00, 0x00, 0x6b,
16]);
17
18const GET_NETWORK_FEE_ACCOUNT: [u8; 4] = [0x3e, 0x7a, 0x47, 0xb1];
20const GET_INFRA_FEE_ACCOUNT: [u8; 4] = [0x74, 0x33, 0x16, 0x04];
21const GET_BROTLI_COMPRESSION_LEVEL: [u8; 4] = [0xb1, 0x9e, 0x6b, 0xef];
22const GET_SCHEDULED_UPGRADE: [u8; 4] = [0xed, 0x23, 0xfa, 0x57];
23const IS_CHAIN_OWNER: [u8; 4] = [0x26, 0xef, 0x69, 0x9d];
24const GET_ALL_CHAIN_OWNERS: [u8; 4] = [0x51, 0x6b, 0xaf, 0x03];
25const RECTIFY_CHAIN_OWNER: [u8; 4] = [0x18, 0x3b, 0xe5, 0xf2];
26const IS_NATIVE_TOKEN_OWNER: [u8; 4] = [0x40, 0xb6, 0x62, 0x08];
27const GET_ALL_NATIVE_TOKEN_OWNERS: [u8; 4] = [0xf5, 0xc8, 0x16, 0x7a];
28const GET_NATIVE_TOKEN_MANAGEMENT_FROM: [u8; 4] = [0xaa, 0x57, 0x87, 0x88];
29const GET_TRANSACTION_FILTERING_FROM: [u8; 4] = [0x7a, 0x86, 0xfe, 0x96];
30const IS_TRANSACTION_FILTERER: [u8; 4] = [0xa5, 0x3f, 0xef, 0x64];
31const GET_ALL_TRANSACTION_FILTERERS: [u8; 4] = [0x3d, 0xbb, 0x43, 0x98];
32const GET_FILTERED_FUNDS_RECIPIENT: [u8; 4] = [0x8b, 0x00, 0x16, 0x72];
33const IS_CALLDATA_PRICE_INCREASE_ENABLED: [u8; 4] = [0x7f, 0xe5, 0x5a, 0x2f];
34const GET_PARENT_GAS_FLOOR_PER_TOKEN: [u8; 4] = [0xee, 0x36, 0x03, 0x8e];
35const GET_MAX_STYLUS_CONTRACT_FRAGMENTS: [u8; 4] = [0xea, 0x25, 0x8c, 0x64];
36
37const NETWORK_FEE_ACCOUNT_OFFSET: u64 = 3;
39const INFRA_FEE_ACCOUNT_OFFSET: u64 = 6;
40const BROTLI_COMPRESSION_LEVEL_OFFSET: u64 = 7;
41const UPGRADE_VERSION_OFFSET: u64 = 1;
42const UPGRADE_TIMESTAMP_OFFSET: u64 = 2;
43
44const L1_GAS_FLOOR_PER_TOKEN: u64 = 12;
46
47const SLOAD_GAS: u64 = 800;
48const COPY_GAS: u64 = 3;
49
50pub fn create_arbownerpublic_precompile() -> DynPrecompile {
51 DynPrecompile::new_stateful(PrecompileId::custom("arbownerpublic"), handler)
52}
53
54fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
55 let gas_limit = input.gas;
56 let data = input.data;
57 if data.len() < 4 {
58 return Err(PrecompileError::other("input too short"));
59 }
60
61 let selector: [u8; 4] = [data[0], data[1], data[2], data[3]];
62
63 let result = match selector {
64 GET_NETWORK_FEE_ACCOUNT => read_state_field(&mut input, NETWORK_FEE_ACCOUNT_OFFSET),
65 GET_INFRA_FEE_ACCOUNT => {
67 if let Some(r) = crate::check_method_version(5, 0) {
68 return r;
69 }
70 read_state_field(&mut input, INFRA_FEE_ACCOUNT_OFFSET)
71 }
72 GET_BROTLI_COMPRESSION_LEVEL => {
74 if let Some(r) = crate::check_method_version(20, 0) {
75 return r;
76 }
77 read_state_field(&mut input, BROTLI_COMPRESSION_LEVEL_OFFSET)
78 }
79 GET_SCHEDULED_UPGRADE => {
81 if let Some(r) = crate::check_method_version(20, 0) {
82 return r;
83 }
84 handle_scheduled_upgrade(&mut input)
85 }
86 IS_CHAIN_OWNER => handle_is_chain_owner(&mut input),
87 GET_ALL_CHAIN_OWNERS => handle_get_all_members(&mut input),
88 RECTIFY_CHAIN_OWNER => {
90 if let Some(r) = crate::check_method_version(11, 0) {
91 return r;
92 }
93 let gas_cost = (SLOAD_GAS + COPY_GAS).min(input.gas);
94 Ok(PrecompileOutput::new(gas_cost, Vec::new().into()))
95 }
96 IS_NATIVE_TOKEN_OWNER => {
98 if let Some(r) = crate::check_method_version(41, 0) {
99 return r;
100 }
101 handle_is_set_member(&mut input, NATIVE_TOKEN_SUBSPACE)
102 }
103 IS_TRANSACTION_FILTERER => {
105 if let Some(r) = crate::check_method_version(60, 0) {
106 return r;
107 }
108 handle_is_set_member(&mut input, TRANSACTION_FILTERER_SUBSPACE)
109 }
110 GET_ALL_NATIVE_TOKEN_OWNERS => {
112 if let Some(r) = crate::check_method_version(41, 0) {
113 return r;
114 }
115 handle_get_all_set_members(&mut input, NATIVE_TOKEN_SUBSPACE)
116 }
117 GET_ALL_TRANSACTION_FILTERERS => {
119 if let Some(r) = crate::check_method_version(60, 0) {
120 return r;
121 }
122 handle_get_all_set_members(&mut input, TRANSACTION_FILTERER_SUBSPACE)
123 }
124 GET_NATIVE_TOKEN_MANAGEMENT_FROM => {
126 if let Some(r) = crate::check_method_version(50, 0) {
127 return r;
128 }
129 read_state_field(&mut input, NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET)
130 }
131 GET_TRANSACTION_FILTERING_FROM => {
133 if let Some(r) = crate::check_method_version(60, 0) {
134 return r;
135 }
136 read_state_field(&mut input, TX_FILTERING_ENABLED_FROM_TIME_OFFSET)
137 }
138 GET_FILTERED_FUNDS_RECIPIENT => {
140 if let Some(r) = crate::check_method_version(60, 0) {
141 return r;
142 }
143 read_state_field(&mut input, FILTERED_FUNDS_RECIPIENT_OFFSET)
144 }
145 IS_CALLDATA_PRICE_INCREASE_ENABLED => {
147 if let Some(r) = crate::check_method_version(40, 0) {
148 return r;
149 }
150 let gas_limit = input.gas;
151 load_arbos(&mut input)?;
152 let features_key = derive_subspace_key(ROOT_STORAGE_KEY, FEATURES_SUBSPACE);
153 let features_slot = map_slot(features_key.as_slice(), 0);
154 let features = sload_field(&mut input, features_slot)?;
155 let enabled = features & U256::from(1);
156 let gas_cost = (2 * SLOAD_GAS + COPY_GAS).min(gas_limit);
157 Ok(PrecompileOutput::new(
158 gas_cost,
159 enabled.to_be_bytes::<32>().to_vec().into(),
160 ))
161 }
162 GET_PARENT_GAS_FLOOR_PER_TOKEN => {
164 if let Some(r) = crate::check_method_version(50, 0) {
165 return r;
166 }
167 let gas_limit = input.gas;
168 load_arbos(&mut input)?;
169 let field_slot = subspace_slot(L1_PRICING_SUBSPACE, L1_GAS_FLOOR_PER_TOKEN);
170 let value = sload_field(&mut input, field_slot)?;
171 Ok(PrecompileOutput::new(
172 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
173 value.to_be_bytes::<32>().to_vec().into(),
174 ))
175 }
176 GET_MAX_STYLUS_CONTRACT_FRAGMENTS => {
178 if let Some(r) = crate::check_method_version(60, 0) {
179 return r;
180 }
181 let gas_cost = (SLOAD_GAS + 100 + COPY_GAS).min(input.gas);
183 Ok(PrecompileOutput::new(gas_cost, vec![0u8; 32].into()))
184 }
185 _ => Err(PrecompileError::other("unknown ArbOwnerPublic selector")),
186 };
187 crate::gas_check(gas_limit, result)
188}
189
190fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
193 input
194 .internals_mut()
195 .load_account(ARBOS_STATE_ADDRESS)
196 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
197 Ok(())
198}
199
200fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
201 let val = input
202 .internals_mut()
203 .sload(ARBOS_STATE_ADDRESS, slot)
204 .map_err(|_| PrecompileError::other("sload failed"))?;
205 Ok(val.data)
206}
207
208fn read_state_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
209 let gas_limit = input.gas;
210 load_arbos(input)?;
211
212 let value = sload_field(input, root_slot(offset))?;
213 Ok(PrecompileOutput::new(
214 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
215 value.to_be_bytes::<32>().to_vec().into(),
216 ))
217}
218
219fn handle_scheduled_upgrade(input: &mut PrecompileInput<'_>) -> PrecompileResult {
220 let gas_limit = input.gas;
221 load_arbos(input)?;
222
223 let version = sload_field(input, root_slot(UPGRADE_VERSION_OFFSET))?;
224 let timestamp = sload_field(input, root_slot(UPGRADE_TIMESTAMP_OFFSET))?;
225
226 let mut out = Vec::with_capacity(64);
227 out.extend_from_slice(&version.to_be_bytes::<32>());
228 out.extend_from_slice(×tamp.to_be_bytes::<32>());
229
230 Ok(PrecompileOutput::new(
232 (3 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
233 out.into(),
234 ))
235}
236
237fn handle_is_chain_owner(input: &mut PrecompileInput<'_>) -> PrecompileResult {
238 let data = input.data;
239 if data.len() < 36 {
240 return Err(PrecompileError::other("input too short"));
241 }
242
243 let gas_limit = input.gas;
244 let addr = Address::from_slice(&data[16..36]);
245 load_arbos(input)?;
246
247 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_OWNER_SUBSPACE);
249 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
250
251 let addr_as_b256 = alloy_primitives::B256::left_padding_from(addr.as_slice());
252 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_as_b256);
253
254 let value = sload_field(input, member_slot)?;
255 let is_owner = value != U256::ZERO;
256
257 let result = if is_owner {
258 U256::from(1u64)
259 } else {
260 U256::ZERO
261 };
262
263 Ok(PrecompileOutput::new(
265 (2 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
266 result.to_be_bytes::<32>().to_vec().into(),
267 ))
268}
269
270fn handle_get_all_members(input: &mut PrecompileInput<'_>) -> PrecompileResult {
271 let gas_limit = input.gas;
272 load_arbos(input)?;
273
274 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_OWNER_SUBSPACE);
276 let size_slot = map_slot(set_key.as_slice(), 0);
277 let size = sload_field(input, size_slot)?;
278 let count: u64 = size.try_into().unwrap_or(0);
279
280 let max_members = count.min(256); let mut out = Vec::with_capacity(64 + max_members as usize * 32);
283 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
284 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
285
286 for i in 0..max_members {
287 let member_slot = map_slot(set_key.as_slice(), i + 1);
288 let addr_val = sload_field(input, member_slot)?;
289 out.extend_from_slice(&addr_val.to_be_bytes::<32>());
290 }
291
292 Ok(PrecompileOutput::new(
294 ((2 + max_members) * SLOAD_GAS + (2 + max_members) * COPY_GAS).min(gas_limit),
295 out.into(),
296 ))
297}
298
299fn handle_is_set_member(input: &mut PrecompileInput<'_>, subspace: &[u8]) -> PrecompileResult {
300 let data = input.data;
301 if data.len() < 36 {
302 return Err(PrecompileError::other("input too short"));
303 }
304 let gas_limit = input.gas;
305 let addr = Address::from_slice(&data[16..36]);
306 load_arbos(input)?;
307
308 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, subspace);
309 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
310 let addr_hash = alloy_primitives::B256::left_padding_from(addr.as_slice());
311 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
312 let value = sload_field(input, member_slot)?;
313 let is_member = if value != U256::ZERO {
314 U256::from(1u64)
315 } else {
316 U256::ZERO
317 };
318
319 Ok(PrecompileOutput::new(
321 (2 * SLOAD_GAS + 2 * COPY_GAS).min(gas_limit),
322 is_member.to_be_bytes::<32>().to_vec().into(),
323 ))
324}
325
326fn handle_get_all_set_members(
327 input: &mut PrecompileInput<'_>,
328 subspace: &[u8],
329) -> PrecompileResult {
330 let gas_limit = input.gas;
331 load_arbos(input)?;
332
333 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, subspace);
334 let size_slot = map_slot(set_key.as_slice(), 0);
335 let size = sload_field(input, size_slot)?;
336 let count: u64 = size.try_into().unwrap_or(0);
337 let max_members = count.min(65536);
338
339 let mut out = Vec::with_capacity(64 + max_members as usize * 32);
340 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
341 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
342
343 for i in 0..max_members {
344 let member_slot = map_slot(set_key.as_slice(), i + 1);
345 let addr_val = sload_field(input, member_slot)?;
346 out.extend_from_slice(&addr_val.to_be_bytes::<32>());
347 }
348
349 Ok(PrecompileOutput::new(
351 ((2 + max_members) * SLOAD_GAS + (2 + max_members) * COPY_GAS).min(gas_limit),
352 out.into(),
353 ))
354}