1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, B256, U256};
3use alloy_sol_types::{SolEvent, SolInterface};
4use revm::{
5 precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult},
6 primitives::Log,
7};
8
9use crate::{
10 interfaces::IArbOwner,
11 storage_slot::{
12 derive_subspace_key, map_slot, map_slot_b256, root_slot, subspace_slot,
13 ARBOS_STATE_ADDRESS, CACHE_MANAGERS_KEY, CHAIN_CONFIG_SUBSPACE, CHAIN_OWNER_SUBSPACE,
14 FEATURES_SUBSPACE, FILTERED_FUNDS_RECIPIENT_OFFSET, L1_PRICING_SUBSPACE,
15 L2_PRICING_SUBSPACE, NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET, NATIVE_TOKEN_SUBSPACE,
16 PROGRAMS_SUBSPACE, ROOT_STORAGE_KEY, TRANSACTION_FILTERER_SUBSPACE,
17 TX_FILTERING_ENABLED_FROM_TIME_OFFSET,
18 },
19};
20
21pub const ARBOWNER_ADDRESS: Address = Address::new([
23 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
24 0x00, 0x00, 0x00, 0x70,
25]);
26
27const COLLECT_TIPS_OFFSET: u64 = 11; const ARBOS_VERSION_60: u64 = 60;
29
30const NETWORK_FEE_ACCOUNT_OFFSET: u64 = 3;
32const INFRA_FEE_ACCOUNT_OFFSET: u64 = 6;
33const BROTLI_COMPRESSION_LEVEL_OFFSET: u64 = 7;
34const UPGRADE_VERSION_OFFSET: u64 = 1;
35const UPGRADE_TIMESTAMP_OFFSET: u64 = 2;
36
37const L1_PAY_REWARDS_TO: u64 = 0;
39const L1_EQUILIBRATION_UNITS: u64 = 1;
40const L1_INERTIA: u64 = 2;
41const L1_PER_UNIT_REWARD: u64 = 3;
42const L1_PRICE_PER_UNIT: u64 = 7;
43const L1_PER_BATCH_GAS_COST: u64 = 9;
44const L1_AMORTIZED_COST_CAP_BIPS: u64 = 10;
45const L1_FEES_AVAILABLE: u64 = 11;
46const L1_GAS_FLOOR_PER_TOKEN: u64 = 12;
47
48const L2_SPEED_LIMIT: u64 = 0;
50const L2_PER_BLOCK_GAS_LIMIT: u64 = 1;
51const L2_BASE_FEE: u64 = 2;
52const L2_MIN_BASE_FEE: u64 = 3;
53const L2_GAS_BACKLOG: u64 = 4;
54const L2_PRICING_INERTIA: u64 = 5;
55const L2_BACKLOG_TOLERANCE: u64 = 6;
56const L2_PER_TX_GAS_LIMIT: u64 = 7;
57
58const L1_PRICER_FUNDS_POOL_ADDRESS: Address = Address::new([
60 0xa4, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
61 0x00, 0x00, 0x00, 0xf6,
62]);
63
64const SLOAD_GAS: u64 = 800;
65const SSTORE_GAS: u64 = 20_000;
66const COPY_GAS: u64 = 3;
67
68pub fn create_arbowner_precompile() -> DynPrecompile {
69 DynPrecompile::new_stateful(PrecompileId::custom("arbowner"), handler)
70}
71
72fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
73 let gas_limit = input.gas;
74 let data = input.data;
75 if data.len() < 4 {
76 return crate::burn_all_revert(gas_limit);
77 }
78 let selector: [u8; 4] = [data[0], data[1], data[2], data[3]];
79
80 verify_owner(&mut input)?;
81
82 let call = match IArbOwner::ArbOwnerCalls::abi_decode(data) {
83 Ok(c) => c,
84 Err(_) => return crate::burn_all_revert(gas_limit),
85 };
86
87 crate::init_precompile_gas(data.len());
88
89 use IArbOwner::ArbOwnerCalls as Calls;
90 let is_read_only = matches!(
91 call,
92 Calls::getNetworkFeeAccount(_)
93 | Calls::getInfraFeeAccount(_)
94 | Calls::isChainOwner(_)
95 | Calls::getAllChainOwners(_)
96 | Calls::isTransactionFilterer(_)
97 | Calls::getAllTransactionFilterers(_)
98 | Calls::isNativeTokenOwner(_)
99 | Calls::getAllNativeTokenOwners(_)
100 | Calls::getFilteredFundsRecipient(_)
101 );
102
103 let result = match call {
104 Calls::getNetworkFeeAccount(_) => read_root_field(&mut input, NETWORK_FEE_ACCOUNT_OFFSET),
106 Calls::getInfraFeeAccount(_) => {
107 if let Some(r) = crate::check_method_version(gas_limit, 5, 0) {
108 return r;
109 }
110 read_root_field(&mut input, INFRA_FEE_ACCOUNT_OFFSET)
111 }
112 Calls::getFilteredFundsRecipient(_) => {
113 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
114 return r;
115 }
116 read_root_field(&mut input, FILTERED_FUNDS_RECIPIENT_OFFSET)
117 }
118 Calls::isChainOwner(_) => handle_is_chain_owner(&mut input),
119 Calls::getAllChainOwners(_) => handle_get_all_chain_owners(&mut input),
120 Calls::getAllTransactionFilterers(_) => {
121 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
122 return r;
123 }
124 handle_get_all_members(&mut input, TRANSACTION_FILTERER_SUBSPACE)
125 }
126 Calls::getAllNativeTokenOwners(_) => {
127 if let Some(r) = crate::check_method_version(gas_limit, 41, 0) {
128 return r;
129 }
130 handle_get_all_members(&mut input, NATIVE_TOKEN_SUBSPACE)
131 }
132 Calls::isTransactionFilterer(_) => {
133 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
134 return r;
135 }
136 handle_is_member(&mut input, TRANSACTION_FILTERER_SUBSPACE)
137 }
138 Calls::isNativeTokenOwner(_) => {
139 if let Some(r) = crate::check_method_version(gas_limit, 41, 0) {
140 return r;
141 }
142 handle_is_member(&mut input, NATIVE_TOKEN_SUBSPACE)
143 }
144
145 Calls::addChainOwner(_) => handle_add_chain_owner(&mut input),
147 Calls::removeChainOwner(_) => handle_remove_chain_owner(&mut input),
148
149 Calls::setNetworkFeeAccount(_) => write_root_field(&mut input, NETWORK_FEE_ACCOUNT_OFFSET),
151 Calls::setInfraFeeAccount(_) => {
152 if let Some(r) = crate::check_method_version(gas_limit, 5, 0) {
153 return r;
154 }
155 write_root_field(&mut input, INFRA_FEE_ACCOUNT_OFFSET)
156 }
157 Calls::setBrotliCompressionLevel(_) => {
158 if let Some(r) = crate::check_method_version(gas_limit, 20, 0) {
159 return r;
160 }
161 write_root_field(&mut input, BROTLI_COMPRESSION_LEVEL_OFFSET)
162 }
163 Calls::scheduleArbOSUpgrade(_) => handle_schedule_upgrade(&mut input),
164
165 Calls::setSpeedLimit(_) => match data.get(4..36) {
167 None => Err(PrecompileError::other("input too short")),
168 Some(bytes) if U256::from_be_slice(bytes).is_zero() => {
169 Err(PrecompileError::other("speed limit must be nonzero"))
170 }
171 Some(_) => write_l2_field(&mut input, L2_SPEED_LIMIT),
172 },
173 Calls::setL2BaseFee(_) => write_l2_field(&mut input, L2_BASE_FEE),
174 Calls::setMinimumL2BaseFee(_) => write_l2_field(&mut input, L2_MIN_BASE_FEE),
175 Calls::setMaxBlockGasLimit(_) => write_l2_field(&mut input, L2_PER_BLOCK_GAS_LIMIT),
176 Calls::setMaxTxGasLimit(_) => write_l2_field(&mut input, L2_PER_TX_GAS_LIMIT),
177 Calls::setL2GasPricingInertia(_) => match data.get(4..36) {
178 None => Err(PrecompileError::other("input too short")),
179 Some(bytes) if U256::from_be_slice(bytes).is_zero() => {
180 Err(PrecompileError::other("price inertia must be nonzero"))
181 }
182 Some(_) => write_l2_field(&mut input, L2_PRICING_INERTIA),
183 },
184 Calls::setL2GasBacklogTolerance(_) => write_l2_field(&mut input, L2_BACKLOG_TOLERANCE),
185 Calls::setGasBacklog(_) => {
186 if let Some(r) = crate::check_method_version(gas_limit, 50, 0) {
187 return r;
188 }
189 write_l2_field(&mut input, L2_GAS_BACKLOG)
190 }
191
192 Calls::setL1PricingEquilibrationUnits(_) => {
194 write_l1_field(&mut input, L1_EQUILIBRATION_UNITS)
195 }
196 Calls::setL1PricingInertia(_) => write_l1_field(&mut input, L1_INERTIA),
197 Calls::setL1PricingRewardRecipient(_) => write_l1_field(&mut input, L1_PAY_REWARDS_TO),
198 Calls::setL1PricingRewardRate(_) => write_l1_field(&mut input, L1_PER_UNIT_REWARD),
199 Calls::setL1PricePerUnit(_) => write_l1_field(&mut input, L1_PRICE_PER_UNIT),
200 Calls::setParentGasFloorPerToken(_) => {
201 if let Some(r) = crate::check_method_version(gas_limit, 50, 0) {
202 return r;
203 }
204 write_l1_field(&mut input, L1_GAS_FLOOR_PER_TOKEN)
205 }
206 Calls::setPerBatchGasCharge(_) => write_l1_field(&mut input, L1_PER_BATCH_GAS_COST),
207 Calls::setAmortizedCostCapBips(_) => write_l1_field(&mut input, L1_AMORTIZED_COST_CAP_BIPS),
208 Calls::setL1BaseFeeEstimateInertia(_) => write_l1_field(&mut input, L1_INERTIA),
209 Calls::releaseL1PricerSurplusFunds(_) => {
210 if let Some(r) = crate::check_method_version(gas_limit, 10, 0) {
211 return r;
212 }
213 handle_release_l1_pricer_surplus_funds(&mut input)
214 }
215
216 Calls::setInkPrice(_) => {
218 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
219 return r;
220 }
221 match read_u32_param(data) {
222 Err(e) => Err(e),
223 Ok(val) if val == 0 || val > 0xFF_FFFF => Err(PrecompileError::other(
224 "ink price must be a positive uint24",
225 )),
226 Ok(val) => write_stylus_param(&mut input, StylusField::InkPrice, val as u64),
227 }
228 }
229 Calls::setWasmMaxStackDepth(_) => {
230 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
231 return r;
232 }
233 let val = read_u32_param(data)?;
234 write_stylus_param(&mut input, StylusField::MaxStackDepth, val as u64)
235 }
236 Calls::setWasmFreePages(_) => {
237 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
238 return r;
239 }
240 let val = read_u32_param(data)?;
241 write_stylus_param(&mut input, StylusField::FreePages, val as u64)
242 }
243 Calls::setWasmPageGas(_) => {
244 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
245 return r;
246 }
247 let val = read_u32_param(data)?;
248 write_stylus_param(&mut input, StylusField::PageGas, val as u64)
249 }
250 Calls::setWasmPageLimit(_) => {
251 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
252 return r;
253 }
254 let val = read_u32_param(data)?;
255 write_stylus_param(&mut input, StylusField::PageLimit, val as u64)
256 }
257 Calls::setWasmMinInitGas(_) => {
258 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
259 return r;
260 }
261 let val = read_u32_param(data)?;
262 write_stylus_param(&mut input, StylusField::MinInitGas, val as u64)
263 }
264 Calls::setWasmInitCostScalar(_) => {
265 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
266 return r;
267 }
268 let val = read_u32_param(data)?;
269 let stored = (val as u64).saturating_add(1) / 2;
271 write_stylus_param(&mut input, StylusField::InitCostScalar, stored)
272 }
273 Calls::setWasmExpiryDays(_) => {
274 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
275 return r;
276 }
277 let val = read_u32_param(data)?;
278 write_stylus_param(&mut input, StylusField::ExpiryDays, val as u64)
279 }
280 Calls::setWasmKeepaliveDays(_) => {
281 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
282 return r;
283 }
284 let val = read_u32_param(data)?;
285 write_stylus_param(&mut input, StylusField::KeepaliveDays, val as u64)
286 }
287 Calls::setWasmBlockCacheSize(_) => {
288 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
289 return r;
290 }
291 let val = read_u32_param(data)?;
292 write_stylus_param(&mut input, StylusField::BlockCacheSize, val as u64)
293 }
294 Calls::setWasmMaxSize(_) => {
295 if let Some(r) = crate::check_method_version(gas_limit, 40, 0) {
296 return r;
297 }
298 let val = read_u32_param(data)?;
299 write_stylus_param(&mut input, StylusField::MaxWasmSize, val as u64)
300 }
301 Calls::setWasmActivationGas(_) => {
302 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
303 return r;
304 }
305 if data.len() < 36 {
306 return crate::burn_all_revert(gas_limit);
307 }
308 let val = U256::from_be_slice(&data[4..36]);
309 sstore_field(&mut input, programs_activation_gas_slot(), val)?;
310 Ok(PrecompileOutput::new(0, Vec::new().into()))
311 }
312 Calls::setMaxStylusContractFragments(_) => {
313 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
314 return r;
315 }
316 let val = read_u32_param(data)?;
317 write_stylus_param(&mut input, StylusField::MaxFragmentCount, val as u64)
318 }
319 Calls::addWasmCacheManager(_) => {
320 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
321 return r;
322 }
323 handle_add_cache_manager(&mut input)
324 }
325 Calls::removeWasmCacheManager(_) => {
326 if let Some(r) = crate::check_method_version(gas_limit, 30, 0) {
327 return r;
328 }
329 handle_remove_cache_manager(&mut input)
330 }
331 Calls::setCalldataPriceIncrease(_) => {
332 if let Some(r) = crate::check_method_version(gas_limit, 40, 0) {
333 return r;
334 }
335 handle_set_calldata_price_increase(&mut input)
336 }
337 Calls::setCollectTips(_) => {
338 if let Some(r) = crate::check_method_version(gas_limit, ARBOS_VERSION_60, 0) {
339 return r;
340 }
341 handle_set_collect_tips(&mut input)
342 }
343
344 Calls::addTransactionFilterer(_) => {
346 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
347 return r;
348 }
349 handle_add_to_set_with_feature_check(
350 &mut input,
351 TRANSACTION_FILTERER_SUBSPACE,
352 TX_FILTERING_ENABLED_FROM_TIME_OFFSET,
353 Some(IArbOwner::TransactionFiltererAdded::SIGNATURE_HASH),
354 )
355 }
356 Calls::removeTransactionFilterer(_) => {
357 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
358 return r;
359 }
360 handle_remove_from_set(
361 &mut input,
362 TRANSACTION_FILTERER_SUBSPACE,
363 Some(IArbOwner::TransactionFiltererRemoved::SIGNATURE_HASH),
364 )
365 }
366 Calls::setTransactionFilteringFrom(_) => {
367 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
368 return r;
369 }
370 handle_set_feature_time(&mut input, TX_FILTERING_ENABLED_FROM_TIME_OFFSET)
371 }
372 Calls::setFilteredFundsRecipient(_) => {
373 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
374 return r;
375 }
376 handle_set_filtered_funds_recipient(&mut input)
377 }
378
379 Calls::setNativeTokenManagementFrom(_) => {
381 if let Some(r) = crate::check_method_version(gas_limit, 41, 0) {
382 return r;
383 }
384 handle_set_feature_time(&mut input, NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET)
385 }
386 Calls::addNativeTokenOwner(_) => {
387 if let Some(r) = crate::check_method_version(gas_limit, 41, 0) {
388 return r;
389 }
390 handle_add_to_set_with_feature_check(
391 &mut input,
392 NATIVE_TOKEN_SUBSPACE,
393 NATIVE_TOKEN_ENABLED_FROM_TIME_OFFSET,
394 Some(IArbOwner::NativeTokenOwnerAdded::SIGNATURE_HASH),
395 )
396 }
397 Calls::removeNativeTokenOwner(_) => {
398 if let Some(r) = crate::check_method_version(gas_limit, 41, 0) {
399 return r;
400 }
401 handle_remove_from_set(
402 &mut input,
403 NATIVE_TOKEN_SUBSPACE,
404 Some(IArbOwner::NativeTokenOwnerRemoved::SIGNATURE_HASH),
405 )
406 }
407
408 Calls::setGasPricingConstraints(_) => {
410 if let Some(r) = crate::check_method_version(gas_limit, 50, 0) {
411 return r;
412 }
413 handle_set_gas_pricing_constraints(&mut input)
414 }
415 Calls::setMultiGasPricingConstraints(_) => {
416 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
417 return r;
418 }
419 handle_set_multi_gas_pricing_constraints(&mut input)
420 }
421
422 Calls::setChainConfig(_) => {
424 if let Some(r) = crate::check_method_version(gas_limit, 11, 0) {
425 return r;
426 }
427 handle_set_chain_config(&mut input)
428 }
429 };
430
431 let result = match result {
432 Ok(output) => {
433 if output.reverted {
434 Ok(PrecompileOutput::new_reverted(0, output.bytes))
435 } else {
436 let arbos_version = crate::get_arbos_version();
437 if !is_read_only || arbos_version < 11 {
438 emit_owner_acts(&mut input, &selector, data);
439 }
440 Ok(PrecompileOutput::new(0, output.bytes))
441 }
442 }
443 Err(_) => Ok(PrecompileOutput::new_reverted(0, Default::default())),
444 };
445 crate::gas_check(gas_limit, result)
446}
447
448fn verify_owner(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
451 let caller = input.caller;
452 load_arbos(input)?;
453
454 let set_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_OWNER_SUBSPACE);
457 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
458
459 let addr_as_b256 = alloy_primitives::B256::left_padding_from(caller.as_slice());
460 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_as_b256);
461
462 let value = sload_field(input, member_slot)?;
463 crate::charge_precompile_gas(800); if value == U256::ZERO {
465 return Err(PrecompileError::other(
466 "ArbOwner: caller is not a chain owner",
467 ));
468 }
469 Ok(())
470}
471
472fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
475 input
476 .internals_mut()
477 .load_account(ARBOS_STATE_ADDRESS)
478 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
479 Ok(())
480}
481
482fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
483 let val = input
484 .internals_mut()
485 .sload(ARBOS_STATE_ADDRESS, slot)
486 .map_err(|_| PrecompileError::other("sload failed"))?;
487 crate::charge_precompile_gas(SLOAD_GAS);
488 Ok(val.data)
489}
490
491fn sstore_field(
492 input: &mut PrecompileInput<'_>,
493 slot: U256,
494 value: U256,
495) -> Result<(), PrecompileError> {
496 input
497 .internals_mut()
498 .sstore(ARBOS_STATE_ADDRESS, slot, value)
499 .map_err(|_| PrecompileError::other("sstore failed"))?;
500 crate::charge_precompile_gas(SSTORE_GAS);
501 Ok(())
502}
503
504fn read_root_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
505 let gas_limit = input.gas;
506 let value = sload_field(input, root_slot(offset))?;
507 Ok(PrecompileOutput::new(
508 (SLOAD_GAS + COPY_GAS).min(gas_limit),
509 value.to_be_bytes::<32>().to_vec().into(),
510 ))
511}
512
513fn write_root_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
514 let data = input.data;
515 if data.len() < 36 {
516 return crate::burn_all_revert(input.gas);
517 }
518 let gas_limit = input.gas;
519 let value = U256::from_be_slice(&data[4..36]);
520 sstore_field(input, root_slot(offset), value)?;
521 Ok(PrecompileOutput::new(
522 (SSTORE_GAS + COPY_GAS).min(gas_limit),
523 Vec::new().into(),
524 ))
525}
526
527fn handle_set_filtered_funds_recipient(input: &mut PrecompileInput<'_>) -> PrecompileResult {
529 let data = input.data;
530 if data.len() < 36 {
531 return crate::burn_all_revert(input.gas);
532 }
533 let gas_limit = input.gas;
534 let addr = Address::from_slice(&data[16..36]);
535 sstore_field(
536 input,
537 root_slot(FILTERED_FUNDS_RECIPIENT_OFFSET),
538 U256::from_be_slice(addr.as_slice()),
539 )?;
540 emit_address_event(
541 input,
542 IArbOwner::FilteredFundsRecipientSet::SIGNATURE_HASH,
543 addr,
544 );
545 Ok(PrecompileOutput::new(
546 (SSTORE_GAS + COPY_GAS).min(gas_limit),
547 Vec::new().into(),
548 ))
549}
550
551fn write_l1_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
552 let data = input.data;
553 if data.len() < 36 {
554 return crate::burn_all_revert(input.gas);
555 }
556 let gas_limit = input.gas;
557 let value = U256::from_be_slice(&data[4..36]);
558 let field_slot = subspace_slot(L1_PRICING_SUBSPACE, offset);
559 sstore_field(input, field_slot, value)?;
560 Ok(PrecompileOutput::new(
561 (SSTORE_GAS + COPY_GAS).min(gas_limit),
562 Vec::new().into(),
563 ))
564}
565
566fn write_l2_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
567 let data = input.data;
568 if data.len() < 36 {
569 return crate::burn_all_revert(input.gas);
570 }
571 let gas_limit = input.gas;
572 let value = U256::from_be_slice(&data[4..36]);
573 let field_slot = subspace_slot(L2_PRICING_SUBSPACE, offset);
574 sstore_field(input, field_slot, value)?;
575 Ok(PrecompileOutput::new(
576 (SSTORE_GAS + COPY_GAS).min(gas_limit),
577 Vec::new().into(),
578 ))
579}
580
581fn handle_schedule_upgrade(input: &mut PrecompileInput<'_>) -> PrecompileResult {
582 let data = input.data;
583 if data.len() < 68 {
584 return crate::burn_all_revert(input.gas);
585 }
586 let gas_limit = input.gas;
587 let new_version = U256::from_be_slice(&data[4..36]);
588 let timestamp = U256::from_be_slice(&data[36..68]);
589 sstore_field(input, root_slot(UPGRADE_VERSION_OFFSET), new_version)?;
590 sstore_field(input, root_slot(UPGRADE_TIMESTAMP_OFFSET), timestamp)?;
591 Ok(PrecompileOutput::new(
592 (2 * SSTORE_GAS + COPY_GAS).min(gas_limit),
593 Vec::new().into(),
594 ))
595}
596
597fn emit_owner_acts(input: &mut PrecompileInput<'_>, selector: &[u8; 4], calldata: &[u8]) {
599 use alloy_primitives::{Log, B256};
600
601 let topic0 = IArbOwner::OwnerActs::SIGNATURE_HASH;
602 let mut method_topic = [0u8; 32];
603 method_topic[..4].copy_from_slice(selector);
604 let topic1 = B256::from(method_topic);
605 let topic2 = B256::left_padding_from(input.caller.as_slice());
606
607 let mut log_data = Vec::with_capacity(64 + calldata.len().div_ceil(32) * 32);
609 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);
612 let pad = (32 - (calldata.len() % 32)) % 32;
614 log_data.extend(std::iter::repeat_n(0u8, pad));
615
616 input.internals_mut().log(Log::new_unchecked(
617 ARBOWNER_ADDRESS,
618 vec![topic0, topic1, topic2],
619 log_data.into(),
620 ));
621}
622
623fn handle_is_chain_owner(input: &mut PrecompileInput<'_>) -> PrecompileResult {
627 handle_is_member(input, CHAIN_OWNER_SUBSPACE)
628}
629
630fn handle_get_all_chain_owners(input: &mut PrecompileInput<'_>) -> PrecompileResult {
632 handle_get_all_members(input, CHAIN_OWNER_SUBSPACE)
633}
634
635fn handle_add_chain_owner(input: &mut PrecompileInput<'_>) -> PrecompileResult {
637 let data = input.data;
638 if data.len() < 36 {
639 return crate::burn_all_revert(input.gas);
640 }
641 let gas_limit = input.gas;
642 let addr = Address::from_slice(&data[16..36]);
643
644 address_set_add(input, address_set_key(CHAIN_OWNER_SUBSPACE), addr)?;
645
646 let arbos_version = read_arbos_version(input)?;
647 if arbos_version >= 60 {
648 let topic1 = B256::left_padding_from(addr.as_slice());
649 input.internals_mut().log(Log::new_unchecked(
650 ARBOWNER_ADDRESS,
651 vec![IArbOwner::ChainOwnerAdded::SIGNATURE_HASH, topic1],
652 alloy_primitives::Bytes::new(),
653 ));
654 }
655
656 let gas_used = 2 * SLOAD_GAS + 3 * SSTORE_GAS + COPY_GAS;
657 Ok(PrecompileOutput::new(
658 gas_used.min(gas_limit),
659 Vec::new().into(),
660 ))
661}
662
663fn handle_remove_chain_owner(input: &mut PrecompileInput<'_>) -> PrecompileResult {
665 let data = input.data;
666 if data.len() < 36 {
667 return crate::burn_all_revert(input.gas);
668 }
669 let gas_limit = input.gas;
670 let addr = Address::from_slice(&data[16..36]);
671
672 let set_key = address_set_key(CHAIN_OWNER_SUBSPACE);
673 if !is_member_of(input, set_key, addr)? {
674 return Err(PrecompileError::other("tried to remove non-owner"));
675 }
676 address_set_remove(input, set_key, addr)?;
677
678 let arbos_version = read_arbos_version(input)?;
679 if arbos_version >= 60 {
680 let topic1 = B256::left_padding_from(addr.as_slice());
681 input.internals_mut().log(Log::new_unchecked(
682 ARBOWNER_ADDRESS,
683 vec![IArbOwner::ChainOwnerRemoved::SIGNATURE_HASH, topic1],
684 alloy_primitives::Bytes::new(),
685 ));
686 }
687
688 let gas_used = 3 * SLOAD_GAS + 4 * SSTORE_GAS + COPY_GAS;
689 Ok(PrecompileOutput::new(
690 gas_used.min(gas_limit),
691 Vec::new().into(),
692 ))
693}
694
695fn handle_release_l1_pricer_surplus_funds(input: &mut PrecompileInput<'_>) -> PrecompileResult {
700 let data = input.data;
701 if data.len() < 36 {
702 return crate::burn_all_revert(input.gas);
703 }
704 let gas_limit = input.gas;
705 let max_wei = U256::from_be_slice(&data[4..36]);
706
707 let pool_balance = {
709 let acct = input
710 .internals_mut()
711 .load_account(L1_PRICER_FUNDS_POOL_ADDRESS)
712 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
713 acct.data.info.balance
714 };
715
716 load_arbos(input)?;
718 let avail_slot = subspace_slot(L1_PRICING_SUBSPACE, L1_FEES_AVAILABLE);
719 let recognized = sload_field(input, avail_slot)?;
720
721 if pool_balance <= recognized {
723 return Ok(PrecompileOutput::new(
725 (SLOAD_GAS + COPY_GAS + 100).min(gas_limit),
726 U256::ZERO.to_be_bytes::<32>().to_vec().into(),
727 ));
728 }
729
730 let mut wei_to_transfer = pool_balance - recognized;
731 if wei_to_transfer > max_wei {
732 wei_to_transfer = max_wei;
733 }
734
735 let new_available = recognized + wei_to_transfer;
737 sstore_field(input, avail_slot, new_available)?;
738
739 Ok(PrecompileOutput::new(
740 (SLOAD_GAS + SSTORE_GAS + COPY_GAS + 100).min(gas_limit),
741 wei_to_transfer.to_be_bytes::<32>().to_vec().into(),
742 ))
743}
744
745fn address_set_key(subspace: &[u8]) -> B256 {
747 derive_subspace_key(ROOT_STORAGE_KEY, subspace)
748}
749
750fn is_member_of(
752 input: &mut PrecompileInput<'_>,
753 set_key: B256,
754 addr: Address,
755) -> Result<bool, PrecompileError> {
756 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
757 let addr_hash = B256::left_padding_from(addr.as_slice());
758 let slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
759 let val = sload_field(input, slot)?;
760 Ok(val != U256::ZERO)
761}
762
763fn handle_is_member(input: &mut PrecompileInput<'_>, subspace: &[u8]) -> PrecompileResult {
765 let data = input.data;
766 if data.len() < 36 {
767 return crate::burn_all_revert(input.gas);
768 }
769 let gas_limit = input.gas;
770 let addr = Address::from_slice(&data[16..36]);
771 let is_member = is_member_of(input, address_set_key(subspace), addr)?;
772 let result = if is_member {
773 U256::from(1u64)
774 } else {
775 U256::ZERO
776 };
777 Ok(PrecompileOutput::new(
778 (SLOAD_GAS + COPY_GAS).min(gas_limit),
779 result.to_be_bytes::<32>().to_vec().into(),
780 ))
781}
782
783fn handle_get_all_members(input: &mut PrecompileInput<'_>, subspace: &[u8]) -> PrecompileResult {
785 let gas_limit = input.gas;
786 let set_key = address_set_key(subspace);
787 let size_slot = map_slot(set_key.as_slice(), 0);
788 let size = sload_field(input, size_slot)?;
789 let count: u64 = size
790 .try_into()
791 .map_err(|_| PrecompileError::other("invalid address set size"))?;
792 const MAX_MEMBERS: u64 = 65536;
793 let count = count.min(MAX_MEMBERS);
794
795 let mut addresses = Vec::with_capacity(count as usize);
796 for i in 1..=count {
797 let member_slot = map_slot(set_key.as_slice(), i);
798 let val = sload_field(input, member_slot)?;
799 addresses.push(val);
800 }
801
802 let mut out = Vec::with_capacity(64 + 32 * addresses.len());
803 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
804 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
805 for addr_val in &addresses {
806 out.extend_from_slice(&addr_val.to_be_bytes::<32>());
807 }
808
809 let gas_used = (1 + count) * SLOAD_GAS + COPY_GAS;
810 Ok(PrecompileOutput::new(gas_used.min(gas_limit), out.into()))
811}
812
813fn address_set_add(
815 input: &mut PrecompileInput<'_>,
816 set_key: B256,
817 addr: Address,
818) -> Result<bool, PrecompileError> {
819 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
820 let addr_hash = B256::left_padding_from(addr.as_slice());
821 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
822 let existing = sload_field(input, member_slot)?;
823
824 if existing != U256::ZERO {
825 return Ok(false); }
827
828 let size_slot = map_slot(set_key.as_slice(), 0);
830 let size = sload_field(input, size_slot)?;
831 let size_u64: u64 = size
832 .try_into()
833 .map_err(|_| PrecompileError::other("invalid address set size"))?;
834 let new_size = size_u64 + 1;
835
836 let new_pos_slot = map_slot(set_key.as_slice(), new_size);
838 let addr_as_u256 = U256::from_be_slice(addr.as_slice());
839 sstore_field(input, new_pos_slot, addr_as_u256)?;
840
841 sstore_field(input, member_slot, U256::from(new_size))?;
843
844 sstore_field(input, size_slot, U256::from(new_size))?;
846
847 Ok(true)
848}
849
850fn address_set_remove(
852 input: &mut PrecompileInput<'_>,
853 set_key: B256,
854 addr: Address,
855) -> Result<(), PrecompileError> {
856 let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
857 let addr_hash = B256::left_padding_from(addr.as_slice());
858 let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
859
860 let position = sload_field(input, member_slot)?;
862 if position == U256::ZERO {
863 return Err(PrecompileError::other("address not in set"));
864 }
865 let pos_u64: u64 = position
866 .try_into()
867 .map_err(|_| PrecompileError::other("invalid position"))?;
868
869 sstore_field(input, member_slot, U256::ZERO)?;
871
872 let size_slot = map_slot(set_key.as_slice(), 0);
874 let size = sload_field(input, size_slot)?;
875 let size_u64: u64 = size
876 .try_into()
877 .map_err(|_| PrecompileError::other("invalid size"))?;
878
879 if pos_u64 < size_u64 {
881 let last_slot = map_slot(set_key.as_slice(), size_u64);
882 let last_val = sload_field(input, last_slot)?;
883
884 let removed_slot = map_slot(set_key.as_slice(), pos_u64);
886 sstore_field(input, removed_slot, last_val)?;
887
888 let last_bytes = last_val.to_be_bytes::<32>();
890 let last_hash = B256::from(last_bytes);
891 let last_member_slot = map_slot_b256(by_address_key.as_slice(), &last_hash);
892 sstore_field(input, last_member_slot, U256::from(pos_u64))?;
893 }
894
895 let last_pos_slot = map_slot(set_key.as_slice(), size_u64);
897 sstore_field(input, last_pos_slot, U256::ZERO)?;
898
899 sstore_field(input, size_slot, U256::from(size_u64 - 1))?;
901
902 Ok(())
903}
904
905enum StylusField {
926 InkPrice, MaxStackDepth, FreePages, PageGas, PageLimit, MinInitGas, InitCostScalar, ExpiryDays, KeepaliveDays, BlockCacheSize, MaxWasmSize, MaxFragmentCount, }
939
940impl StylusField {
941 fn byte_range(&self) -> (usize, usize) {
942 match self {
943 Self::InkPrice => (2, 5),
944 Self::MaxStackDepth => (5, 9),
945 Self::FreePages => (9, 11),
946 Self::PageGas => (11, 13),
947 Self::PageLimit => (13, 15),
948 Self::MinInitGas => (15, 16),
949 Self::InitCostScalar => (17, 18),
950 Self::ExpiryDays => (19, 21),
951 Self::KeepaliveDays => (21, 23),
952 Self::BlockCacheSize => (23, 25),
953 Self::MaxWasmSize => (25, 29),
954 Self::MaxFragmentCount => (29, 30),
955 }
956 }
957}
958
959fn programs_params_slot() -> U256 {
961 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
963 let params_key = derive_subspace_key(programs_key.as_slice(), &[0]); map_slot(params_key.as_slice(), 0)
965}
966
967fn programs_activation_gas_slot() -> U256 {
969 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
970 let activation_key = derive_subspace_key(programs_key.as_slice(), &[5]);
971 map_slot(activation_key.as_slice(), 0)
972}
973
974fn read_stylus_params_word(input: &mut PrecompileInput<'_>) -> Result<[u8; 32], PrecompileError> {
976 let slot = programs_params_slot();
977 let val = sload_field(input, slot)?;
978 Ok(val.to_be_bytes::<32>())
979}
980
981fn write_stylus_param(
983 input: &mut PrecompileInput<'_>,
984 field: StylusField,
985 value: u64,
986) -> PrecompileResult {
987 let gas_limit = input.gas;
988 let slot = programs_params_slot();
989 let mut word = read_stylus_params_word(input)?;
990
991 let (start, end) = field.byte_range();
992 let len = end - start;
993 let bytes = value.to_be_bytes();
994 word[start..end].copy_from_slice(&bytes[8 - len..]);
996
997 sstore_field(input, slot, U256::from_be_bytes(word))?;
998 Ok(PrecompileOutput::new(
999 (SLOAD_GAS + SSTORE_GAS + COPY_GAS).min(gas_limit),
1000 Vec::new().into(),
1001 ))
1002}
1003
1004fn read_u32_param(data: &[u8]) -> Result<u32, PrecompileError> {
1006 if data.len() < 36 {
1007 return Err(PrecompileError::other("input too short"));
1008 }
1009 let val = U256::from_be_slice(&data[4..36]);
1010 val.try_into()
1011 .map_err(|_| PrecompileError::other("value overflow"))
1012}
1013
1014fn cache_managers_set_key() -> B256 {
1018 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
1019 derive_subspace_key(programs_key.as_slice(), CACHE_MANAGERS_KEY)
1020}
1021
1022fn handle_add_cache_manager(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1023 let data = input.data;
1024 if data.len() < 36 {
1025 return crate::burn_all_revert(input.gas);
1026 }
1027 let gas_limit = input.gas;
1028 let addr = Address::from_slice(&data[16..36]);
1029 address_set_add(input, cache_managers_set_key(), addr)?;
1030 let gas_used = 2 * SLOAD_GAS + 3 * SSTORE_GAS + COPY_GAS;
1031 Ok(PrecompileOutput::new(
1032 gas_used.min(gas_limit),
1033 Vec::new().into(),
1034 ))
1035}
1036
1037fn handle_remove_cache_manager(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1038 let data = input.data;
1039 if data.len() < 36 {
1040 return crate::burn_all_revert(input.gas);
1041 }
1042 let gas_limit = input.gas;
1043 let addr = Address::from_slice(&data[16..36]);
1044 let set_key = cache_managers_set_key();
1045 if !is_member_of(input, set_key, addr)? {
1046 return Err(PrecompileError::other("address is not a cache manager"));
1047 }
1048 address_set_remove(input, set_key, addr)?;
1049 let gas_used = 3 * SLOAD_GAS + 4 * SSTORE_GAS + COPY_GAS;
1050 Ok(PrecompileOutput::new(
1051 gas_used.min(gas_limit),
1052 Vec::new().into(),
1053 ))
1054}
1055
1056const FEATURE_ENABLE_DELAY: u64 = 7 * 24 * 60 * 60;
1058
1059fn handle_set_feature_time(input: &mut PrecompileInput<'_>, time_offset: u64) -> PrecompileResult {
1061 let data = input.data;
1062 if data.len() < 36 {
1063 return crate::burn_all_revert(input.gas);
1064 }
1065 let gas_limit = input.gas;
1066 let timestamp: u64 = U256::from_be_slice(&data[4..36])
1067 .try_into()
1068 .map_err(|_| PrecompileError::other("timestamp too large"))?;
1069
1070 if timestamp == 0 {
1071 sstore_field(input, root_slot(time_offset), U256::ZERO)?;
1073 return Ok(PrecompileOutput::new(
1074 (SSTORE_GAS + COPY_GAS).min(gas_limit),
1075 Vec::new().into(),
1076 ));
1077 }
1078
1079 let stored_val = sload_field(input, root_slot(time_offset))?;
1080 let stored: u64 = stored_val.try_into().unwrap_or(0);
1081 let now = input
1082 .internals_mut()
1083 .block_timestamp()
1084 .try_into()
1085 .unwrap_or(0u64);
1086
1087 if (stored > now + FEATURE_ENABLE_DELAY || stored == 0)
1089 && timestamp < now + FEATURE_ENABLE_DELAY
1090 {
1091 return Err(PrecompileError::other(
1092 "feature must be enabled at least 7 days in the future",
1093 ));
1094 }
1095 if stored > now && stored <= now + FEATURE_ENABLE_DELAY && timestamp < stored {
1096 return Err(PrecompileError::other(
1097 "feature cannot be updated to a time earlier than the current scheduled enable time",
1098 ));
1099 }
1100
1101 sstore_field(input, root_slot(time_offset), U256::from(timestamp))?;
1102 Ok(PrecompileOutput::new(
1103 (SLOAD_GAS + SSTORE_GAS + COPY_GAS).min(gas_limit),
1104 Vec::new().into(),
1105 ))
1106}
1107
1108fn emit_address_event(input: &mut PrecompileInput<'_>, topic0: B256, addr: Address) {
1110 let topic1 = B256::left_padding_from(addr.as_slice());
1111 input.internals_mut().log(Log::new_unchecked(
1112 ARBOWNER_ADDRESS,
1113 vec![topic0, topic1],
1114 alloy_primitives::Bytes::new(),
1115 ));
1116}
1117
1118fn handle_add_to_set_with_feature_check(
1119 input: &mut PrecompileInput<'_>,
1120 subspace: &[u8],
1121 time_offset: u64,
1122 event_topic: Option<B256>,
1123) -> PrecompileResult {
1124 let data = input.data;
1125 if data.len() < 36 {
1126 return crate::burn_all_revert(input.gas);
1127 }
1128 let gas_limit = input.gas;
1129 let addr = Address::from_slice(&data[16..36]);
1130
1131 let enabled_time_val = sload_field(input, root_slot(time_offset))?;
1132 let enabled_time: u64 = enabled_time_val.try_into().unwrap_or(0);
1133 let now: u64 = input
1134 .internals_mut()
1135 .block_timestamp()
1136 .try_into()
1137 .unwrap_or(0u64);
1138
1139 if enabled_time == 0 || enabled_time > now {
1140 return Err(PrecompileError::other("feature is not enabled yet"));
1141 }
1142
1143 address_set_add(input, address_set_key(subspace), addr)?;
1144
1145 if let Some(topic0) = event_topic {
1146 emit_address_event(input, topic0, addr);
1147 }
1148
1149 let gas_used = 2 * SLOAD_GAS + 3 * SSTORE_GAS + COPY_GAS;
1150 Ok(PrecompileOutput::new(
1151 gas_used.min(gas_limit),
1152 Vec::new().into(),
1153 ))
1154}
1155
1156fn handle_remove_from_set(
1157 input: &mut PrecompileInput<'_>,
1158 subspace: &[u8],
1159 event_topic: Option<B256>,
1160) -> PrecompileResult {
1161 let data = input.data;
1162 if data.len() < 36 {
1163 return crate::burn_all_revert(input.gas);
1164 }
1165 let gas_limit = input.gas;
1166 let addr = Address::from_slice(&data[16..36]);
1167
1168 let set_key = address_set_key(subspace);
1169 if !is_member_of(input, set_key, addr)? {
1170 return Err(PrecompileError::other("address is not a member"));
1171 }
1172
1173 address_set_remove(input, set_key, addr)?;
1174
1175 if let Some(topic0) = event_topic {
1176 emit_address_event(input, topic0, addr);
1177 }
1178
1179 let gas_used = 3 * SLOAD_GAS + 4 * SSTORE_GAS + COPY_GAS;
1180 Ok(PrecompileOutput::new(
1181 gas_used.min(gas_limit),
1182 Vec::new().into(),
1183 ))
1184}
1185
1186const GC_KEY: &[u8] = &[0];
1190const MGC_KEY: &[u8] = &[1];
1191const CONSTRAINT_TARGET: u64 = 0;
1192const CONSTRAINT_WINDOW: u64 = 1;
1193const CONSTRAINT_BACKLOG: u64 = 2;
1194const MGC_MAX_WEIGHT: u64 = 3;
1195const MGC_WEIGHTS_BASE: u64 = 4;
1196const NUM_RESOURCE_KINDS: u64 = 9;
1197const GAS_CONSTRAINTS_MAX_NUM: usize = 20;
1198const MAX_PRICING_EXPONENT_BIPS: u64 = 85_000;
1199
1200fn l2_pricing_key() -> B256 {
1202 derive_subspace_key(ROOT_STORAGE_KEY, L2_PRICING_SUBSPACE)
1203}
1204
1205fn gc_vector_key() -> B256 {
1207 derive_subspace_key(l2_pricing_key().as_slice(), GC_KEY)
1208}
1209
1210fn mgc_vector_key() -> B256 {
1212 derive_subspace_key(l2_pricing_key().as_slice(), MGC_KEY)
1213}
1214
1215fn vector_length_slot(vector_key: B256) -> U256 {
1217 map_slot(vector_key.as_slice(), 0)
1218}
1219
1220fn vector_element_key(vector_key: B256, index: u64) -> B256 {
1222 derive_subspace_key(vector_key.as_slice(), &index.to_be_bytes())
1223}
1224
1225fn constraint_field_slot(element_key: B256, field_offset: u64) -> U256 {
1227 map_slot(element_key.as_slice(), field_offset)
1228}
1229
1230fn read_arbos_version(input: &mut PrecompileInput<'_>) -> Result<u64, PrecompileError> {
1232 let val = sload_field(input, root_slot(0))?; val.try_into()
1234 .map_err(|_| PrecompileError::other("invalid ArbOS version"))
1235}
1236
1237fn clear_gas_constraints_vector(
1239 input: &mut PrecompileInput<'_>,
1240 vector_key: B256,
1241 fields_per_element: u64,
1242) -> Result<u64, PrecompileError> {
1243 let len_slot = vector_length_slot(vector_key);
1244 let len: u64 = sload_field(input, len_slot)?.try_into().unwrap_or(0);
1245
1246 for i in 0..len {
1248 let elem_key = vector_element_key(vector_key, i);
1249 for f in 0..fields_per_element {
1250 sstore_field(input, constraint_field_slot(elem_key, f), U256::ZERO)?;
1251 }
1252 }
1253
1254 sstore_field(input, len_slot, U256::ZERO)?;
1256
1257 Ok(len)
1258}
1259
1260fn clear_multi_gas_constraints(input: &mut PrecompileInput<'_>) -> Result<u64, PrecompileError> {
1262 let vector_key = mgc_vector_key();
1263 let len_slot = vector_length_slot(vector_key);
1264 let len: u64 = sload_field(input, len_slot)?.try_into().unwrap_or(0);
1265
1266 for i in 0..len {
1267 let elem_key = vector_element_key(vector_key, i);
1268 for f in 0..4 {
1270 sstore_field(input, constraint_field_slot(elem_key, f), U256::ZERO)?;
1271 }
1272 for r in 0..NUM_RESOURCE_KINDS {
1274 sstore_field(
1275 input,
1276 constraint_field_slot(elem_key, MGC_WEIGHTS_BASE + r),
1277 U256::ZERO,
1278 )?;
1279 }
1280 }
1281
1282 sstore_field(input, len_slot, U256::ZERO)?;
1283 Ok(len)
1284}
1285
1286fn handle_set_gas_pricing_constraints(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1293 let data = input.data;
1294 if data.len() < 68 {
1296 return crate::burn_all_revert(input.gas);
1297 }
1298 let gas_limit = input.gas;
1299
1300 let count: u64 = U256::from_be_slice(&data[36..68])
1302 .try_into()
1303 .map_err(|_| PrecompileError::other("array length overflow"))?;
1304
1305 let expected_len = 68 + (count as usize) * 96;
1307 if data.len() < expected_len {
1308 return crate::burn_all_revert(gas_limit);
1309 }
1310
1311 let vector_key = gc_vector_key();
1313 clear_gas_constraints_vector(input, vector_key, 3)?;
1314
1315 let arbos_version = read_arbos_version(input)?;
1317 use arb_chainspec::arbos_version as arb_ver;
1318 if (arb_ver::ARBOS_VERSION_MULTI_CONSTRAINT_FIX..arb_ver::ARBOS_VERSION_MULTI_GAS_CONSTRAINTS)
1319 .contains(&arbos_version)
1320 && (count as usize) > GAS_CONSTRAINTS_MAX_NUM
1321 {
1322 return Err(PrecompileError::other("too many constraints"));
1323 }
1324
1325 let len_slot = vector_length_slot(vector_key);
1327 for i in 0..count {
1328 let base = 68 + (i as usize) * 96;
1329 let target: u64 = U256::from_be_slice(&data[base..base + 32])
1330 .try_into()
1331 .unwrap_or(0);
1332 let window: u64 = U256::from_be_slice(&data[base + 32..base + 64])
1333 .try_into()
1334 .unwrap_or(0);
1335 let backlog: u64 = U256::from_be_slice(&data[base + 64..base + 96])
1336 .try_into()
1337 .unwrap_or(0);
1338
1339 if target == 0 || window == 0 {
1340 return Err(PrecompileError::other("invalid constraint parameters"));
1341 }
1342
1343 let elem_key = vector_element_key(vector_key, i);
1345 sstore_field(
1346 input,
1347 constraint_field_slot(elem_key, CONSTRAINT_TARGET),
1348 U256::from(target),
1349 )?;
1350 sstore_field(
1351 input,
1352 constraint_field_slot(elem_key, CONSTRAINT_WINDOW),
1353 U256::from(window),
1354 )?;
1355 sstore_field(
1356 input,
1357 constraint_field_slot(elem_key, CONSTRAINT_BACKLOG),
1358 U256::from(backlog),
1359 )?;
1360
1361 sstore_field(input, len_slot, U256::from(i + 1))?;
1363 }
1364
1365 let gas_used = SLOAD_GAS + (count * 4 + 2) * SSTORE_GAS + count * SLOAD_GAS + COPY_GAS;
1367 Ok(PrecompileOutput::new(
1368 gas_used.min(gas_limit),
1369 Vec::new().into(),
1370 ))
1371}
1372
1373fn handle_set_multi_gas_pricing_constraints(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1378 let data = input.data;
1379 if data.len() < 68 {
1380 return crate::burn_all_revert(input.gas);
1381 }
1382 let gas_limit = input.gas;
1383
1384 clear_multi_gas_constraints(input)?;
1386
1387 let _outer_offset: usize = U256::from_be_slice(&data[4..36])
1389 .try_into()
1390 .unwrap_or(0usize);
1391 let count: u64 = U256::from_be_slice(&data[36..68])
1393 .try_into()
1394 .map_err(|_| PrecompileError::other("array length overflow"))?;
1395
1396 let array_data_start = 68; let mut struct_offsets = Vec::with_capacity(count as usize);
1401 for i in 0..count as usize {
1402 let offset_pos = array_data_start + i * 32;
1403 if data.len() < offset_pos + 32 {
1404 return crate::burn_all_revert(gas_limit);
1405 }
1406 let offset: usize = U256::from_be_slice(&data[offset_pos..offset_pos + 32])
1407 .try_into()
1408 .unwrap_or(0);
1409 struct_offsets.push(array_data_start + offset);
1411 }
1412
1413 let vector_key = mgc_vector_key();
1414 let len_slot = vector_length_slot(vector_key);
1415
1416 for (i, &struct_start) in struct_offsets.iter().enumerate() {
1417 if data.len() < struct_start + 128 {
1419 return crate::burn_all_revert(gas_limit);
1420 }
1421
1422 let resources_offset: usize = U256::from_be_slice(&data[struct_start..struct_start + 32])
1423 .try_into()
1424 .unwrap_or(0);
1425 let window: u64 = U256::from_be_slice(&data[struct_start + 32..struct_start + 64])
1426 .try_into()
1427 .unwrap_or(0);
1428 let target: u64 = U256::from_be_slice(&data[struct_start + 64..struct_start + 96])
1429 .try_into()
1430 .unwrap_or(0);
1431 let backlog: u64 = U256::from_be_slice(&data[struct_start + 96..struct_start + 128])
1432 .try_into()
1433 .unwrap_or(0);
1434
1435 if target == 0 || window == 0 {
1436 return Err(PrecompileError::other("invalid constraint parameters"));
1437 }
1438 let resources_start = struct_start + resources_offset;
1439
1440 if data.len() < resources_start + 32 {
1441 return crate::burn_all_revert(gas_limit);
1442 }
1443
1444 let num_resources: usize =
1445 U256::from_be_slice(&data[resources_start..resources_start + 32])
1446 .try_into()
1447 .unwrap_or(0);
1448
1449 let mut weights = [0u64; 9];
1451 let mut max_weight = 0u64;
1452 for r in 0..num_resources {
1453 let r_start = resources_start + 32 + r * 64;
1454 if data.len() < r_start + 64 {
1455 return crate::burn_all_revert(gas_limit);
1456 }
1457 let resource: u8 = U256::from_be_slice(&data[r_start..r_start + 32])
1458 .try_into()
1459 .unwrap_or(0);
1460 let weight: u64 = U256::from_be_slice(&data[r_start + 32..r_start + 64])
1461 .try_into()
1462 .unwrap_or(0);
1463
1464 if (resource as u64) < NUM_RESOURCE_KINDS {
1465 weights[resource as usize] = weight;
1466 if weight > max_weight {
1467 max_weight = weight;
1468 }
1469 }
1470 }
1471
1472 let elem_key = vector_element_key(vector_key, i as u64);
1474 sstore_field(
1475 input,
1476 constraint_field_slot(elem_key, CONSTRAINT_TARGET),
1477 U256::from(target),
1478 )?;
1479 sstore_field(
1480 input,
1481 constraint_field_slot(elem_key, CONSTRAINT_WINDOW),
1482 U256::from(window),
1483 )?;
1484 sstore_field(
1485 input,
1486 constraint_field_slot(elem_key, CONSTRAINT_BACKLOG),
1487 U256::from(backlog),
1488 )?;
1489 sstore_field(
1490 input,
1491 constraint_field_slot(elem_key, MGC_MAX_WEIGHT),
1492 U256::from(max_weight),
1493 )?;
1494
1495 for (r, &weight) in weights.iter().enumerate().take(NUM_RESOURCE_KINDS as usize) {
1497 sstore_field(
1498 input,
1499 constraint_field_slot(elem_key, MGC_WEIGHTS_BASE + r as u64),
1500 U256::from(weight),
1501 )?;
1502 }
1503
1504 sstore_field(input, len_slot, U256::from(i as u64 + 1))?;
1506
1507 validate_multi_gas_exponents(input, vector_key, i as u64 + 1)?;
1509 }
1510
1511 let gas_used = (count * 16 + 2) * SSTORE_GAS + (count * 12 + 2) * SLOAD_GAS + COPY_GAS;
1512 Ok(PrecompileOutput::new(
1513 gas_used.min(gas_limit),
1514 Vec::new().into(),
1515 ))
1516}
1517
1518fn validate_multi_gas_exponents(
1520 input: &mut PrecompileInput<'_>,
1521 vector_key: B256,
1522 count: u64,
1523) -> Result<(), PrecompileError> {
1524 let mut exponents = [0u64; 8];
1525
1526 for i in 0..count {
1527 let elem_key = vector_element_key(vector_key, i);
1528 let target: u64 = sload_field(input, constraint_field_slot(elem_key, CONSTRAINT_TARGET))?
1529 .try_into()
1530 .unwrap_or(0);
1531 let backlog: u64 = sload_field(input, constraint_field_slot(elem_key, CONSTRAINT_BACKLOG))?
1532 .try_into()
1533 .unwrap_or(0);
1534
1535 if backlog == 0 {
1536 continue;
1537 }
1538
1539 let window: u64 = sload_field(input, constraint_field_slot(elem_key, CONSTRAINT_WINDOW))?
1540 .try_into()
1541 .unwrap_or(0);
1542 let max_weight: u64 = sload_field(input, constraint_field_slot(elem_key, MGC_MAX_WEIGHT))?
1543 .try_into()
1544 .unwrap_or(0);
1545
1546 if max_weight == 0 || target == 0 || window == 0 {
1547 continue;
1548 }
1549
1550 let divisor = (window as u128)
1552 .saturating_mul(target as u128)
1553 .saturating_mul(max_weight as u128);
1554
1555 for (r, exponent) in exponents
1556 .iter_mut()
1557 .enumerate()
1558 .take(NUM_RESOURCE_KINDS as usize)
1559 {
1560 let weight: u64 = sload_field(
1561 input,
1562 constraint_field_slot(elem_key, MGC_WEIGHTS_BASE + r as u64),
1563 )?
1564 .try_into()
1565 .unwrap_or(0);
1566
1567 if weight == 0 {
1568 continue;
1569 }
1570
1571 let dividend = (backlog as u128)
1572 .saturating_mul(weight as u128)
1573 .saturating_mul(10_000);
1574 let exp = if divisor > 0 {
1575 (dividend / divisor) as u64
1576 } else {
1577 0
1578 };
1579 *exponent = exponent.saturating_add(exp);
1580 }
1581 }
1582
1583 for &exp in &exponents {
1584 if exp > MAX_PRICING_EXPONENT_BIPS {
1585 return Err(PrecompileError::other("pricing exponent exceeds maximum"));
1586 }
1587 }
1588
1589 Ok(())
1590}
1591
1592fn handle_set_chain_config(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1598 let data = input.data;
1599 if data.len() < 68 {
1600 return crate::burn_all_revert(input.gas);
1601 }
1602 let gas_limit = input.gas;
1603
1604 let bytes_len: usize = U256::from_be_slice(&data[36..68])
1606 .try_into()
1607 .map_err(|_| PrecompileError::other("bytes length overflow"))?;
1608
1609 if data.len() < 68 + bytes_len {
1610 return crate::burn_all_revert(gas_limit);
1611 }
1612 let config_bytes = &data[68..68 + bytes_len];
1613
1614 let cc_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_CONFIG_SUBSPACE);
1616
1617 let old_len: u64 = sload_field(input, map_slot(cc_key.as_slice(), 0))?
1619 .try_into()
1620 .unwrap_or(0);
1621 let old_slots = old_len.div_ceil(32);
1622 for s in 1..=old_slots {
1623 sstore_field(input, map_slot(cc_key.as_slice(), s), U256::ZERO)?;
1624 }
1625
1626 sstore_field(
1628 input,
1629 map_slot(cc_key.as_slice(), 0),
1630 U256::from(bytes_len as u64),
1631 )?;
1632
1633 let mut remaining = config_bytes;
1635 let mut offset = 1u64;
1636 while remaining.len() >= 32 {
1637 let mut slot = [0u8; 32];
1638 slot.copy_from_slice(&remaining[..32]);
1639 sstore_field(
1640 input,
1641 map_slot(cc_key.as_slice(), offset),
1642 U256::from_be_bytes(slot),
1643 )?;
1644 remaining = &remaining[32..];
1645 offset += 1;
1646 }
1647 if !remaining.is_empty() {
1648 let mut slot = [0u8; 32];
1649 slot[..remaining.len()].copy_from_slice(remaining);
1650 sstore_field(
1651 input,
1652 map_slot(cc_key.as_slice(), offset),
1653 U256::from_be_bytes(slot),
1654 )?;
1655 }
1656
1657 let new_slots = (bytes_len as u64).div_ceil(32);
1658 let total_stores = old_slots + 1 + new_slots; let gas_used = total_stores * SSTORE_GAS + SLOAD_GAS + COPY_GAS;
1660 Ok(PrecompileOutput::new(
1661 gas_used.min(gas_limit),
1662 Vec::new().into(),
1663 ))
1664}
1665
1666fn handle_set_calldata_price_increase(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1669 let data = input.data;
1670 if data.len() < 36 {
1671 return crate::burn_all_revert(input.gas);
1672 }
1673 let gas_limit = input.gas;
1674 let enabled = U256::from_be_slice(&data[4..36]) != U256::ZERO;
1675
1676 load_arbos(input)?;
1677
1678 let features_key = derive_subspace_key(ROOT_STORAGE_KEY, FEATURES_SUBSPACE);
1679 let features_slot = map_slot(features_key.as_slice(), 0);
1680 let current = sload_field(input, features_slot)?;
1681
1682 let updated = if enabled {
1683 current | U256::from(1)
1684 } else {
1685 current & !(U256::from(1))
1686 };
1687 sstore_field(input, features_slot, updated)?;
1688
1689 let gas_used = SLOAD_GAS + SSTORE_GAS + COPY_GAS;
1690 Ok(PrecompileOutput::new(
1691 gas_used.min(gas_limit),
1692 Vec::new().into(),
1693 ))
1694}
1695
1696fn handle_set_collect_tips(input: &mut PrecompileInput<'_>) -> PrecompileResult {
1697 let data = input.data;
1698 if data.len() < 36 {
1699 return crate::burn_all_revert(input.gas);
1700 }
1701 let gas_limit = input.gas;
1702 let value = if U256::from_be_slice(&data[4..36]) != U256::ZERO {
1703 U256::from(1u64)
1704 } else {
1705 U256::ZERO
1706 };
1707 sstore_field(input, root_slot(COLLECT_TIPS_OFFSET), value)?;
1708 let gas_used = SLOAD_GAS + SSTORE_GAS + COPY_GAS;
1710 Ok(PrecompileOutput::new(
1711 gas_used.min(gas_limit),
1712 Vec::new().into(),
1713 ))
1714}