arb_precompiles/
arbdebug.rs

1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, Bytes, 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::IArbDebug,
11    storage_slot::{
12        derive_subspace_key, map_slot, map_slot_b256, ARBOS_STATE_ADDRESS, CHAIN_OWNER_SUBSPACE,
13        ROOT_STORAGE_KEY,
14    },
15};
16
17/// ArbDebug precompile address (0xff).
18pub const ARBDEBUG_ADDRESS: Address = Address::new([
19    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
20    0x00, 0x00, 0x00, 0xff,
21]);
22
23const SLOAD_GAS: u64 = 800;
24const SSTORE_GAS: u64 = 20_000;
25const COPY_GAS: u64 = 3;
26const LOG_GAS: u64 = 375;
27const LOG_TOPIC_GAS: u64 = 375;
28const LOG_DATA_GAS: u64 = 8;
29
30pub fn create_arbdebug_precompile() -> DynPrecompile {
31    DynPrecompile::new_stateful(PrecompileId::custom("arbdebug"), handler)
32}
33
34fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
35    let gas_limit = input.gas;
36    if !crate::allow_debug_precompiles() {
37        return crate::burn_all_revert(gas_limit);
38    }
39    crate::init_precompile_gas(input.data.len());
40
41    let call = match IArbDebug::ArbDebugCalls::abi_decode(input.data) {
42        Ok(c) => c,
43        Err(_) => return crate::burn_all_revert(gas_limit),
44    };
45
46    use IArbDebug::ArbDebugCalls;
47    let result = match call {
48        ArbDebugCalls::becomeChainOwner(_) => handle_become_chain_owner(&mut input),
49        ArbDebugCalls::events(c) => handle_events(&mut input, c.flag, c.value),
50        ArbDebugCalls::eventsView(_) => handle_events_view(&mut input),
51        ArbDebugCalls::customRevert(c) => handle_custom_revert(c.number),
52        ArbDebugCalls::legacyError(_) => Err(PrecompileError::other("example legacy error")),
53        ArbDebugCalls::panic(_) => panic!("called ArbDebug's debug-only Panic method"),
54        ArbDebugCalls::overwriteContractCode(_) => Err(PrecompileError::other(
55            "overwriteContractCode not implemented",
56        )),
57    };
58
59    crate::gas_check(gas_limit, result)
60}
61
62fn handle_become_chain_owner(input: &mut PrecompileInput<'_>) -> PrecompileResult {
63    let caller = input.caller;
64    let gas_limit = input.gas;
65
66    input
67        .internals_mut()
68        .load_account(ARBOS_STATE_ADDRESS)
69        .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
70
71    let set_key = derive_subspace_key(ROOT_STORAGE_KEY, CHAIN_OWNER_SUBSPACE);
72    let by_address_key = derive_subspace_key(set_key.as_slice(), &[0]);
73    let addr_hash = B256::left_padding_from(caller.as_slice());
74    let member_slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
75
76    let existing = sload(input, member_slot)?;
77    let gas_used = if existing == U256::ZERO {
78        let size_slot = map_slot(set_key.as_slice(), 0);
79        let size = sload(input, size_slot)?;
80        let new_size = u64::try_from(size).unwrap_or(0) + 1;
81
82        let new_pos_slot = map_slot(set_key.as_slice(), new_size);
83        sstore(input, new_pos_slot, U256::from_be_slice(caller.as_slice()))?;
84        sstore(input, member_slot, U256::from(new_size))?;
85        sstore(input, size_slot, U256::from(new_size))?;
86
87        4 * SLOAD_GAS + 3 * SSTORE_GAS
88    } else {
89        2 * SLOAD_GAS
90    };
91
92    Ok(PrecompileOutput::new(
93        gas_used.min(gas_limit),
94        Vec::new().into(),
95    ))
96}
97
98fn handle_events(input: &mut PrecompileInput<'_>, flag: bool, value: B256) -> PrecompileResult {
99    let gas_limit = input.gas;
100    let data_len = input.data.len();
101    let caller = input.caller;
102    let value_received = input.value;
103
104    input
105        .internals_mut()
106        .load_account(ARBOS_STATE_ADDRESS)
107        .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
108
109    emit_basic_event(input, !flag, value);
110    emit_mixed_event(input, flag, !flag, value, ARBDEBUG_ADDRESS, caller);
111
112    let mut out = Vec::with_capacity(64);
113    out.extend_from_slice(B256::left_padding_from(caller.as_slice()).as_slice());
114    out.extend_from_slice(&value_received.to_be_bytes::<32>());
115
116    let arg_words = (data_len as u64).saturating_sub(4).div_ceil(32);
117    let result_words = (out.len() as u64).div_ceil(32);
118    let basic_log_gas = LOG_GAS + LOG_TOPIC_GAS * 2 + LOG_DATA_GAS * 32;
119    let mixed_log_gas = LOG_GAS + LOG_TOPIC_GAS * 4 + LOG_DATA_GAS * 64;
120    let gas_cost =
121        SLOAD_GAS + COPY_GAS * arg_words + basic_log_gas + mixed_log_gas + COPY_GAS * result_words;
122
123    Ok(PrecompileOutput::new(gas_cost.min(gas_limit), out.into()))
124}
125
126fn handle_events_view(input: &mut PrecompileInput<'_>) -> PrecompileResult {
127    if input.is_static {
128        return Err(PrecompileError::other(
129            "cannot emit logs in static call context",
130        ));
131    }
132    let zero_value = B256::ZERO;
133    emit_basic_event(input, false, zero_value);
134    emit_mixed_event(
135        input,
136        true,
137        false,
138        zero_value,
139        ARBDEBUG_ADDRESS,
140        input.caller,
141    );
142    Ok(PrecompileOutput::new(
143        input.gas.min(3000),
144        Vec::new().into(),
145    ))
146}
147
148fn handle_custom_revert(number: u64) -> PrecompileResult {
149    Err(PrecompileError::other(format!(
150        "custom error {number}: This spider family wards off bugs: /\\oo/\\ //\\(oo)//\\ /\\oo/\\"
151    )))
152}
153
154fn sload(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
155    let v = input
156        .internals_mut()
157        .sload(ARBOS_STATE_ADDRESS, slot)
158        .map_err(|_| PrecompileError::other("sload failed"))?;
159    crate::charge_precompile_gas(SLOAD_GAS);
160    Ok(v.data)
161}
162
163fn sstore(input: &mut PrecompileInput<'_>, slot: U256, value: U256) -> Result<(), PrecompileError> {
164    input
165        .internals_mut()
166        .sstore(ARBOS_STATE_ADDRESS, slot, value)
167        .map_err(|_| PrecompileError::other("sstore failed"))?;
168    crate::charge_precompile_gas(SSTORE_GAS);
169    Ok(())
170}
171
172fn emit_basic_event(input: &mut PrecompileInput<'_>, flag: bool, value: B256) {
173    let topic0 = IArbDebug::Basic::SIGNATURE_HASH;
174    let topic1 = value;
175    let mut data = [0u8; 32];
176    if flag {
177        data[31] = 1;
178    }
179    input.internals_mut().log(Log::new_unchecked(
180        ARBDEBUG_ADDRESS,
181        vec![topic0, topic1],
182        Bytes::copy_from_slice(&data),
183    ));
184}
185
186fn emit_mixed_event(
187    input: &mut PrecompileInput<'_>,
188    flag1: bool,
189    flag2: bool,
190    value: B256,
191    addr1: Address,
192    addr2: Address,
193) {
194    let topic0 = IArbDebug::Mixed::SIGNATURE_HASH;
195    let mut t1 = [0u8; 32];
196    if flag1 {
197        t1[31] = 1;
198    }
199    let topic1 = B256::from(t1);
200    let topic2 = value;
201    let topic3 = B256::left_padding_from(addr2.as_slice());
202    let mut data = Vec::with_capacity(64);
203    let mut flag2_word = [0u8; 32];
204    if flag2 {
205        flag2_word[31] = 1;
206    }
207    data.extend_from_slice(&flag2_word);
208    data.extend_from_slice(B256::left_padding_from(addr1.as_slice()).as_slice());
209    input.internals_mut().log(Log::new_unchecked(
210        ARBDEBUG_ADDRESS,
211        vec![topic0, topic1, topic2, topic3],
212        Bytes::copy_from_slice(&data),
213    ));
214}