1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{keccak256, Address, B256, U256};
3use revm::{
4 precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult},
5 primitives::Log,
6};
7
8use crate::storage_slot::{
9 derive_subspace_key, map_slot, map_slot_b256, root_slot, subspace_slot, ARBOS_STATE_ADDRESS,
10 CACHE_MANAGERS_KEY, CHAIN_CONFIG_SUBSPACE, CHAIN_OWNER_SUBSPACE, FEATURES_SUBSPACE,
11 FILTERED_FUNDS_RECIPIENT_OFFSET, L1_PRICING_SUBSPACE, L2_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
16pub const ARBOWNER_ADDRESS: Address = Address::new([
18 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
19 0x00, 0x00, 0x00, 0x70,
20]);
21
22const GET_NETWORK_FEE_ACCOUNT: [u8; 4] = [0x3e, 0x7a, 0x47, 0xb1];
26const GET_INFRA_FEE_ACCOUNT: [u8; 4] = [0x74, 0x33, 0x16, 0x04];
27const IS_CHAIN_OWNER: [u8; 4] = [0x26, 0xef, 0x69, 0x9d];
28const GET_ALL_CHAIN_OWNERS: [u8; 4] = [0x51, 0x6b, 0xaf, 0x03];
29
30const ADD_CHAIN_OWNER: [u8; 4] = [0x48, 0x1f, 0x8d, 0xbf];
32const REMOVE_CHAIN_OWNER: [u8; 4] = [0x87, 0x92, 0x70, 0x1a];
33
34const SET_NETWORK_FEE_ACCOUNT: [u8; 4] = [0xe1, 0xa3, 0x5b, 0x12];
36const SET_INFRA_FEE_ACCOUNT: [u8; 4] = [0x0b, 0x6c, 0xf6, 0x99];
37const SCHEDULE_ARBOS_UPGRADE: [u8; 4] = [0xe3, 0x88, 0xb3, 0x81];
38const SET_BROTLI_COMPRESSION_LEVEL: [u8; 4] = [0x86, 0x47, 0x23, 0x97];
39const SET_CHAIN_CONFIG: [u8; 4] = [0xf5, 0xb7, 0x78, 0x63];
40
41const SET_SPEED_LIMIT: [u8; 4] = [0x2e, 0x09, 0xca, 0x2e];
43const SET_L2_BASE_FEE: [u8; 4] = [0x72, 0xbc, 0x8c, 0x42];
44const SET_MINIMUM_L2_BASE_FEE: [u8; 4] = [0xa7, 0x47, 0x14, 0x0c];
45const SET_MAX_BLOCK_GAS_LIMIT: [u8; 4] = [0x20, 0x2c, 0xbf, 0xbd];
46const SET_MAX_TX_GAS_LIMIT: [u8; 4] = [0xa3, 0xb1, 0xb3, 0x1d];
47const SET_L2_GAS_PRICING_INERTIA: [u8; 4] = [0x08, 0x88, 0x56, 0x1e];
48const SET_L2_GAS_BACKLOG_TOLERANCE: [u8; 4] = [0x1e, 0xda, 0xbd, 0xa6];
49const SET_GAS_BACKLOG: [u8; 4] = [0x50, 0x52, 0x48, 0x93];
50const SET_GAS_PRICING_CONSTRAINTS: [u8; 4] = [0xea, 0xe0, 0x29, 0x95];
51const SET_MULTI_GAS_PRICING_CONSTRAINTS: [u8; 4] = [0x9c, 0x04, 0x2d, 0x8e];
52
53const SET_L1_PRICING_EQUILIBRATION_UNITS: [u8; 4] = [0x69, 0x2c, 0xeb, 0x1e];
55const SET_L1_PRICING_INERTIA: [u8; 4] = [0x77, 0x6d, 0xbb, 0x4e];
56const SET_L1_PRICING_REWARD_RECIPIENT: [u8; 4] = [0xca, 0x27, 0x9e, 0x4e];
57const SET_L1_PRICING_REWARD_RATE: [u8; 4] = [0xee, 0x65, 0x86, 0xc6];
58const SET_L1_PRICE_PER_UNIT: [u8; 4] = [0x63, 0xbe, 0x3f, 0x93];
59const SET_PARENT_GAS_FLOOR_PER_TOKEN: [u8; 4] = [0x07, 0x71, 0xbb, 0xc7];
60const SET_PER_BATCH_GAS_CHARGE: [u8; 4] = [0x8f, 0x69, 0xb8, 0x12];
61const SET_AMORTIZED_COST_CAP_BIPS: [u8; 4] = [0xa4, 0xb8, 0xdb, 0x1e];
62const RELEASE_L1_PRICER_SURPLUS_FUNDS: [u8; 4] = [0xbf, 0xc5, 0x21, 0xee];
63const SET_L1_BASEFEE_ESTIMATE_INERTIA: [u8; 4] = [0x11, 0xc4, 0x8a, 0x7e];
64
65const SET_INK_PRICE: [u8; 4] = [0x8a, 0x0c, 0x4b, 0x6d];
67const SET_WASM_MAX_STACK_DEPTH: [u8; 4] = [0xf2, 0x41, 0x05, 0xca];
68const SET_WASM_FREE_PAGES: [u8; 4] = [0x53, 0x09, 0xac, 0xc8];
69const SET_WASM_PAGE_GAS: [u8; 4] = [0x82, 0x0b, 0x2b, 0x3d];
70const SET_WASM_PAGE_LIMIT: [u8; 4] = [0x30, 0xc3, 0xb8, 0x41];
71const SET_WASM_MIN_INIT_GAS: [u8; 4] = [0xd2, 0x56, 0x91, 0x32];
72const SET_WASM_INIT_COST_SCALAR: [u8; 4] = [0x7a, 0xaf, 0x8c, 0xa6];
73const SET_WASM_EXPIRY_DAYS: [u8; 4] = [0xd9, 0x13, 0xea, 0x35];
74const SET_WASM_KEEPALIVE_DAYS: [u8; 4] = [0x15, 0x8c, 0x34, 0x18];
75const SET_WASM_BLOCK_CACHE_SIZE: [u8; 4] = [0xce, 0x6e, 0x7e, 0x24];
76const SET_WASM_MAX_SIZE: [u8; 4] = [0x67, 0x00, 0xbb, 0x59];
77const ADD_WASM_CACHE_MANAGER: [u8; 4] = [0x48, 0x28, 0x2e, 0xaf];
78const REMOVE_WASM_CACHE_MANAGER: [u8; 4] = [0x1e, 0xc8, 0xd5, 0x8e];
79const SET_MAX_STYLUS_CONTRACT_FRAGMENTS: [u8; 4] = [0x79, 0xaf, 0xf2, 0x99];
80const SET_CALLDATA_PRICE_INCREASE: [u8; 4] = [0x03, 0x27, 0x40, 0x3c];
81
82const ADD_TRANSACTION_FILTERER: [u8; 4] = [0x84, 0x36, 0x3d, 0xbf];
84const REMOVE_TRANSACTION_FILTERER: [u8; 4] = [0xd8, 0x60, 0xf6, 0xc5];
85const GET_ALL_TRANSACTION_FILTERERS: [u8; 4] = [0x3d, 0xbb, 0x43, 0x98];
86const IS_TRANSACTION_FILTERER: [u8; 4] = [0xa5, 0x3f, 0xef, 0x64];
87const SET_TRANSACTION_FILTERING_FROM: [u8; 4] = [0x08, 0x36, 0x96, 0x1e];
88const SET_FILTERED_FUNDS_RECIPIENT: [u8; 4] = [0x4a, 0xc0, 0xa0, 0x45];
89const GET_FILTERED_FUNDS_RECIPIENT: [u8; 4] = [0x8b, 0x00, 0x16, 0x72];
90const SET_NATIVE_TOKEN_MANAGEMENT_FROM: [u8; 4] = [0x1b, 0x25, 0x67, 0xaa];
91const ADD_NATIVE_TOKEN_OWNER: [u8; 4] = [0xc2, 0x5d, 0xfe, 0xbb];
92const REMOVE_NATIVE_TOKEN_OWNER: [u8; 4] = [0x52, 0x2e, 0xf9, 0xad];
93const GET_ALL_NATIVE_TOKEN_OWNERS: [u8; 4] = [0xf5, 0xc8, 0x16, 0x7a];
94const IS_NATIVE_TOKEN_OWNER: [u8; 4] = [0x40, 0xb6, 0x62, 0x08];
95
96const NETWORK_FEE_ACCOUNT_OFFSET: u64 = 3;
98const INFRA_FEE_ACCOUNT_OFFSET: u64 = 6;
99const BROTLI_COMPRESSION_LEVEL_OFFSET: u64 = 7;
100const UPGRADE_VERSION_OFFSET: u64 = 1;
101const UPGRADE_TIMESTAMP_OFFSET: u64 = 2;
102
103const L1_PAY_REWARDS_TO: u64 = 0;
105const L1_EQUILIBRATION_UNITS: u64 = 1;
106const L1_INERTIA: u64 = 2;
107const L1_PER_UNIT_REWARD: u64 = 3;
108const L1_PRICE_PER_UNIT: u64 = 7;
109const L1_PER_BATCH_GAS_COST: u64 = 9;
110const L1_AMORTIZED_COST_CAP_BIPS: u64 = 10;
111const L1_FEES_AVAILABLE: u64 = 11;
112const L1_GAS_FLOOR_PER_TOKEN: u64 = 12;
113
114const L2_SPEED_LIMIT: u64 = 0;
116const L2_PER_BLOCK_GAS_LIMIT: u64 = 1;
117const L2_BASE_FEE: u64 = 2;
118const L2_MIN_BASE_FEE: u64 = 3;
119const L2_GAS_BACKLOG: u64 = 4;
120const L2_PRICING_INERTIA: u64 = 5;
121const L2_BACKLOG_TOLERANCE: u64 = 6;
122const L2_PER_TX_GAS_LIMIT: u64 = 7;
123
124const L1_PRICER_FUNDS_POOL_ADDRESS: Address = Address::new([
126 0xa4, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
127 0x00, 0x00, 0x00, 0xf6,
128]);
129
130const SLOAD_GAS: u64 = 800;
131const SSTORE_GAS: u64 = 20_000;
132const COPY_GAS: u64 = 3;
133
134pub fn create_arbowner_precompile() -> DynPrecompile {
135 DynPrecompile::new_stateful(PrecompileId::custom("arbowner"), handler)
136}
137
138fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
139 let gas_limit = input.gas;
140 let data = input.data;
141 if data.len() < 4 {
142 return Err(PrecompileError::other("input too short"));
143 }
144
145 verify_owner(&mut input)?;
147
148 let selector: [u8; 4] = [data[0], data[1], data[2], data[3]];
149
150 let result = match selector {
151 GET_NETWORK_FEE_ACCOUNT => read_root_field(&mut input, NETWORK_FEE_ACCOUNT_OFFSET),
153 GET_INFRA_FEE_ACCOUNT => {
155 if let Some(r) = crate::check_method_version(5, 0) {
156 return r;
157 }
158 read_root_field(&mut input, INFRA_FEE_ACCOUNT_OFFSET)
159 }
160 GET_FILTERED_FUNDS_RECIPIENT => {
162 if let Some(r) = crate::check_method_version(60, 0) {
163 return r;
164 }
165 read_root_field(&mut input, FILTERED_FUNDS_RECIPIENT_OFFSET)
166 }
167 IS_CHAIN_OWNER => handle_is_chain_owner(&mut input),
168 GET_ALL_CHAIN_OWNERS => handle_get_all_chain_owners(&mut input),
169 GET_ALL_TRANSACTION_FILTERERS => {
171 if let Some(r) = crate::check_method_version(60, 0) {
172 return r;
173 }
174 handle_get_all_members(&mut input, TRANSACTION_FILTERER_SUBSPACE)
175 }
176 GET_ALL_NATIVE_TOKEN_OWNERS => {
178 if let Some(r) = crate::check_method_version(41, 0) {
179 return r;
180 }
181 handle_get_all_members(&mut input, NATIVE_TOKEN_SUBSPACE)
182 }
183 IS_TRANSACTION_FILTERER => {
185 if let Some(r) = crate::check_method_version(60, 0) {
186 return r;
187 }
188 handle_is_member(&mut input, TRANSACTION_FILTERER_SUBSPACE)
189 }
190 IS_NATIVE_TOKEN_OWNER => {
192 if let Some(r) = crate::check_method_version(41, 0) {
193 return r;
194 }
195 handle_is_member(&mut input, NATIVE_TOKEN_SUBSPACE)
196 }
197
198 ADD_CHAIN_OWNER => handle_add_chain_owner(&mut input),
200 REMOVE_CHAIN_OWNER => handle_remove_chain_owner(&mut input),
201
202 SET_NETWORK_FEE_ACCOUNT => write_root_field(&mut input, NETWORK_FEE_ACCOUNT_OFFSET),
204 SET_INFRA_FEE_ACCOUNT => {
206 if let Some(r) = crate::check_method_version(5, 0) {
207 return r;
208 }
209 write_root_field(&mut input, INFRA_FEE_ACCOUNT_OFFSET)
210 }
211 SET_BROTLI_COMPRESSION_LEVEL => {
213 if let Some(r) = crate::check_method_version(20, 0) {
214 return r;
215 }
216 write_root_field(&mut input, BROTLI_COMPRESSION_LEVEL_OFFSET)
217 }
218 SCHEDULE_ARBOS_UPGRADE => handle_schedule_upgrade(&mut input),
219
220 SET_SPEED_LIMIT => {
222 let val = U256::from_be_slice(
223 input
224 .data
225 .get(4..36)
226 .ok_or_else(|| PrecompileError::other("input too short"))?,
227 );
228 if val.is_zero() {
229 return Err(PrecompileError::other("speed limit must be nonzero"));
230 }
231 write_l2_field(&mut input, L2_SPEED_LIMIT)
232 }
233 SET_L2_BASE_FEE => write_l2_field(&mut input, L2_BASE_FEE),
234 SET_MINIMUM_L2_BASE_FEE => write_l2_field(&mut input, L2_MIN_BASE_FEE),
235 SET_MAX_BLOCK_GAS_LIMIT => {
237 if let Some(r) = crate::check_method_version(50, 0) {
238 return r;
239 }
240 write_l2_field(&mut input, L2_PER_BLOCK_GAS_LIMIT)
241 }
242 SET_MAX_TX_GAS_LIMIT => {
243 let version_slot = root_slot(0); load_arbos(&mut input)?;
246 let raw_version = sload_field(&mut input, version_slot)?.to::<u64>();
247 let arbos_version = raw_version + 55;
248 let offset = if arbos_version < 50 {
249 L2_PER_BLOCK_GAS_LIMIT
250 } else {
251 L2_PER_TX_GAS_LIMIT
252 };
253 write_l2_field(&mut input, offset)
254 }
255 SET_L2_GAS_PRICING_INERTIA => {
256 let val = U256::from_be_slice(
257 input
258 .data
259 .get(4..36)
260 .ok_or_else(|| PrecompileError::other("input too short"))?,
261 );
262 if val.is_zero() {
263 return Err(PrecompileError::other("price inertia must be nonzero"));
264 }
265 write_l2_field(&mut input, L2_PRICING_INERTIA)
266 }
267 SET_L2_GAS_BACKLOG_TOLERANCE => write_l2_field(&mut input, L2_BACKLOG_TOLERANCE),
268 SET_GAS_BACKLOG => {
270 if let Some(r) = crate::check_method_version(50, 0) {
271 return r;
272 }
273 write_l2_field(&mut input, L2_GAS_BACKLOG)
274 }
275
276 SET_L1_PRICING_EQUILIBRATION_UNITS => write_l1_field(&mut input, L1_EQUILIBRATION_UNITS),
278 SET_L1_PRICING_INERTIA => write_l1_field(&mut input, L1_INERTIA),
279 SET_L1_PRICING_REWARD_RECIPIENT => write_l1_field(&mut input, L1_PAY_REWARDS_TO),
280 SET_L1_PRICING_REWARD_RATE => write_l1_field(&mut input, L1_PER_UNIT_REWARD),
281 SET_L1_PRICE_PER_UNIT => write_l1_field(&mut input, L1_PRICE_PER_UNIT),
282 SET_PARENT_GAS_FLOOR_PER_TOKEN => {
284 if let Some(r) = crate::check_method_version(50, 0) {
285 return r;
286 }
287 write_l1_field(&mut input, L1_GAS_FLOOR_PER_TOKEN)
288 }
289 SET_PER_BATCH_GAS_CHARGE => write_l1_field(&mut input, L1_PER_BATCH_GAS_COST),
290 SET_AMORTIZED_COST_CAP_BIPS => write_l1_field(&mut input, L1_AMORTIZED_COST_CAP_BIPS),
291 SET_L1_BASEFEE_ESTIMATE_INERTIA => write_l1_field(&mut input, L1_INERTIA),
292 RELEASE_L1_PRICER_SURPLUS_FUNDS => {
294 if let Some(r) = crate::check_method_version(10, 0) {
295 return r;
296 }
297 handle_release_l1_pricer_surplus_funds(&mut input)
298 }
299
300 SET_INK_PRICE => {
302 if let Some(r) = crate::check_method_version(30, 0) {
303 return r;
304 }
305 let val = read_u32_param(data)?;
306 if val == 0 || val > 0xFF_FFFF {
307 return Err(PrecompileError::other(
308 "ink price must be a positive uint24",
309 ));
310 }
311 write_stylus_param(&mut input, StylusField::InkPrice, val as u64)
312 }
313 SET_WASM_MAX_STACK_DEPTH => {
314 if let Some(r) = crate::check_method_version(30, 0) {
315 return r;
316 }
317 let val = read_u32_param(data)?;
318 write_stylus_param(&mut input, StylusField::MaxStackDepth, val as u64)
319 }
320 SET_WASM_FREE_PAGES => {
321 if let Some(r) = crate::check_method_version(30, 0) {
322 return r;
323 }
324 let val = read_u32_param(data)?;
325 write_stylus_param(&mut input, StylusField::FreePages, val as u64)
326 }
327 SET_WASM_PAGE_GAS => {
328 if let Some(r) = crate::check_method_version(30, 0) {
329 return r;
330 }
331 let val = read_u32_param(data)?;
332 write_stylus_param(&mut input, StylusField::PageGas, val as u64)
333 }
334 SET_WASM_PAGE_LIMIT => {
335 if let Some(r) = crate::check_method_version(30, 0) {
336 return r;
337 }
338 let val = read_u32_param(data)?;
339 write_stylus_param(&mut input, StylusField::PageLimit, val as u64)
340 }
341 SET_WASM_MIN_INIT_GAS => {
342 if let Some(r) = crate::check_method_version(30, 0) {
343 return r;
344 }
345 let val = read_u32_param(data)?;
346 write_stylus_param(&mut input, StylusField::MinInitGas, val as u64)
347 }
348 SET_WASM_INIT_COST_SCALAR => {
349 if let Some(r) = crate::check_method_version(30, 0) {
350 return r;
351 }
352 let val = read_u32_param(data)?;
353 write_stylus_param(&mut input, StylusField::InitCostScalar, val as u64)
354 }
355 SET_WASM_EXPIRY_DAYS => {
356 if let Some(r) = crate::check_method_version(30, 0) {
357 return r;
358 }
359 let val = read_u32_param(data)?;
360 write_stylus_param(&mut input, StylusField::ExpiryDays, val as u64)
361 }
362 SET_WASM_KEEPALIVE_DAYS => {
363 if let Some(r) = crate::check_method_version(30, 0) {
364 return r;
365 }
366 let val = read_u32_param(data)?;
367 write_stylus_param(&mut input, StylusField::KeepaliveDays, val as u64)
368 }
369 SET_WASM_BLOCK_CACHE_SIZE => {
370 if let Some(r) = crate::check_method_version(30, 0) {
371 return r;
372 }
373 let val = read_u32_param(data)?;
374 write_stylus_param(&mut input, StylusField::BlockCacheSize, val as u64)
375 }
376 SET_WASM_MAX_SIZE => {
378 if let Some(r) = crate::check_method_version(40, 0) {
379 return r;
380 }
381 let val = read_u32_param(data)?;
382 write_stylus_param(&mut input, StylusField::MaxWasmSize, val as u64)
383 }
384 SET_MAX_STYLUS_CONTRACT_FRAGMENTS => {
386 if let Some(r) = crate::check_method_version(60, 0) {
387 return r;
388 }
389 let val = read_u32_param(data)?;
390 write_stylus_param(&mut input, StylusField::MaxFragmentCount, val as u64)
391 }
392 ADD_WASM_CACHE_MANAGER => {
394 if let Some(r) = crate::check_method_version(30, 0) {
395 return r;
396 }
397 handle_add_cache_manager(&mut input)
398 }
399 REMOVE_WASM_CACHE_MANAGER => {
401 if let Some(r) = crate::check_method_version(30, 0) {
402 return r;
403 }
404 handle_remove_cache_manager(&mut input)
405 }
406 SET_CALLDATA_PRICE_INCREASE => {
408 if let Some(r) = crate::check_method_version(40, 0) {
409 return r;
410 }
411 handle_set_calldata_price_increase(&mut input)
412 }
413
414 ADD_TRANSACTION_FILTERER => {
416 if let Some(r) = crate::check_method_version(60, 0) {
417 return r;
418 }
419 handle_add_to_set_with_feature_check(
420 &mut input,
421 TRANSACTION_FILTERER_SUBSPACE,
422 TX_FILTERING_ENABLED_FROM_TIME_OFFSET,
423 )
424 }
425 REMOVE_TRANSACTION_FILTERER => {
426 if let Some(r) = crate::check_method_version(60, 0) {
427 return r;
428 }
429 handle_remove_from_set(&mut input, TRANSACTION_FILTERER_SUBSPACE)
430 }
431 SET_TRANSACTION_FILTERING_FROM => {
432 if let Some(r) = crate::check_method_version(60, 0) {
433 return r;
434 }
435 handle_set_feature_time(&mut input, TX_FILTERING_ENABLED_FROM_TIME_OFFSET)
436 }
437 SET_FILTERED_FUNDS_RECIPIENT => {
439 if let Some(r) = crate::check_method_version(60, 0) {
440 return r;
441 }
442 write_root_field(&mut input, FILTERED_FUNDS_RECIPIENT_OFFSET)
443 }
444
445 SET_NATIVE_TOKEN_MANAGEMENT_FROM => {
447 if let Some(r) = crate::check_method_version(41, 0) {
448 return r;
449 }
450 handle_set_feature_time(&mut input, NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET)
451 }
452 ADD_NATIVE_TOKEN_OWNER => {
453 if let Some(r) = crate::check_method_version(41, 0) {
454 return r;
455 }
456 handle_add_to_set_with_feature_check(
457 &mut input,
458 NATIVE_TOKEN_SUBSPACE,
459 NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET,
460 )
461 }
462 REMOVE_NATIVE_TOKEN_OWNER => {
463 if let Some(r) = crate::check_method_version(41, 0) {
464 return r;
465 }
466 handle_remove_from_set(&mut input, NATIVE_TOKEN_SUBSPACE)
467 }
468
469 SET_GAS_PRICING_CONSTRAINTS => {
472 if let Some(r) = crate::check_method_version(50, 0) {
473 return r;
474 }
475 handle_set_gas_pricing_constraints(&mut input)
476 }
477 SET_MULTI_GAS_PRICING_CONSTRAINTS => {
479 if let Some(r) = crate::check_method_version(60, 0) {
480 return r;
481 }
482 handle_set_multi_gas_pricing_constraints(&mut input)
483 }
484
485 SET_CHAIN_CONFIG => {
487 if let Some(r) = crate::check_method_version(11, 0) {
488 return r;
489 }
490 handle_set_chain_config(&mut input)
491 }
492
493 _ => Err(PrecompileError::other("unknown ArbOwner selector")),
494 };
495 let result = result.map(|output| {
500 let arbos_version = crate::get_arbos_version();
501 let is_read_only = matches!(
502 selector,
503 GET_NETWORK_FEE_ACCOUNT
504 | GET_INFRA_FEE_ACCOUNT
505 | IS_CHAIN_OWNER
506 | GET_ALL_CHAIN_OWNERS
507 | IS_TRANSACTION_FILTERER
508 | GET_ALL_TRANSACTION_FILTERERS
509 | IS_NATIVE_TOKEN_OWNER
510 | GET_ALL_NATIVE_TOKEN_OWNERS
511 | GET_FILTERED_FUNDS_RECIPIENT
512 );
513 if !is_read_only || arbos_version < 11 {
514 emit_owner_acts(&mut input, &selector, data);
515 }
516 PrecompileOutput::new(0, output.bytes)
517 });
518 crate::gas_check(gas_limit, result)
519}
520
521fn verify_owner(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
524 let caller = input.caller;
525 load_arbos(input)?;
526
527 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_OWNER_SUBSPACE);
530 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
531
532 let addr_as_b256 = alloy_primitives::B256::left_padding_from(caller.as_slice());
533 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_as_b256);
534
535 let value = sload_field(input, member_slot)?;
536 if value == U256::ZERO {
537 return Err(PrecompileError::other(
538 "ArbOwner: caller is not a chain owner",
539 ));
540 }
541 Ok(())
542}
543
544fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
547 input
548 .internals_mut()
549 .load_account(ARBOS_STATE_ADDRESS)
550 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
551 Ok(())
552}
553
554fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
555 let val = input
556 .internals_mut()
557 .sload(ARBOS_STATE_ADDRESS, slot)
558 .map_err(|_| PrecompileError::other("sload failed"))?;
559 Ok(val.data)
560}
561
562fn sstore_field(
563 input: &mut PrecompileInput<'_>,
564 slot: U256,
565 value: U256,
566) -> Result<(), PrecompileError> {
567 input
568 .internals_mut()
569 .sstore(ARBOS_STATE_ADDRESS, slot, value)
570 .map_err(|_| PrecompileError::other("sstore failed"))?;
571 Ok(())
572}
573
574fn read_root_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
575 let gas_limit = input.gas;
576 let value = sload_field(input, root_slot(offset))?;
577 Ok(PrecompileOutput::new(
578 (SLOAD_GAS + COPY_GAS).min(gas_limit),
579 value.to_be_bytes::<32>().to_vec().into(),
580 ))
581}
582
583fn write_root_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
584 let data = input.data;
585 if data.len() < 36 {
586 return Err(PrecompileError::other("input too short"));
587 }
588 let gas_limit = input.gas;
589 let value = U256::from_be_slice(&data[4..36]);
590 sstore_field(input, root_slot(offset), value)?;
591 Ok(PrecompileOutput::new(
592 (SSTORE_GAS + COPY_GAS).min(gas_limit),
593 Vec::new().into(),
594 ))
595}
596
597fn write_l1_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
598 let data = input.data;
599 if data.len() < 36 {
600 return Err(PrecompileError::other("input too short"));
601 }
602 let gas_limit = input.gas;
603 let value = U256::from_be_slice(&data[4..36]);
604 let field_slot = subspace_slot(L1_PRICING_SUBSPACE, offset);
605 sstore_field(input, field_slot, value)?;
606 Ok(PrecompileOutput::new(
607 (SSTORE_GAS + COPY_GAS).min(gas_limit),
608 Vec::new().into(),
609 ))
610}
611
612fn write_l2_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
613 let data = input.data;
614 if data.len() < 36 {
615 return Err(PrecompileError::other("input too short"));
616 }
617 let gas_limit = input.gas;
618 let value = U256::from_be_slice(&data[4..36]);
619 let field_slot = subspace_slot(L2_PRICING_SUBSPACE, offset);
620 sstore_field(input, field_slot, value)?;
621 Ok(PrecompileOutput::new(
622 (SSTORE_GAS + COPY_GAS).min(gas_limit),
623 Vec::new().into(),
624 ))
625}
626
627fn handle_schedule_upgrade(input: &mut PrecompileInput<'_>) -> PrecompileResult {
628 let data = input.data;
629 if data.len() < 68 {
630 return Err(PrecompileError::other("input too short"));
631 }
632 let gas_limit = input.gas;
633 let new_version = U256::from_be_slice(&data[4..36]);
634 let timestamp = U256::from_be_slice(&data[36..68]);
635 sstore_field(input, root_slot(UPGRADE_VERSION_OFFSET), new_version)?;
636 sstore_field(input, root_slot(UPGRADE_TIMESTAMP_OFFSET), timestamp)?;
637 Ok(PrecompileOutput::new(
638 (2 * SSTORE_GAS + COPY_GAS).min(gas_limit),
639 Vec::new().into(),
640 ))
641}
642
643fn emit_owner_acts(input: &mut PrecompileInput<'_>, selector: &[u8; 4], calldata: &[u8]) {
646 use alloy_primitives::{keccak256, Log, B256};
647
648 let topic0 = keccak256("OwnerActs(bytes4,address,bytes)");
650 let mut method_topic = [0u8; 32];
651 method_topic[..4].copy_from_slice(selector);
652 let topic1 = B256::from(method_topic);
653 let topic2 = B256::left_padding_from(input.caller.as_slice());
654
655 let mut log_data = Vec::with_capacity(64 + calldata.len().div_ceil(32) * 32);
657 log_data.extend_from_slice(&U256::from(32).to_be_bytes::<32>()); log_data.extend_from_slice(&U256::from(calldata.len()).to_be_bytes::<32>()); log_data.extend_from_slice(calldata);
660 let pad = (32 - (calldata.len() % 32)) % 32;
662 log_data.extend(std::iter::repeat_n(0u8, pad));
663
664 input.internals_mut().log(Log::new_unchecked(
665 ARBOWNER_ADDRESS,
666 vec![topic0, topic1, topic2],
667 log_data.into(),
668 ));
669}
670
671fn handle_is_chain_owner(input: &mut PrecompileInput<'_>) -> PrecompileResult {
675 handle_is_member(input, CHAIN_OWNER_SUBSPACE)
676}
677
678fn handle_get_all_chain_owners(input: &mut PrecompileInput<'_>) -> PrecompileResult {
680 handle_get_all_members(input, CHAIN_OWNER_SUBSPACE)
681}
682
683fn handle_add_chain_owner(input: &mut PrecompileInput<'_>) -> PrecompileResult {
685 let data = input.data;
686 if data.len() < 36 {
687 return Err(PrecompileError::other("input too short"));
688 }
689 let gas_limit = input.gas;
690 let addr = Address::from_slice(&data[16..36]);
691
692 address_set_add(input, address_set_key(CHAIN_OWNER_SUBSPACE), addr)?;
693
694 let arbos_version = read_arbos_version(input)? + 55;
696 if arbos_version >= 60 {
697 let topic0 = keccak256("ChainOwnerAdded(address)");
698 let topic1 = B256::left_padding_from(addr.as_slice());
699 input.internals_mut().log(Log::new_unchecked(
700 ARBOWNER_ADDRESS,
701 vec![topic0, topic1],
702 alloy_primitives::Bytes::new(),
703 ));
704 }
705
706 let gas_used = 2 * SLOAD_GAS + 3 * SSTORE_GAS + COPY_GAS;
707 Ok(PrecompileOutput::new(
708 gas_used.min(gas_limit),
709 Vec::new().into(),
710 ))
711}
712
713fn handle_remove_chain_owner(input: &mut PrecompileInput<'_>) -> PrecompileResult {
715 let data = input.data;
716 if data.len() < 36 {
717 return Err(PrecompileError::other("input too short"));
718 }
719 let gas_limit = input.gas;
720 let addr = Address::from_slice(&data[16..36]);
721
722 let set_key = address_set_key(CHAIN_OWNER_SUBSPACE);
723 if !is_member_of(input, set_key, addr)? {
724 return Err(PrecompileError::other("tried to remove non-owner"));
725 }
726 address_set_remove(input, set_key, addr)?;
727
728 let arbos_version = read_arbos_version(input)? + 55;
730 if arbos_version >= 60 {
731 let topic0 = keccak256("ChainOwnerRemoved(address)");
732 let topic1 = B256::left_padding_from(addr.as_slice());
733 input.internals_mut().log(Log::new_unchecked(
734 ARBOWNER_ADDRESS,
735 vec![topic0, topic1],
736 alloy_primitives::Bytes::new(),
737 ));
738 }
739
740 let gas_used = 3 * SLOAD_GAS + 4 * SSTORE_GAS + COPY_GAS;
741 Ok(PrecompileOutput::new(
742 gas_used.min(gas_limit),
743 Vec::new().into(),
744 ))
745}
746
747fn handle_release_l1_pricer_surplus_funds(input: &mut PrecompileInput<'_>) -> PrecompileResult {
752 let data = input.data;
753 if data.len() < 36 {
754 return Err(PrecompileError::other("input too short"));
755 }
756 let gas_limit = input.gas;
757 let max_wei = U256::from_be_slice(&data[4..36]);
758
759 let pool_balance = {
761 let acct = input
762 .internals_mut()
763 .load_account(L1_PRICER_FUNDS_POOL_ADDRESS)
764 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
765 acct.data.info.balance
766 };
767
768 load_arbos(input)?;
770 let avail_slot = subspace_slot(L1_PRICING_SUBSPACE, L1_FEES_AVAILABLE);
771 let recognized = sload_field(input, avail_slot)?;
772
773 if pool_balance <= recognized {
775 return Ok(PrecompileOutput::new(
777 (SLOAD_GAS + COPY_GAS + 100).min(gas_limit),
778 U256::ZERO.to_be_bytes::<32>().to_vec().into(),
779 ));
780 }
781
782 let mut wei_to_transfer = pool_balance - recognized;
783 if wei_to_transfer > max_wei {
784 wei_to_transfer = max_wei;
785 }
786
787 let new_available = recognized + wei_to_transfer;
789 sstore_field(input, avail_slot, new_available)?;
790
791 Ok(PrecompileOutput::new(
792 (SLOAD_GAS + SSTORE_GAS + COPY_GAS + 100).min(gas_limit),
793 wei_to_transfer.to_be_bytes::<32>().to_vec().into(),
794 ))
795}
796
797fn address_set_key(subspace: &[u8]) -> B256 {
799 derive_subspace_key(ROOT_STORAGE_KEY, subspace)
800}
801
802fn is_member_of(
804 input: &mut PrecompileInput<'_>,
805 set_key: B256,
806 addr: Address,
807) -> Result<bool, PrecompileError> {
808 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
809 let addr_hash = B256::left_padding_from(addr.as_slice());
810 let slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
811 let val = sload_field(input, slot)?;
812 Ok(val != U256::ZERO)
813}
814
815fn handle_is_member(input: &mut PrecompileInput<'_>, subspace: &[u8]) -> PrecompileResult {
817 let data = input.data;
818 if data.len() < 36 {
819 return Err(PrecompileError::other("input too short"));
820 }
821 let gas_limit = input.gas;
822 let addr = Address::from_slice(&data[16..36]);
823 let is_member = is_member_of(input, address_set_key(subspace), addr)?;
824 let result = if is_member {
825 U256::from(1u64)
826 } else {
827 U256::ZERO
828 };
829 Ok(PrecompileOutput::new(
830 (SLOAD_GAS + COPY_GAS).min(gas_limit),
831 result.to_be_bytes::<32>().to_vec().into(),
832 ))
833}
834
835fn handle_get_all_members(input: &mut PrecompileInput<'_>, subspace: &[u8]) -> PrecompileResult {
837 let gas_limit = input.gas;
838 let set_key = address_set_key(subspace);
839 let size_slot = map_slot(set_key.as_slice(), 0);
840 let size = sload_field(input, size_slot)?;
841 let count: u64 = size
842 .try_into()
843 .map_err(|_| PrecompileError::other("invalid address set size"))?;
844 const MAX_MEMBERS: u64 = 65536;
845 let count = count.min(MAX_MEMBERS);
846
847 let mut addresses = Vec::with_capacity(count as usize);
848 for i in 1..=count {
849 let member_slot = map_slot(set_key.as_slice(), i);
850 let val = sload_field(input, member_slot)?;
851 addresses.push(val);
852 }
853
854 let mut out = Vec::with_capacity(64 + 32 * addresses.len());
855 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
856 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
857 for addr_val in &addresses {
858 out.extend_from_slice(&addr_val.to_be_bytes::<32>());
859 }
860
861 let gas_used = (1 + count) * SLOAD_GAS + COPY_GAS;
862 Ok(PrecompileOutput::new(gas_used.min(gas_limit), out.into()))
863}
864
865fn address_set_add(
867 input: &mut PrecompileInput<'_>,
868 set_key: B256,
869 addr: Address,
870) -> Result<bool, PrecompileError> {
871 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
872 let addr_hash = B256::left_padding_from(addr.as_slice());
873 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
874 let existing = sload_field(input, member_slot)?;
875
876 if existing != U256::ZERO {
877 return Ok(false); }
879
880 let size_slot = map_slot(set_key.as_slice(), 0);
882 let size = sload_field(input, size_slot)?;
883 let size_u64: u64 = size
884 .try_into()
885 .map_err(|_| PrecompileError::other("invalid address set size"))?;
886 let new_size = size_u64 + 1;
887
888 let new_pos_slot = map_slot(set_key.as_slice(), new_size);
890 let addr_as_u256 = U256::from_be_slice(addr.as_slice());
891 sstore_field(input, new_pos_slot, addr_as_u256)?;
892
893 sstore_field(input, member_slot, U256::from(new_size))?;
895
896 sstore_field(input, size_slot, U256::from(new_size))?;
898
899 Ok(true)
900}
901
902fn address_set_remove(
904 input: &mut PrecompileInput<'_>,
905 set_key: B256,
906 addr: Address,
907) -> Result<(), PrecompileError> {
908 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
909 let addr_hash = B256::left_padding_from(addr.as_slice());
910 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
911
912 let position = sload_field(input, member_slot)?;
914 if position == U256::ZERO {
915 return Err(PrecompileError::other("address not in set"));
916 }
917 let pos_u64: u64 = position
918 .try_into()
919 .map_err(|_| PrecompileError::other("invalid position"))?;
920
921 sstore_field(input, member_slot, U256::ZERO)?;
923
924 let size_slot = map_slot(set_key.as_slice(), 0);
926 let size = sload_field(input, size_slot)?;
927 let size_u64: u64 = size
928 .try_into()
929 .map_err(|_| PrecompileError::other("invalid size"))?;
930
931 if pos_u64 < size_u64 {
933 let last_slot = map_slot(set_key.as_slice(), size_u64);
934 let last_val = sload_field(input, last_slot)?;
935
936 let removed_slot = map_slot(set_key.as_slice(), pos_u64);
938 sstore_field(input, removed_slot, last_val)?;
939
940 let last_bytes = last_val.to_be_bytes::<32>();
942 let last_hash = B256::from(last_bytes);
943 let last_member_slot = map_slot_b256(by_address_key.as_slice(), &last_hash);
944 sstore_field(input, last_member_slot, U256::from(pos_u64))?;
945 }
946
947 let last_pos_slot = map_slot(set_key.as_slice(), size_u64);
949 sstore_field(input, last_pos_slot, U256::ZERO)?;
950
951 sstore_field(input, size_slot, U256::from(size_u64 - 1))?;
953
954 Ok(())
955}
956
957enum StylusField {
978 InkPrice, MaxStackDepth, FreePages, PageGas, PageLimit, MinInitGas, InitCostScalar, ExpiryDays, KeepaliveDays, BlockCacheSize, MaxWasmSize, MaxFragmentCount, }
991
992impl StylusField {
993 fn byte_range(&self) -> (usize, usize) {
994 match self {
995 Self::InkPrice => (2, 5),
996 Self::MaxStackDepth => (5, 9),
997 Self::FreePages => (9, 11),
998 Self::PageGas => (11, 13),
999 Self::PageLimit => (13, 15),
1000 Self::MinInitGas => (15, 16),
1001 Self::InitCostScalar => (17, 18),
1002 Self::ExpiryDays => (19, 21),
1003 Self::KeepaliveDays => (21, 23),
1004 Self::BlockCacheSize => (23, 25),
1005 Self::MaxWasmSize => (25, 29),
1006 Self::MaxFragmentCount => (29, 30),
1007 }
1008 }
1009}
1010
1011fn programs_params_slot() -> U256 {
1013 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
1015 let params_key = derive_subspace_key(programs_key.as_slice(), &[0]); map_slot(params_key.as_slice(), 0)
1017}
1018
1019fn read_stylus_params_word(input: &mut PrecompileInput<'_>) -> Result<[u8; 32], PrecompileError> {
1021 let slot = programs_params_slot();
1022 let val = sload_field(input, slot)?;
1023 Ok(val.to_be_bytes::<32>())
1024}
1025
1026fn write_stylus_param(
1028 input: &mut PrecompileInput<'_>,
1029 field: StylusField,
1030 value: u64,
1031) -> PrecompileResult {
1032 let gas_limit = input.gas;
1033 let slot = programs_params_slot();
1034 let mut word = read_stylus_params_word(input)?;
1035
1036 let (start, end) = field.byte_range();
1037 let len = end - start;
1038 let bytes = value.to_be_bytes();
1039 word[start..end].copy_from_slice(&bytes[8 - len..]);
1041
1042 sstore_field(input, slot, U256::from_be_bytes(word))?;
1043 Ok(PrecompileOutput::new(
1044 (SLOAD_GAS + SSTORE_GAS + COPY_GAS).min(gas_limit),
1045 Vec::new().into(),
1046 ))
1047}
1048
1049fn read_u32_param(data: &[u8]) -> Result<u32, PrecompileError> {
1051 if data.len() < 36 {
1052 return Err(PrecompileError::other("input too short"));
1053 }
1054 let val = U256::from_be_slice(&data[4..36]);
1055 val.try_into()
1056 .map_err(|_| PrecompileError::other("value overflow"))
1057}
1058
1059fn cache_managers_set_key() -> B256 {
1063 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
1064 derive_subspace_key(programs_key.as_slice(), CACHE_MANAGERS_KEY)
1065}
1066
1067fn handle_add_cache_manager(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1068 let data = input.data;
1069 if data.len() < 36 {
1070 return Err(PrecompileError::other("input too short"));
1071 }
1072 let gas_limit = input.gas;
1073 let addr = Address::from_slice(&data[16..36]);
1074 address_set_add(input, cache_managers_set_key(), addr)?;
1075 let gas_used = 2 * SLOAD_GAS + 3 * SSTORE_GAS + COPY_GAS;
1076 Ok(PrecompileOutput::new(
1077 gas_used.min(gas_limit),
1078 Vec::new().into(),
1079 ))
1080}
1081
1082fn handle_remove_cache_manager(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1083 let data = input.data;
1084 if data.len() < 36 {
1085 return Err(PrecompileError::other("input too short"));
1086 }
1087 let gas_limit = input.gas;
1088 let addr = Address::from_slice(&data[16..36]);
1089 let set_key = cache_managers_set_key();
1090 if !is_member_of(input, set_key, addr)? {
1091 return Err(PrecompileError::other("address is not a cache manager"));
1092 }
1093 address_set_remove(input, set_key, addr)?;
1094 let gas_used = 3 * SLOAD_GAS + 4 * SSTORE_GAS + COPY_GAS;
1095 Ok(PrecompileOutput::new(
1096 gas_used.min(gas_limit),
1097 Vec::new().into(),
1098 ))
1099}
1100
1101const FEATURE_ENABLE_DELAY: u64 = 7 * 24 * 60 * 60;
1103
1104fn handle_set_feature_time(input: &mut PrecompileInput<'_>, time_offset: u64) -> PrecompileResult {
1106 let data = input.data;
1107 if data.len() < 36 {
1108 return Err(PrecompileError::other("input too short"));
1109 }
1110 let gas_limit = input.gas;
1111 let timestamp: u64 = U256::from_be_slice(&data[4..36])
1112 .try_into()
1113 .map_err(|_| PrecompileError::other("timestamp too large"))?;
1114
1115 if timestamp == 0 {
1116 sstore_field(input, root_slot(time_offset), U256::ZERO)?;
1118 return Ok(PrecompileOutput::new(
1119 (SSTORE_GAS + COPY_GAS).min(gas_limit),
1120 Vec::new().into(),
1121 ));
1122 }
1123
1124 let stored_val = sload_field(input, root_slot(time_offset))?;
1125 let stored: u64 = stored_val.try_into().unwrap_or(0);
1126 let now = input
1127 .internals_mut()
1128 .block_timestamp()
1129 .try_into()
1130 .unwrap_or(0u64);
1131
1132 if (stored > now + FEATURE_ENABLE_DELAY || stored == 0)
1134 && timestamp < now + FEATURE_ENABLE_DELAY
1135 {
1136 return Err(PrecompileError::other(
1137 "feature must be enabled at least 7 days in the future",
1138 ));
1139 }
1140 if stored > now && stored <= now + FEATURE_ENABLE_DELAY && timestamp < stored {
1141 return Err(PrecompileError::other(
1142 "feature cannot be updated to a time earlier than the current scheduled enable time",
1143 ));
1144 }
1145
1146 sstore_field(input, root_slot(time_offset), U256::from(timestamp))?;
1147 Ok(PrecompileOutput::new(
1148 (SLOAD_GAS + SSTORE_GAS + COPY_GAS).min(gas_limit),
1149 Vec::new().into(),
1150 ))
1151}
1152
1153fn handle_add_to_set_with_feature_check(
1155 input: &mut PrecompileInput<'_>,
1156 subspace: &[u8],
1157 time_offset: u64,
1158) -> PrecompileResult {
1159 let data = input.data;
1160 if data.len() < 36 {
1161 return Err(PrecompileError::other("input too short"));
1162 }
1163 let gas_limit = input.gas;
1164 let addr = Address::from_slice(&data[16..36]);
1165
1166 let enabled_time_val = sload_field(input, root_slot(time_offset))?;
1168 let enabled_time: u64 = enabled_time_val.try_into().unwrap_or(0);
1169 let now: u64 = input
1170 .internals_mut()
1171 .block_timestamp()
1172 .try_into()
1173 .unwrap_or(0u64);
1174
1175 if enabled_time == 0 || enabled_time > now {
1176 return Err(PrecompileError::other("feature is not enabled yet"));
1177 }
1178
1179 address_set_add(input, address_set_key(subspace), addr)?;
1180
1181 let gas_used = 2 * SLOAD_GAS + 3 * SSTORE_GAS + COPY_GAS;
1182 Ok(PrecompileOutput::new(
1183 gas_used.min(gas_limit),
1184 Vec::new().into(),
1185 ))
1186}
1187
1188fn handle_remove_from_set(input: &mut PrecompileInput<'_>, subspace: &[u8]) -> PrecompileResult {
1190 let data = input.data;
1191 if data.len() < 36 {
1192 return Err(PrecompileError::other("input too short"));
1193 }
1194 let gas_limit = input.gas;
1195 let addr = Address::from_slice(&data[16..36]);
1196
1197 let set_key = address_set_key(subspace);
1199 if !is_member_of(input, set_key, addr)? {
1200 return Err(PrecompileError::other("address is not a member"));
1201 }
1202
1203 address_set_remove(input, set_key, addr)?;
1204
1205 let gas_used = 3 * SLOAD_GAS + 4 * SSTORE_GAS + COPY_GAS;
1206 Ok(PrecompileOutput::new(
1207 gas_used.min(gas_limit),
1208 Vec::new().into(),
1209 ))
1210}
1211
1212const GC_KEY: &[u8] = b"gc";
1215const MGC_KEY: &[u8] = b"mgc";
1216const CONSTRAINT_TARGET: u64 = 0;
1217const CONSTRAINT_WINDOW: u64 = 1;
1218const CONSTRAINT_BACKLOG: u64 = 2;
1219const MGC_MAX_WEIGHT: u64 = 3;
1220const MGC_WEIGHTS_BASE: u64 = 4;
1221const NUM_RESOURCE_KINDS: u64 = 8;
1222const GAS_CONSTRAINTS_MAX_NUM: usize = 20;
1223const MAX_PRICING_EXPONENT_BIPS: u64 = 85_000;
1224
1225fn l2_pricing_key() -> B256 {
1227 derive_subspace_key(ROOT_STORAGE_KEY, L2_PRICING_SUBSPACE)
1228}
1229
1230fn gc_vector_key() -> B256 {
1232 derive_subspace_key(l2_pricing_key().as_slice(), GC_KEY)
1233}
1234
1235fn mgc_vector_key() -> B256 {
1237 derive_subspace_key(l2_pricing_key().as_slice(), MGC_KEY)
1238}
1239
1240fn vector_length_slot(vector_key: B256) -> U256 {
1242 map_slot(vector_key.as_slice(), 0)
1243}
1244
1245fn vector_element_key(vector_key: B256, index: u64) -> B256 {
1247 derive_subspace_key(vector_key.as_slice(), &index.to_be_bytes())
1248}
1249
1250fn constraint_field_slot(element_key: B256, field_offset: u64) -> U256 {
1252 map_slot(element_key.as_slice(), field_offset)
1253}
1254
1255fn read_arbos_version(input: &mut PrecompileInput<'_>) -> Result<u64, PrecompileError> {
1257 let val = sload_field(input, root_slot(0))?; val.try_into()
1259 .map_err(|_| PrecompileError::other("invalid ArbOS version"))
1260}
1261
1262fn clear_gas_constraints_vector(
1264 input: &mut PrecompileInput<'_>,
1265 vector_key: B256,
1266 fields_per_element: u64,
1267) -> Result<u64, PrecompileError> {
1268 let len_slot = vector_length_slot(vector_key);
1269 let len: u64 = sload_field(input, len_slot)?.try_into().unwrap_or(0);
1270
1271 for i in 0..len {
1273 let elem_key = vector_element_key(vector_key, i);
1274 for f in 0..fields_per_element {
1275 sstore_field(input, constraint_field_slot(elem_key, f), U256::ZERO)?;
1276 }
1277 }
1278
1279 sstore_field(input, len_slot, U256::ZERO)?;
1281
1282 Ok(len)
1283}
1284
1285fn clear_multi_gas_constraints(input: &mut PrecompileInput<'_>) -> Result<u64, PrecompileError> {
1287 let vector_key = mgc_vector_key();
1288 let len_slot = vector_length_slot(vector_key);
1289 let len: u64 = sload_field(input, len_slot)?.try_into().unwrap_or(0);
1290
1291 for i in 0..len {
1292 let elem_key = vector_element_key(vector_key, i);
1293 for f in 0..4 {
1295 sstore_field(input, constraint_field_slot(elem_key, f), U256::ZERO)?;
1296 }
1297 for r in 0..NUM_RESOURCE_KINDS {
1299 sstore_field(
1300 input,
1301 constraint_field_slot(elem_key, MGC_WEIGHTS_BASE + r),
1302 U256::ZERO,
1303 )?;
1304 }
1305 }
1306
1307 sstore_field(input, len_slot, U256::ZERO)?;
1308 Ok(len)
1309}
1310
1311fn handle_set_gas_pricing_constraints(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1318 let data = input.data;
1319 if data.len() < 68 {
1321 return Err(PrecompileError::other("input too short"));
1322 }
1323 let gas_limit = input.gas;
1324
1325 let count: u64 = U256::from_be_slice(&data[36..68])
1327 .try_into()
1328 .map_err(|_| PrecompileError::other("array length overflow"))?;
1329
1330 let expected_len = 68 + (count as usize) * 96;
1332 if data.len() < expected_len {
1333 return Err(PrecompileError::other("input too short for constraints"));
1334 }
1335
1336 let vector_key = gc_vector_key();
1338 clear_gas_constraints_vector(input, vector_key, 3)?;
1339
1340 let arbos_version = read_arbos_version(input)?;
1342 use arb_chainspec::arbos_version as arb_ver;
1343 if (arb_ver::ARBOS_VERSION_MULTI_CONSTRAINT_FIX..arb_ver::ARBOS_VERSION_MULTI_GAS_CONSTRAINTS)
1344 .contains(&arbos_version)
1345 && (count as usize) > GAS_CONSTRAINTS_MAX_NUM
1346 {
1347 return Err(PrecompileError::other("too many constraints"));
1348 }
1349
1350 let len_slot = vector_length_slot(vector_key);
1352 for i in 0..count {
1353 let base = 68 + (i as usize) * 96;
1354 let target: u64 = U256::from_be_slice(&data[base..base + 32])
1355 .try_into()
1356 .unwrap_or(0);
1357 let window: u64 = U256::from_be_slice(&data[base + 32..base + 64])
1358 .try_into()
1359 .unwrap_or(0);
1360 let backlog: u64 = U256::from_be_slice(&data[base + 64..base + 96])
1361 .try_into()
1362 .unwrap_or(0);
1363
1364 if target == 0 || window == 0 {
1365 return Err(PrecompileError::other("invalid constraint parameters"));
1366 }
1367
1368 let elem_key = vector_element_key(vector_key, i);
1370 sstore_field(
1371 input,
1372 constraint_field_slot(elem_key, CONSTRAINT_TARGET),
1373 U256::from(target),
1374 )?;
1375 sstore_field(
1376 input,
1377 constraint_field_slot(elem_key, CONSTRAINT_WINDOW),
1378 U256::from(window),
1379 )?;
1380 sstore_field(
1381 input,
1382 constraint_field_slot(elem_key, CONSTRAINT_BACKLOG),
1383 U256::from(backlog),
1384 )?;
1385
1386 sstore_field(input, len_slot, U256::from(i + 1))?;
1388 }
1389
1390 let gas_used = (count * 4 + 2) * SSTORE_GAS + count * SLOAD_GAS + COPY_GAS;
1391 Ok(PrecompileOutput::new(
1392 gas_used.min(gas_limit),
1393 Vec::new().into(),
1394 ))
1395}
1396
1397fn handle_set_multi_gas_pricing_constraints(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1404 let data = input.data;
1405 if data.len() < 68 {
1406 return Err(PrecompileError::other("input too short"));
1407 }
1408 let gas_limit = input.gas;
1409
1410 clear_multi_gas_constraints(input)?;
1412
1413 let _outer_offset: usize = U256::from_be_slice(&data[4..36])
1415 .try_into()
1416 .unwrap_or(0usize);
1417 let count: u64 = U256::from_be_slice(&data[36..68])
1419 .try_into()
1420 .map_err(|_| PrecompileError::other("array length overflow"))?;
1421
1422 let array_data_start = 68; let mut struct_offsets = Vec::with_capacity(count as usize);
1427 for i in 0..count as usize {
1428 let offset_pos = array_data_start + i * 32;
1429 if data.len() < offset_pos + 32 {
1430 return Err(PrecompileError::other("input too short for struct offsets"));
1431 }
1432 let offset: usize = U256::from_be_slice(&data[offset_pos..offset_pos + 32])
1433 .try_into()
1434 .unwrap_or(0);
1435 struct_offsets.push(array_data_start + offset);
1437 }
1438
1439 let vector_key = mgc_vector_key();
1440 let len_slot = vector_length_slot(vector_key);
1441
1442 for (i, &struct_start) in struct_offsets.iter().enumerate() {
1443 if data.len() < struct_start + 128 {
1445 return Err(PrecompileError::other(
1446 "input too short for constraint struct",
1447 ));
1448 }
1449
1450 let target: u64 = U256::from_be_slice(&data[struct_start..struct_start + 32])
1451 .try_into()
1452 .unwrap_or(0);
1453 let window: u64 = U256::from_be_slice(&data[struct_start + 32..struct_start + 64])
1454 .try_into()
1455 .unwrap_or(0);
1456 let backlog: u64 = U256::from_be_slice(&data[struct_start + 64..struct_start + 96])
1457 .try_into()
1458 .unwrap_or(0);
1459
1460 if target == 0 || window == 0 {
1461 return Err(PrecompileError::other("invalid constraint parameters"));
1462 }
1463
1464 let resources_offset: usize =
1466 U256::from_be_slice(&data[struct_start + 96..struct_start + 128])
1467 .try_into()
1468 .unwrap_or(0);
1469 let resources_start = struct_start + resources_offset;
1470
1471 if data.len() < resources_start + 32 {
1472 return Err(PrecompileError::other(
1473 "input too short for resources array",
1474 ));
1475 }
1476
1477 let num_resources: usize =
1478 U256::from_be_slice(&data[resources_start..resources_start + 32])
1479 .try_into()
1480 .unwrap_or(0);
1481
1482 let mut weights = [0u64; 8];
1484 let mut max_weight = 0u64;
1485 for r in 0..num_resources {
1486 let r_start = resources_start + 32 + r * 64;
1487 if data.len() < r_start + 64 {
1488 return Err(PrecompileError::other(
1489 "input too short for resource weight",
1490 ));
1491 }
1492 let resource: u8 = U256::from_be_slice(&data[r_start..r_start + 32])
1493 .try_into()
1494 .unwrap_or(0);
1495 let weight: u64 = U256::from_be_slice(&data[r_start + 32..r_start + 64])
1496 .try_into()
1497 .unwrap_or(0);
1498
1499 if (resource as u64) < NUM_RESOURCE_KINDS {
1500 weights[resource as usize] = weight;
1501 if weight > max_weight {
1502 max_weight = weight;
1503 }
1504 }
1505 }
1506
1507 let elem_key = vector_element_key(vector_key, i as u64);
1509 sstore_field(
1510 input,
1511 constraint_field_slot(elem_key, CONSTRAINT_TARGET),
1512 U256::from(target),
1513 )?;
1514 sstore_field(
1515 input,
1516 constraint_field_slot(elem_key, CONSTRAINT_WINDOW),
1517 U256::from(window),
1518 )?;
1519 sstore_field(
1520 input,
1521 constraint_field_slot(elem_key, CONSTRAINT_BACKLOG),
1522 U256::from(backlog),
1523 )?;
1524 sstore_field(
1525 input,
1526 constraint_field_slot(elem_key, MGC_MAX_WEIGHT),
1527 U256::from(max_weight),
1528 )?;
1529
1530 for (r, &weight) in weights.iter().enumerate().take(NUM_RESOURCE_KINDS as usize) {
1532 sstore_field(
1533 input,
1534 constraint_field_slot(elem_key, MGC_WEIGHTS_BASE + r as u64),
1535 U256::from(weight),
1536 )?;
1537 }
1538
1539 sstore_field(input, len_slot, U256::from(i as u64 + 1))?;
1541
1542 validate_multi_gas_exponents(input, vector_key, i as u64 + 1)?;
1544 }
1545
1546 let gas_used = (count * 16 + 2) * SSTORE_GAS + (count * 12 + 2) * SLOAD_GAS + COPY_GAS;
1547 Ok(PrecompileOutput::new(
1548 gas_used.min(gas_limit),
1549 Vec::new().into(),
1550 ))
1551}
1552
1553fn validate_multi_gas_exponents(
1555 input: &mut PrecompileInput<'_>,
1556 vector_key: B256,
1557 count: u64,
1558) -> Result<(), PrecompileError> {
1559 let mut exponents = [0u64; 8];
1560
1561 for i in 0..count {
1562 let elem_key = vector_element_key(vector_key, i);
1563 let target: u64 = sload_field(input, constraint_field_slot(elem_key, CONSTRAINT_TARGET))?
1564 .try_into()
1565 .unwrap_or(0);
1566 let backlog: u64 = sload_field(input, constraint_field_slot(elem_key, CONSTRAINT_BACKLOG))?
1567 .try_into()
1568 .unwrap_or(0);
1569
1570 if backlog == 0 {
1571 continue;
1572 }
1573
1574 let window: u64 = sload_field(input, constraint_field_slot(elem_key, CONSTRAINT_WINDOW))?
1575 .try_into()
1576 .unwrap_or(0);
1577 let max_weight: u64 = sload_field(input, constraint_field_slot(elem_key, MGC_MAX_WEIGHT))?
1578 .try_into()
1579 .unwrap_or(0);
1580
1581 if max_weight == 0 || target == 0 || window == 0 {
1582 continue;
1583 }
1584
1585 let divisor = (window as u128)
1587 .saturating_mul(target as u128)
1588 .saturating_mul(max_weight as u128);
1589 let divisor_bips = divisor.saturating_mul(10000);
1590
1591 for (r, exponent) in exponents
1592 .iter_mut()
1593 .enumerate()
1594 .take(NUM_RESOURCE_KINDS as usize)
1595 {
1596 let weight: u64 = sload_field(
1597 input,
1598 constraint_field_slot(elem_key, MGC_WEIGHTS_BASE + r as u64),
1599 )?
1600 .try_into()
1601 .unwrap_or(0);
1602
1603 if weight == 0 {
1604 continue;
1605 }
1606
1607 let dividend = (backlog as u128).saturating_mul(weight as u128) * 10000;
1608 let exp = if divisor_bips > 0 {
1609 (dividend / divisor_bips) as u64
1610 } else {
1611 0
1612 };
1613 *exponent = exponent.saturating_add(exp);
1614 }
1615 }
1616
1617 for &exp in &exponents {
1618 if exp > MAX_PRICING_EXPONENT_BIPS {
1619 return Err(PrecompileError::other("pricing exponent exceeds maximum"));
1620 }
1621 }
1622
1623 Ok(())
1624}
1625
1626fn handle_set_chain_config(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1632 let data = input.data;
1633 if data.len() < 68 {
1634 return Err(PrecompileError::other("input too short"));
1635 }
1636 let gas_limit = input.gas;
1637
1638 let bytes_len: usize = U256::from_be_slice(&data[36..68])
1640 .try_into()
1641 .map_err(|_| PrecompileError::other("bytes length overflow"))?;
1642
1643 if data.len() < 68 + bytes_len {
1644 return Err(PrecompileError::other(
1645 "input too short for chain config bytes",
1646 ));
1647 }
1648 let config_bytes = &data[68..68 + bytes_len];
1649
1650 let cc_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_CONFIG_SUBSPACE);
1652
1653 let old_len: u64 = sload_field(input, map_slot(cc_key.as_slice(), 0))?
1655 .try_into()
1656 .unwrap_or(0);
1657 let old_slots = old_len.div_ceil(32);
1658 for s in 1..=old_slots {
1659 sstore_field(input, map_slot(cc_key.as_slice(), s), U256::ZERO)?;
1660 }
1661
1662 sstore_field(
1664 input,
1665 map_slot(cc_key.as_slice(), 0),
1666 U256::from(bytes_len as u64),
1667 )?;
1668
1669 let mut remaining = config_bytes;
1671 let mut offset = 1u64;
1672 while remaining.len() >= 32 {
1673 let mut slot = [0u8; 32];
1674 slot.copy_from_slice(&remaining[..32]);
1675 sstore_field(
1676 input,
1677 map_slot(cc_key.as_slice(), offset),
1678 U256::from_be_bytes(slot),
1679 )?;
1680 remaining = &remaining[32..];
1681 offset += 1;
1682 }
1683 if !remaining.is_empty() {
1684 let mut slot = [0u8; 32];
1685 slot[..remaining.len()].copy_from_slice(remaining);
1686 sstore_field(
1687 input,
1688 map_slot(cc_key.as_slice(), offset),
1689 U256::from_be_bytes(slot),
1690 )?;
1691 }
1692
1693 let new_slots = (bytes_len as u64).div_ceil(32);
1694 let total_stores = old_slots + 1 + new_slots; let gas_used = total_stores * SSTORE_GAS + SLOAD_GAS + COPY_GAS;
1696 Ok(PrecompileOutput::new(
1697 gas_used.min(gas_limit),
1698 Vec::new().into(),
1699 ))
1700}
1701
1702fn handle_set_calldata_price_increase(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1705 let data = input.data;
1706 if data.len() < 36 {
1707 return Err(PrecompileError::other("input too short"));
1708 }
1709 let gas_limit = input.gas;
1710 let enabled = U256::from_be_slice(&data[4..36]) != U256::ZERO;
1711
1712 load_arbos(input)?;
1713
1714 let features_key = derive_subspace_key(ROOT_STORAGE_KEY, FEATURES_SUBSPACE);
1715 let features_slot = map_slot(features_key.as_slice(), 0);
1716 let current = sload_field(input, features_slot)?;
1717
1718 let updated = if enabled {
1719 current | U256::from(1)
1720 } else {
1721 current & !(U256::from(1))
1722 };
1723 sstore_field(input, features_slot, updated)?;
1724
1725 let gas_used = SLOAD_GAS + SSTORE_GAS + COPY_GAS;
1726 Ok(PrecompileOutput::new(
1727 gas_used.min(gas_limit),
1728 Vec::new().into(),
1729 ))
1730}