1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, Log, B256, U256};
3use alloy_sol_types::{SolEvent, SolInterface};
4use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
5
6use crate::{
7 interfaces::IArbFilteredTxManager,
8 storage_slot::{
9 derive_subspace_key, map_slot_b256, ARBOS_STATE_ADDRESS, FILTERED_TX_STATE_ADDRESS,
10 ROOT_STORAGE_KEY, TRANSACTION_FILTERER_SUBSPACE,
11 },
12};
13
14pub const ARBFILTEREDTXMANAGER_ADDRESS: Address = Address::new([
16 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
17 0x00, 0x00, 0x00, 0x74,
18]);
19
20const SLOAD_GAS: u64 = 800;
21const SSTORE_GAS: u64 = 20_000;
22const COPY_GAS: u64 = 3;
23
24const PRESENT_VALUE: U256 = U256::from_limbs([1, 0, 0, 0]);
26
27pub fn create_arbfilteredtxmanager_precompile() -> DynPrecompile {
28 DynPrecompile::new_stateful(PrecompileId::custom("arbfilteredtxmanager"), handler)
29}
30
31fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
32 if let Some(result) = crate::check_precompile_version(
33 arb_chainspec::arbos_version::ARBOS_VERSION_TRANSACTION_FILTERING,
34 ) {
35 return result;
36 }
37
38 let gas_limit = input.gas;
39 crate::init_precompile_gas(input.data.len());
40
41 let call =
42 match IArbFilteredTxManager::ArbFilteredTransactionsManagerCalls::abi_decode(input.data) {
43 Ok(c) => c,
44 Err(_) => return crate::burn_all_revert(gas_limit),
45 };
46
47 use IArbFilteredTxManager::ArbFilteredTransactionsManagerCalls as Calls;
48 let result = match call {
49 Calls::addFilteredTransaction(c) => handle_add_filtered_tx(&mut input, c.txHash),
50 Calls::deleteFilteredTransaction(c) => handle_delete_filtered_tx(&mut input, c.txHash),
51 Calls::isTransactionFiltered(c) => handle_is_tx_filtered(&mut input, c.txHash),
52 };
53 crate::gas_check(gas_limit, result)
54}
55
56fn load_accounts(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
59 input
60 .internals_mut()
61 .load_account(ARBOS_STATE_ADDRESS)
62 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
63 input
64 .internals_mut()
65 .load_account(FILTERED_TX_STATE_ADDRESS)
66 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
67 Ok(())
68}
69
70fn sload_arbos(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
71 let val = input
72 .internals_mut()
73 .sload(ARBOS_STATE_ADDRESS, slot)
74 .map_err(|_| PrecompileError::other("sload failed"))?;
75 Ok(val.data)
76}
77
78fn sload_filtered(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
79 let val = input
80 .internals_mut()
81 .sload(FILTERED_TX_STATE_ADDRESS, slot)
82 .map_err(|_| PrecompileError::other("sload failed"))?;
83 Ok(val.data)
84}
85
86fn sstore_filtered(
87 input: &mut PrecompileInput<'_>,
88 slot: U256,
89 value: U256,
90) -> Result<(), PrecompileError> {
91 input
92 .internals_mut()
93 .sstore(FILTERED_TX_STATE_ADDRESS, slot, value)
94 .map_err(|_| PrecompileError::other("sstore failed"))?;
95 Ok(())
96}
97
98fn filtered_tx_slot(tx_hash: &B256) -> U256 {
101 map_slot_b256(&[], tx_hash)
102}
103
104fn is_transaction_filterer(
106 input: &mut PrecompileInput<'_>,
107 addr: Address,
108) -> Result<bool, PrecompileError> {
109 let filterer_key = derive_subspace_key(ROOT_STORAGE_KEY, TRANSACTION_FILTERER_SUBSPACE);
112 let by_address_key = derive_subspace_key(filterer_key.as_slice(), &[0]);
113 let addr_hash = B256::left_padding_from(addr.as_slice());
114 let slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
115 let val = sload_arbos(input, slot)?;
116 Ok(val != U256::ZERO)
117}
118
119fn handle_is_tx_filtered(input: &mut PrecompileInput<'_>, tx_hash: B256) -> PrecompileResult {
120 let gas_limit = input.gas;
121 load_accounts(input)?;
122
123 let slot = filtered_tx_slot(&tx_hash);
124 let value = sload_filtered(input, slot)?;
125 let is_filtered = if value == PRESENT_VALUE {
126 U256::from(1u64)
127 } else {
128 U256::ZERO
129 };
130
131 Ok(PrecompileOutput::new(
132 (SLOAD_GAS + COPY_GAS).min(gas_limit),
133 is_filtered.to_be_bytes::<32>().to_vec().into(),
134 ))
135}
136
137fn handle_add_filtered_tx(input: &mut PrecompileInput<'_>, tx_hash: B256) -> PrecompileResult {
138 let gas_limit = input.gas;
139 let caller = input.caller;
140 load_accounts(input)?;
141
142 if !is_transaction_filterer(input, caller)? {
143 return Err(PrecompileError::other(
144 "caller is not a transaction filterer",
145 ));
146 }
147
148 let slot = filtered_tx_slot(&tx_hash);
149 sstore_filtered(input, slot, PRESENT_VALUE)?;
150
151 input.internals_mut().log(Log::new_unchecked(
152 ARBFILTEREDTXMANAGER_ADDRESS,
153 vec![
154 IArbFilteredTxManager::FilteredTransactionAdded::SIGNATURE_HASH,
155 tx_hash,
156 ],
157 Default::default(),
158 ));
159
160 let gas_used = 2 * SLOAD_GAS + SSTORE_GAS + COPY_GAS;
161 Ok(PrecompileOutput::new(
162 gas_used.min(gas_limit),
163 vec![].into(),
164 ))
165}
166
167fn handle_delete_filtered_tx(input: &mut PrecompileInput<'_>, tx_hash: B256) -> PrecompileResult {
168 let gas_limit = input.gas;
169 let caller = input.caller;
170 load_accounts(input)?;
171
172 if !is_transaction_filterer(input, caller)? {
173 return Err(PrecompileError::other(
174 "caller is not a transaction filterer",
175 ));
176 }
177
178 let slot = filtered_tx_slot(&tx_hash);
179 sstore_filtered(input, slot, U256::ZERO)?;
180
181 input.internals_mut().log(Log::new_unchecked(
182 ARBFILTEREDTXMANAGER_ADDRESS,
183 vec![
184 IArbFilteredTxManager::FilteredTransactionDeleted::SIGNATURE_HASH,
185 tx_hash,
186 ],
187 Default::default(),
188 ));
189
190 let gas_used = 2 * SLOAD_GAS + SSTORE_GAS + COPY_GAS;
191 Ok(PrecompileOutput::new(
192 gas_used.min(gas_limit),
193 vec![].into(),
194 ))
195}