cfx_execute_helper/observer/
access_list.rs1use cfx_executor::observer::{
2 CallTracer, CheckpointTracer, DrainTrace, InternalTransferTracer,
3 OpcodeTracer, SetAuthTracer, StorageTracer,
4};
5use cfx_types::{u256_to_address_be, u256_to_h256_be, Address, H256};
6use cfx_vm_interpreter::instructions::Instruction;
7use cfx_vm_types::InterpreterInfo;
8use primitives::{AccessList, AccessListItem};
9use std::collections::{BTreeMap, BTreeSet, HashSet};
10use typemap::ShareDebugMap;
11
12#[derive(Debug, Default)]
17pub struct AccessListInspector {
18 excluded: HashSet<Address>,
20 touched_slots: BTreeMap<Address, BTreeSet<H256>>,
22}
23
24impl From<(AccessList, HashSet<Address>)> for AccessListInspector {
25 fn from(data: (AccessList, HashSet<Address>)) -> Self {
26 Self::new(data.0, data.1)
27 }
28}
29
30impl AccessListInspector {
31 pub fn new(access_list: AccessList, excluded: HashSet<Address>) -> Self {
33 let mut touched_slots = BTreeMap::<Address, BTreeSet<H256>>::new();
34 for item in access_list {
35 if excluded.contains(&item.address) {
36 continue;
37 }
38 touched_slots
39 .entry(item.address)
40 .or_default()
41 .extend(item.storage_keys);
42 }
43 Self {
44 excluded,
45 touched_slots,
46 }
47 }
48
49 pub fn excluded(&self) -> &HashSet<Address> { &self.excluded }
51
52 pub fn touched_slots(&self) -> &BTreeMap<Address, BTreeSet<H256>> {
55 &self.touched_slots
56 }
57
58 pub fn into_touched_slots(self) -> BTreeMap<Address, BTreeSet<H256>> {
61 self.touched_slots
62 }
63
64 pub fn into_access_list(self) -> AccessList {
68 let items = self.touched_slots.into_iter().map(|(address, slots)| {
69 AccessListItem {
70 address,
71 storage_keys: slots.into_iter().collect(),
72 }
73 });
74 items.collect()
75 }
76
77 pub fn access_list(&self) -> AccessList {
81 let items =
82 self.touched_slots
83 .iter()
84 .map(|(address, slots)| AccessListItem {
85 address: *address,
86 storage_keys: slots.iter().copied().collect(),
87 });
88 items.collect()
89 }
90
91 pub fn collect_excluded_addresses(&mut self, item: Address) {
92 self.excluded.insert(item);
93 }
94
95 fn record_address(&mut self, address: Address) {
99 if !self.excluded.contains(&address) {
100 self.touched_slots.entry(address).or_default();
101 }
102 }
103}
104
105impl DrainTrace for AccessListInspector {
106 fn drain_trace(self, map: &mut ShareDebugMap) {
107 map.insert::<AccessListKey>(self.into_access_list());
108 }
109}
110
111pub struct AccessListKey;
112
113impl typemap::Key for AccessListKey {
114 type Value = AccessList;
115}
116
117impl OpcodeTracer for AccessListInspector {
118 fn do_trace_opcode(&self, enabled: &mut bool) { *enabled |= true; }
119
120 fn step(&mut self, interp: &dyn InterpreterInfo) {
121 let Some(ins) = Instruction::from_u8(interp.current_opcode()) else {
122 return;
123 };
124 match ins {
125 Instruction::SLOAD | Instruction::SSTORE => {
126 if let Some(slot) = interp.stack().last() {
127 let cur_contract = interp.contract_address();
128 self.touched_slots
129 .entry(cur_contract)
130 .or_default()
131 .insert(u256_to_h256_be(*slot));
132 }
133 }
134 Instruction::EXTCODECOPY
135 | Instruction::EXTCODEHASH
136 | Instruction::EXTCODESIZE
137 | Instruction::BALANCE
138 | Instruction::SUICIDE => {
139 let operands = match ins {
140 Instruction::EXTCODECOPY => 4,
141 _ => 1,
142 };
143 let stack = interp.stack();
144 if stack.len() >= operands {
145 let address = stack[stack.len() - 1];
148 self.record_address(u256_to_address_be(address));
149 }
150 }
151 Instruction::DELEGATECALL
152 | Instruction::CALL
153 | Instruction::STATICCALL
154 | Instruction::CALLCODE => {
155 let operands = match ins {
160 Instruction::CALL | Instruction::CALLCODE => 7,
162 _ => 6,
164 };
165 let stack = interp.stack();
166 if stack.len() >= operands {
167 let address = stack[stack.len() - 2];
168 self.record_address(u256_to_address_be(address));
169 }
170 }
171 _ => (),
172 }
173 }
174}
175
176impl CallTracer for AccessListInspector {}
177impl CheckpointTracer for AccessListInspector {}
178impl InternalTransferTracer for AccessListInspector {}
179impl StorageTracer for AccessListInspector {}
180impl SetAuthTracer for AccessListInspector {}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn caller_access_list_merges_slots_for_duplicate_addresses() {
188 let address = Address::from_low_u64_be(1);
189 let lower_slot = H256::from_low_u64_be(7);
190 let higher_slot = H256::from_low_u64_be(8);
191 let inspector = AccessListInspector::new(
192 vec![
193 AccessListItem {
194 address,
195 storage_keys: vec![lower_slot],
196 },
197 AccessListItem {
198 address,
199 storage_keys: vec![higher_slot],
200 },
201 ],
202 HashSet::new(),
203 );
204
205 assert_eq!(
206 inspector.access_list(),
207 vec![AccessListItem {
208 address,
209 storage_keys: vec![lower_slot, higher_slot],
210 }]
211 );
212 }
213
214 #[test]
215 fn caller_access_list_omits_excluded_addresses() {
216 let excluded_address = Address::from_low_u64_be(1);
217 let included_address = Address::from_low_u64_be(2);
218 let excluded_slot = H256::from_low_u64_be(7);
219 let included_slot = H256::from_low_u64_be(8);
220 let inspector = AccessListInspector::new(
221 vec![
222 AccessListItem {
223 address: excluded_address,
224 storage_keys: vec![excluded_slot],
225 },
226 AccessListItem {
227 address: included_address,
228 storage_keys: vec![included_slot],
229 },
230 ],
231 HashSet::from([excluded_address]),
232 );
233
234 assert_eq!(
235 inspector.access_list(),
236 vec![AccessListItem {
237 address: included_address,
238 storage_keys: vec![included_slot],
239 }]
240 );
241 }
242}