arb_precompiles/
arbdebug.rs

1use std::sync::Arc;
2
3use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
4use alloy_primitives::{Address, B256, Bytes, U256};
5use alloy_sol_types::{SolError, SolEvent, SolInterface};
6use arb_context::ArbPrecompileCtx;
7use arb_storage::ARBOS_STATE_ADDRESS;
8use revm::{
9    precompile::{PrecompileId, PrecompileOutput, PrecompileResult},
10    primitives::Log,
11};
12
13use crate::{ArbPrecompileError, interfaces::IArbDebug};
14
15/// ArbDebug precompile address (0xff).
16pub const ARBDEBUG_ADDRESS: Address = Address::new([
17    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
18    0x00, 0x00, 0x00, 0xff,
19]);
20
21const SLOAD_GAS: u64 = 800;
22const SSTORE_GAS: u64 = 20_000;
23const COPY_GAS: u64 = 3;
24const LOG_GAS: u64 = 375;
25const LOG_TOPIC_GAS: u64 = 375;
26const LOG_DATA_GAS: u64 = 8;
27
28pub fn create_arbdebug_precompile(ctx: Arc<ArbPrecompileCtx>) -> DynPrecompile {
29    DynPrecompile::new_stateful(PrecompileId::custom("arbdebug"), move |input| {
30        handler(input, &ctx)
31    })
32}
33
34fn handler(mut input: PrecompileInput<'_>, ctx: &ArbPrecompileCtx) -> PrecompileResult {
35    let mut gas_used = 0u64;
36    let gas_limit = input.gas;
37    if !ctx.block.allow_debug_precompiles {
38        return crate::burn_all_revert(gas_limit);
39    }
40    crate::init_precompile_gas(&mut gas_used, ctx, input.data.len());
41
42    let call = match IArbDebug::ArbDebugCalls::abi_decode(input.data) {
43        Ok(c) => c,
44        Err(_) => return crate::burn_all_revert(gas_limit),
45    };
46
47    use IArbDebug::ArbDebugCalls;
48    let input_len = input.data.len();
49    let result = match call {
50        ArbDebugCalls::becomeChainOwner(_) => {
51            handle_become_chain_owner(&mut input, &mut gas_used, ctx)
52        }
53        ArbDebugCalls::events(c) => handle_events(&mut input, &mut gas_used, ctx, c.flag, c.value),
54        ArbDebugCalls::eventsView(_) => handle_events_view(&mut input, &mut gas_used, ctx),
55        ArbDebugCalls::customRevert(c) => {
56            gas_used = 0;
57            crate::init_precompile_gas_pure(&mut gas_used, ctx, input_len);
58            handle_custom_revert(&mut gas_used, ctx, c.number, gas_limit)
59        }
60        ArbDebugCalls::legacyError(_) => {
61            gas_used = 0;
62            crate::init_precompile_gas_pure(&mut gas_used, ctx, input_len);
63            Err(ArbPrecompileError::empty_revert(gas_used).into())
64        }
65        ArbDebugCalls::panic(_) => {
66            if let Some(r) = crate::check_method_version(
67                ctx,
68                gas_limit,
69                arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS,
70                0,
71            ) {
72                return r;
73            }
74            panic!("called ArbDebug's debug-only Panic method")
75        }
76        ArbDebugCalls::overwriteContractCode(c) => {
77            handle_overwrite_contract_code(&mut input, &mut gas_used, ctx, c.target, c.newCode)
78        }
79    };
80
81    crate::gas_check(ctx, gas_limit, gas_used, result)
82}
83
84fn handle_become_chain_owner(
85    input: &mut PrecompileInput<'_>,
86    gas_used: &mut u64,
87    ctx: &ArbPrecompileCtx,
88) -> PrecompileResult {
89    let caller = input.caller;
90    let gas_limit = input.gas;
91
92    input
93        .internals_mut()
94        .load_account(ARBOS_STATE_ADDRESS)
95        .map_err(ArbPrecompileError::fatal)?;
96
97    let internals = input.internals_mut();
98    let arb_state = ctx
99        .block
100        .arbos_state(internals)
101        .map_err(ArbPrecompileError::fatal)?;
102
103    let was_member = arb_state
104        .chain_owners
105        .is_member(internals, caller)
106        .map_err(ArbPrecompileError::fatal)?;
107    crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
108
109    if !was_member {
110        arb_state
111            .chain_owners
112            .add(internals, caller)
113            .map_err(ArbPrecompileError::fatal)?;
114        crate::charge_storage_read(gas_used, ctx, 2 * SLOAD_GAS);
115        crate::charge_storage_write(gas_used, ctx, 3 * SSTORE_GAS);
116    }
117
118    Ok(PrecompileOutput::new(
119        (*gas_used).min(gas_limit),
120        Vec::new().into(),
121    ))
122}
123
124fn handle_events(
125    input: &mut PrecompileInput<'_>,
126    gas_used: &mut u64,
127    ctx: &ArbPrecompileCtx,
128    flag: bool,
129    value: B256,
130) -> PrecompileResult {
131    let gas_limit = input.gas;
132    let caller = input.caller;
133    let value_received = input.value;
134
135    input
136        .internals_mut()
137        .load_account(ARBOS_STATE_ADDRESS)
138        .map_err(ArbPrecompileError::fatal)?;
139
140    emit_basic_event(input, !flag, value);
141    emit_mixed_event(input, flag, !flag, value, ARBDEBUG_ADDRESS, caller);
142
143    let mut out = Vec::with_capacity(64);
144    out.extend_from_slice(B256::left_padding_from(caller.as_slice()).as_slice());
145    out.extend_from_slice(&value_received.to_be_bytes::<32>());
146
147    let result_words = (out.len() as u64).div_ceil(32);
148    let basic_log_gas = LOG_GAS + LOG_TOPIC_GAS * 2 + LOG_DATA_GAS * 32;
149    let mixed_log_gas = LOG_GAS + LOG_TOPIC_GAS * 4 + LOG_DATA_GAS * 64;
150    crate::charge_history_growth(gas_used, ctx, basic_log_gas + mixed_log_gas);
151    crate::charge_computation(gas_used, ctx, COPY_GAS * result_words);
152    Ok(PrecompileOutput::new(
153        (*gas_used).min(gas_limit),
154        out.into(),
155    ))
156}
157
158fn handle_events_view(
159    input: &mut PrecompileInput<'_>,
160    gas_used: &mut u64,
161    ctx: &ArbPrecompileCtx,
162) -> PrecompileResult {
163    // v < 11: view-method log writes are permitted; emit and succeed.
164    // v >= 11: framework rejects with ErrWriteProtection.
165    if ctx.block.arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_11 {
166        return Err(ArbPrecompileError::empty_revert(*gas_used).into());
167    }
168
169    let gas_limit = input.gas;
170    let caller = input.caller;
171
172    input
173        .internals_mut()
174        .load_account(ARBOS_STATE_ADDRESS)
175        .map_err(ArbPrecompileError::fatal)?;
176
177    let value = B256::ZERO;
178    let flag = true;
179    emit_basic_event(input, !flag, value);
180    emit_mixed_event(input, flag, !flag, value, ARBDEBUG_ADDRESS, caller);
181
182    let basic_log_gas = LOG_GAS + LOG_TOPIC_GAS * 2 + LOG_DATA_GAS * 32;
183    let mixed_log_gas = LOG_GAS + LOG_TOPIC_GAS * 4 + LOG_DATA_GAS * 64;
184    crate::charge_history_growth(gas_used, ctx, basic_log_gas + mixed_log_gas);
185
186    Ok(PrecompileOutput::new(
187        (*gas_used).min(gas_limit),
188        Vec::new().into(),
189    ))
190}
191
192fn handle_custom_revert(
193    gas_used: &mut u64,
194    ctx: &ArbPrecompileCtx,
195    number: u64,
196    gas_limit: u64,
197) -> PrecompileResult {
198    let payload = IArbDebug::Custom {
199        _0: number,
200        _1: "This spider family wards off bugs: /\\oo/\\ //\\(oo)//\\ /\\oo/\\".to_string(),
201        _2: true,
202    }
203    .abi_encode();
204    crate::sol_error_revert(gas_used, ctx, payload, gas_limit)
205}
206
207fn emit_basic_event(input: &mut PrecompileInput<'_>, flag: bool, value: B256) {
208    let topic0 = IArbDebug::Basic::SIGNATURE_HASH;
209    let topic1 = value;
210    let mut data = [0u8; 32];
211    if flag {
212        data[31] = 1;
213    }
214    input.internals_mut().log(Log::new_unchecked(
215        ARBDEBUG_ADDRESS,
216        vec![topic0, topic1],
217        Bytes::copy_from_slice(&data),
218    ));
219}
220
221/// `ArbDebug.overwriteContractCode(address target, bytes newCode) -> bytes oldCode`.
222/// Replaces the target account's runtime code with `newCode` and returns the
223/// previous code, without any code-size or EIP-3541 checks (debug-only).
224fn handle_overwrite_contract_code(
225    input: &mut PrecompileInput<'_>,
226    gas_used: &mut u64,
227    ctx: &ArbPrecompileCtx,
228    target: Address,
229    new_code: Bytes,
230) -> PrecompileResult {
231    let gas_limit = input.gas;
232
233    let old_code: Vec<u8> = match input.internals_mut().load_account_code(target) {
234        Ok(state_load) => state_load
235            .data
236            .code()
237            .map(|bc| bc.original_byte_slice().to_vec())
238            .unwrap_or_default(),
239        Err(e) => return Err(ArbPrecompileError::fatal(e).into()),
240    };
241
242    let bytecode = revm::bytecode::Bytecode::new_raw(new_code.clone());
243    if let Err(e) = input.internals_mut().set_code(target, bytecode) {
244        return Err(ArbPrecompileError::fatal(e).into());
245    }
246
247    // ABI-encode `bytes memory oldCode`: offset(0x20) | length(N) | data padded.
248    let len = old_code.len();
249    let padded_len = len.div_ceil(32) * 32;
250    let mut out = Vec::with_capacity(64 + padded_len);
251    out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
252    out.extend_from_slice(&U256::from(len as u64).to_be_bytes::<32>());
253    out.extend_from_slice(&old_code);
254    out.resize(64 + padded_len, 0);
255
256    let result_words = (out.len() as u64).div_ceil(32);
257    crate::charge_computation(gas_used, ctx, COPY_GAS.saturating_mul(result_words));
258    Ok(PrecompileOutput::new(
259        (*gas_used).min(gas_limit),
260        out.into(),
261    ))
262}
263
264fn emit_mixed_event(
265    input: &mut PrecompileInput<'_>,
266    flag1: bool,
267    flag2: bool,
268    value: B256,
269    addr1: Address,
270    addr2: Address,
271) {
272    let topic0 = IArbDebug::Mixed::SIGNATURE_HASH;
273    let mut t1 = [0u8; 32];
274    if flag1 {
275        t1[31] = 1;
276    }
277    let topic1 = B256::from(t1);
278    let topic2 = value;
279    let topic3 = B256::left_padding_from(addr2.as_slice());
280    let mut data = Vec::with_capacity(64);
281    let mut flag2_word = [0u8; 32];
282    if flag2 {
283        flag2_word[31] = 1;
284    }
285    data.extend_from_slice(&flag2_word);
286    data.extend_from_slice(B256::left_padding_from(addr1.as_slice()).as_slice());
287    input.internals_mut().log(Log::new_unchecked(
288        ARBDEBUG_ADDRESS,
289        vec![topic0, topic1, topic2, topic3],
290        Bytes::copy_from_slice(&data),
291    ));
292}