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
17pub 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}