1use alloy_primitives::{Bytes, U256 as RU256};
2use foundry_cheatcodes::Vm::{AccountAccessKind, StorageAccess};
3use polkadot_sdk::{
4 pallet_revive::{self, AccountInfo, Code, tracing::Tracing},
5 sp_core::{H160, U256},
6};
7use revive_env::Runtime;
8
9#[derive(Debug, Default)]
10pub(crate) struct StorageTracer {
11 is_create: Option<Code>,
13
14 records: Vec<AccountAccess>,
15 pending: Vec<AccountAccess>,
16 records_inner: Vec<AccountAccess>,
17 index: usize,
18 calls: Vec<H160>,
19}
20
21#[derive(Debug, Clone)]
23pub struct AccountAccess {
24 pub depth: u64,
26 pub kind: AccountAccessKind,
28 pub account: H160,
30 pub accessor: H160,
32 pub data: Bytes,
34 pub value: U256,
36 pub old_balance: U256,
38 pub new_balance: U256,
40 pub storage_accesses: Vec<StorageAccess>,
42 pub reverted: bool,
44 pub index: usize,
45 pub initialized: bool,
46}
47
48impl StorageTracer {
49 pub fn get_records(&self) -> Vec<AccountAccess> {
50 assert!(
51 self.pending.is_empty(),
52 "pending call stack is not empty; found calls without matching returns: {:?}",
53 self.pending
54 );
55 assert!(
56 self.records_inner.is_empty(),
57 "inner stack is not empty; found calls without matching returns: {:?}",
58 self.records_inner
59 );
60 let mut accounts = self.records.clone();
61 accounts.sort_by_key(|f| f.index);
62 accounts
63 }
64
65 fn current_addr(&self) -> H160 {
66 self.calls.last().copied().unwrap_or_default()
67 }
68}
69
70impl Tracing for StorageTracer {
71 fn instantiate_code(&mut self, code: &Code, _salt: Option<&[u8; 32]>) {
72 self.is_create = Some(code.clone());
73 }
74
75 fn enter_child_span(
76 &mut self,
77 from: H160,
78 to: H160,
79 is_delegate_call: Option<H160>,
80 is_read_only: bool,
81 value: U256,
82 input: &[u8],
83 _gas: U256,
84 ) {
85 let code = self.is_create.take();
86
87 if is_delegate_call.is_some() {
88 self.calls.push(self.current_addr());
89 } else {
90 self.calls.push(to);
91 }
92
93 let kind = if code.is_some() {
94 AccountAccessKind::Create
95 } else if is_read_only {
96 AccountAccessKind::StaticCall
97 } else if is_delegate_call.is_some() {
98 AccountAccessKind::DelegateCall
99 } else {
100 AccountAccessKind::Call
101 };
102
103 let last_depth = if !self.pending.is_empty() {
104 self.pending.last().map(|record| record.depth).expect("must have at least one record")
105 } else {
106 self.records.last().map(|record| record.depth).unwrap_or_default()
107 };
108 let new_depth = last_depth.checked_add(1).expect("overflow in recording call depth");
109
110 let mut record = AccountAccess {
111 depth: new_depth,
112 kind,
113 account: to,
114 accessor: from,
115 data: Bytes::from(input.to_vec()),
116 value,
117 reverted: false,
118 old_balance: pallet_revive::Pallet::<Runtime>::evm_balance(&to),
119 new_balance: U256::zero(),
120 storage_accesses: Default::default(),
121 index: self.index,
122 initialized: true,
123 };
124 if let Some(code) = code {
125 match code {
126 Code::Upload(items) => {
127 record.data = Bytes::from(items);
128 }
129 Code::Existing(_) => (),
130 }
131 }
132 self.pending.push(record);
133 self.index += 1;
134 }
135
136 fn terminate(
137 &mut self,
138 contract_address: H160,
139 beneficiary_address: H160,
140 _gas_left: U256,
141 value: U256,
142 ) {
143 let last_depth = if !self.pending.is_empty() {
144 self.pending.last().map(|record| record.depth).expect("must have at least one record")
145 } else {
146 self.records.last().map(|record| record.depth).unwrap_or_default()
147 };
148 let new_depth = last_depth.checked_add(1).expect("overflow in recording call depth");
149 let account = AccountInfo::<Runtime>::is_contract(&beneficiary_address);
150 let record = AccountAccess {
151 depth: new_depth,
152 kind: AccountAccessKind::SelfDestruct,
153 account: beneficiary_address,
154 accessor: contract_address,
155 data: Bytes::new(),
156 value,
157 reverted: false,
158 old_balance: pallet_revive::Pallet::<Runtime>::evm_balance(&beneficiary_address),
159 new_balance: U256::zero(),
160 storage_accesses: Default::default(),
161 index: self.index,
162 initialized: account,
163 };
164 self.index += 1;
165
166 self.records_inner.push(record);
167 }
168
169 fn exit_child_span_with_error(
170 &mut self,
171 _error: polkadot_sdk::sp_runtime::DispatchError,
172 _gas_left: U256,
173 ) {
174 self.calls.pop();
175
176 let is_create = self.is_create.take();
177 let mut record = self.pending.pop().expect("unexpected return while recording call");
178 record.new_balance = pallet_revive::Pallet::<Runtime>::evm_balance(&self.current_addr());
179 record.reverted = true;
180 record.storage_accesses.iter_mut().for_each(|x| x.reverted = true);
181 self.records_inner.iter_mut().for_each(|x| {
182 if record.reverted {
183 x.reverted = true;
184 x.storage_accesses.iter_mut().for_each(|x| x.reverted = true);
185 }
186 });
187
188 if let Some(code) = is_create {
189 record.kind = AccountAccessKind::Create;
190 match code {
191 Code::Upload(items) => {
192 record.data = Bytes::from(items);
193 }
194 Code::Existing(_) => (),
195 }
196 }
197
198 if self.pending.is_empty() {
199 self.records.push(record);
201 if !self.records_inner.is_empty() {
203 self.records.extend(std::mem::take(&mut self.records_inner));
204 }
205 } else {
206 self.records_inner.push(record);
208 }
209 }
210
211 fn exit_child_span(
212 &mut self,
213 output: &polkadot_sdk::pallet_revive::ExecReturnValue,
214 _gas_left: U256,
215 ) {
216 self.calls.pop();
217
218 let is_create = self.is_create.take();
219
220 let mut record = self.pending.pop().expect("unexpected return while recording call");
221 record.new_balance = pallet_revive::Pallet::<Runtime>::evm_balance(&self.current_addr());
222 if output.did_revert() {
223 record.reverted = true;
224 record.storage_accesses.iter_mut().for_each(|x| x.reverted = true);
225 self.records_inner.iter_mut().for_each(|x| {
226 if record.reverted {
227 x.reverted = true;
228 x.storage_accesses.iter_mut().for_each(|x| x.reverted = true);
229 }
230 });
231 }
232
233 if let Some(code) = is_create {
234 record.kind = AccountAccessKind::Create;
235 match code {
236 Code::Upload(items) => {
237 record.data = Bytes::from(items);
238 }
239 Code::Existing(_) => (),
240 }
241 }
242
243 if self.pending.is_empty() {
244 self.records.push(record);
246
247 if !self.records_inner.is_empty() {
249 self.records.extend(std::mem::take(&mut self.records_inner));
250 }
251 } else {
252 self.records_inner.push(record);
254 }
255 }
256
257 fn storage_read(&mut self, key: &polkadot_sdk::pallet_revive::Key, value: Option<&[u8]>) {
258 let account = self.current_addr().0.into();
259 let record = self.pending.last_mut().expect("expected at least one record");
260 record.storage_accesses.push(StorageAccess {
261 account,
262 slot: RU256::from_be_slice(key.unhashed()).into(),
263 isWrite: false,
264 previousValue: RU256::from_be_slice(value.unwrap_or_default()).into(),
265 newValue: RU256::from_be_slice(value.unwrap_or_default()).into(),
266 reverted: false,
267 });
268 }
269 fn storage_write(
270 &mut self,
271 key: &polkadot_sdk::pallet_revive::Key,
272 old_value: Option<Vec<u8>>,
273 new_value: Option<&[u8]>,
274 ) {
275 let account = self.current_addr().0.into();
276 let record = self.pending.last_mut().expect("expected at least one record");
277 record.storage_accesses.push(StorageAccess {
278 account,
279 slot: RU256::from_be_slice(key.unhashed()).into(),
280 isWrite: true,
281 previousValue: RU256::from_be_slice(old_value.unwrap_or_default().as_slice()).into(),
282 newValue: RU256::from_be_slice(new_value.unwrap_or_default()).into(),
283 reverted: false,
284 });
285 }
286}