1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, B256, U256};
3use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
4
5use crate::storage_slot::{
6 derive_subspace_key, map_slot_b256, ARBOS_STATE_ADDRESS, FILTERED_TX_STATE_ADDRESS,
7 ROOT_STORAGE_KEY, TRANSACTION_FILTERER_SUBSPACE,
8};
9
10pub const ARBFILTEREDTXMANAGER_ADDRESS: Address = Address::new([
12 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
13 0x00, 0x00, 0x00, 0x74,
14]);
15
16const ADD_FILTERED_TX: [u8; 4] = [0xbf, 0xc1, 0xd5, 0x0e];
18const DELETE_FILTERED_TX: [u8; 4] = [0x0b, 0x23, 0x48, 0x5a];
19const IS_TX_FILTERED: [u8; 4] = [0x37, 0x94, 0x6f, 0x6a];
20
21const SLOAD_GAS: u64 = 800;
22const SSTORE_GAS: u64 = 20_000;
23const COPY_GAS: u64 = 3;
24
25const PRESENT_VALUE: U256 = U256::from_limbs([1, 0, 0, 0]);
27
28pub fn create_arbfilteredtxmanager_precompile() -> DynPrecompile {
29 DynPrecompile::new_stateful(PrecompileId::custom("arbfilteredtxmanager"), handler)
30}
31
32fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
33 if let Some(result) = crate::check_precompile_version(
35 arb_chainspec::arbos_version::ARBOS_VERSION_TRANSACTION_FILTERING,
36 ) {
37 return result;
38 }
39
40 let data = input.data;
41 if data.len() < 4 {
42 return Err(PrecompileError::other("input too short"));
43 }
44
45 let selector: [u8; 4] = [data[0], data[1], data[2], data[3]];
46
47 let result = match selector {
48 ADD_FILTERED_TX => handle_add_filtered_tx(&mut input),
49 DELETE_FILTERED_TX => handle_delete_filtered_tx(&mut input),
50 IS_TX_FILTERED => handle_is_tx_filtered(&mut input),
51 _ => Err(PrecompileError::other("unknown selector")),
52 };
53 crate::gas_check(input.gas, 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<'_>) -> PrecompileResult {
121 let data = input.data;
122 if data.len() < 36 {
123 return Err(PrecompileError::other("input too short"));
124 }
125
126 let gas_limit = input.gas;
127 let tx_hash = B256::from_slice(&data[4..36]);
128 load_accounts(input)?;
129
130 let slot = filtered_tx_slot(&tx_hash);
131 let value = sload_filtered(input, slot)?;
132 let is_filtered = if value == PRESENT_VALUE {
133 U256::from(1u64)
134 } else {
135 U256::ZERO
136 };
137
138 Ok(PrecompileOutput::new(
139 (SLOAD_GAS + COPY_GAS).min(gas_limit),
140 is_filtered.to_be_bytes::<32>().to_vec().into(),
141 ))
142}
143
144fn handle_add_filtered_tx(input: &mut PrecompileInput<'_>) -> PrecompileResult {
146 let data = input.data;
147 if data.len() < 36 {
148 return Err(PrecompileError::other("input too short"));
149 }
150
151 let gas_limit = input.gas;
152 let tx_hash = B256::from_slice(&data[4..36]);
153 let caller = input.caller;
154 load_accounts(input)?;
155
156 if !is_transaction_filterer(input, caller)? {
157 return Err(PrecompileError::other(
158 "caller is not a transaction filterer",
159 ));
160 }
161
162 let slot = filtered_tx_slot(&tx_hash);
163 sstore_filtered(input, slot, PRESENT_VALUE)?;
164
165 let gas_used = 2 * SLOAD_GAS + SSTORE_GAS + COPY_GAS;
166 Ok(PrecompileOutput::new(
167 gas_used.min(gas_limit),
168 vec![].into(),
169 ))
170}
171
172fn handle_delete_filtered_tx(input: &mut PrecompileInput<'_>) -> PrecompileResult {
174 let data = input.data;
175 if data.len() < 36 {
176 return Err(PrecompileError::other("input too short"));
177 }
178
179 let gas_limit = input.gas;
180 let tx_hash = B256::from_slice(&data[4..36]);
181 let caller = input.caller;
182 load_accounts(input)?;
183
184 if !is_transaction_filterer(input, caller)? {
185 return Err(PrecompileError::other(
186 "caller is not a transaction filterer",
187 ));
188 }
189
190 let slot = filtered_tx_slot(&tx_hash);
191 sstore_filtered(input, slot, U256::ZERO)?;
192
193 let gas_used = 2 * SLOAD_GAS + SSTORE_GAS + COPY_GAS;
194 Ok(PrecompileOutput::new(
195 gas_used.min(gas_limit),
196 vec![].into(),
197 ))
198}