1use crate::Nibbles;
4use alloc::vec::Vec;
5use alloy_primitives::{keccak256, map::B256Map, B256};
6use evm2::evm::AccountChanges;
7
8#[derive(Debug, Copy, Clone)]
11pub struct ProofV2Target {
12 pub key_nibbles: Nibbles,
14 pub parent: ProofV2TargetParent,
16}
17
18impl ProofV2Target {
19 pub fn new(key: B256) -> Self {
22 let key_nibbles = unsafe { Nibbles::unpack_unchecked(key.as_slice()) };
24 Self { key_nibbles, parent: ProofV2TargetParent::NONE }
25 }
26
27 pub fn key(&self) -> B256 {
29 B256::from_slice(&self.key_nibbles.pack())
30 }
31
32 pub const fn with_parent(mut self, parent: ProofV2TargetParent) -> Self {
34 self.parent = parent;
35 self
36 }
37}
38
39impl From<B256> for ProofV2Target {
40 fn from(key: B256) -> Self {
41 Self::new(key)
42 }
43}
44
45#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
56pub struct ProofV2TargetParent(Option<u8>);
57
58impl ProofV2TargetParent {
59 pub const NONE: Self = Self(None);
61
62 pub const fn new(path_len: usize) -> Self {
68 assert!(path_len < 64, "parent path length must be less than 64");
69 Self(Some(path_len as u8))
70 }
71
72 pub const fn is_known(self) -> bool {
74 self.0.is_some()
75 }
76
77 pub const fn path_len(self) -> Option<usize> {
79 match self.0 {
80 Some(path_len) => Some(path_len as usize),
81 None => None,
82 }
83 }
84
85 pub fn path(self, mut target_path: Nibbles) -> Option<Nibbles> {
87 target_path.truncate(self.path_len()?);
88 Some(target_path)
89 }
90}
91
92#[derive(Debug, Default)]
95pub struct MultiProofTargetsV2 {
96 pub account_targets: Vec<ProofV2Target>,
98 pub storage_targets: B256Map<Vec<ProofV2Target>>,
100}
101
102impl MultiProofTargetsV2 {
103 pub fn is_empty(&self) -> bool {
105 self.account_targets.is_empty() && self.storage_targets.is_empty()
106 }
107
108 pub fn chunking_length(&self) -> usize {
110 self.account_targets.len() +
111 self.storage_targets.values().map(|slots| slots.len()).sum::<usize>()
112 }
113
114 pub fn chunks(self, chunk_size: usize) -> impl Iterator<Item = Self> {
120 ChunkedMultiProofTargetsV2::new(self, chunk_size)
121 }
122
123 pub fn from_account_changes<'a>(
132 changes: impl IntoIterator<Item = AccountChanges<'a>>,
133 ) -> (Self, usize) {
134 let mut targets = Self::default();
135 let mut storage_target_count = 0;
136 for changes in changes {
137 if changes.selfdestructed {
138 continue
139 }
140
141 let storage_slots = changes
142 .storage
143 .changed_slots()
144 .map(|(key, _)| ProofV2Target::new(keccak256(B256::from(*key))))
145 .collect::<Vec<_>>();
146 let info_changed = changes.original != changes.current;
147 if !info_changed && storage_slots.is_empty() {
148 continue
149 }
150
151 let hashed_address = keccak256(changes.address);
152 if info_changed {
153 targets.account_targets.push(ProofV2Target::new(hashed_address));
154 }
155 if !storage_slots.is_empty() {
156 storage_target_count += storage_slots.len();
157 targets.storage_targets.insert(hashed_address, storage_slots);
158 }
159 }
160
161 (targets, storage_target_count)
162 }
163}
164
165#[derive(Debug)]
173pub struct ChunkedMultiProofTargetsV2 {
174 account_targets: alloc::vec::IntoIter<ProofV2Target>,
176 storage_targets: B256Map<Vec<ProofV2Target>>,
178 current_account_storage: Option<(B256, alloc::vec::IntoIter<ProofV2Target>)>,
180 size: usize,
182}
183
184impl ChunkedMultiProofTargetsV2 {
185 pub fn new(targets: MultiProofTargetsV2, size: usize) -> Self {
191 assert!(size > 0, "chunk size must be non-zero");
192 Self {
193 account_targets: targets.account_targets.into_iter(),
194 storage_targets: targets.storage_targets,
195 current_account_storage: None,
196 size,
197 }
198 }
199}
200
201impl Iterator for ChunkedMultiProofTargetsV2 {
202 type Item = MultiProofTargetsV2;
203
204 fn next(&mut self) -> Option<Self::Item> {
205 let mut chunk = MultiProofTargetsV2::default();
206 let mut count = 0;
207
208 if let Some((account_addr, ref mut storage_iter)) = self.current_account_storage {
210 let remaining_capacity = self.size - count;
211 let slots: Vec<_> = storage_iter.by_ref().take(remaining_capacity).collect();
212
213 count += slots.len();
214 chunk.storage_targets.insert(account_addr, slots);
215
216 if storage_iter.len() == 0 {
218 self.current_account_storage = None;
219 }
220 }
221
222 while count < self.size {
224 let Some(account_target) = self.account_targets.next() else {
225 break;
226 };
227
228 chunk.account_targets.push(account_target);
230 count += 1;
231
232 let account_addr = account_target.key();
234 if let Some(storage_slots) = self.storage_targets.remove(&account_addr) {
235 let remaining_capacity = self.size - count;
236
237 if storage_slots.len() <= remaining_capacity {
238 count += storage_slots.len();
240 chunk.storage_targets.insert(account_addr, storage_slots);
241 } else {
242 let mut storage_iter = storage_slots.into_iter();
244 let slots_in_chunk: Vec<_> =
245 storage_iter.by_ref().take(remaining_capacity).collect();
246 count += slots_in_chunk.len();
247
248 chunk.storage_targets.insert(account_addr, slots_in_chunk);
249
250 self.current_account_storage = Some((account_addr, storage_iter));
252 break;
253 }
254 }
255 }
256
257 while let Some((account_addr, storage_slots)) = self.storage_targets.iter_mut().next() &&
259 count < self.size
260 {
261 let account_addr = *account_addr;
262 let storage_slots = core::mem::take(storage_slots);
263 let remaining_capacity = self.size - count;
264
265 self.storage_targets.remove(&account_addr);
268
269 if storage_slots.len() <= remaining_capacity {
270 count += storage_slots.len();
272 chunk.storage_targets.insert(account_addr, storage_slots);
273 } else {
274 let mut storage_iter = storage_slots.into_iter();
276 let slots_in_chunk: Vec<_> =
277 storage_iter.by_ref().take(remaining_capacity).collect();
278
279 chunk.storage_targets.insert(account_addr, slots_in_chunk);
280
281 if storage_iter.len() > 0 {
283 self.current_account_storage = Some((account_addr, storage_iter));
284 }
285 break;
286 }
287 }
288
289 if chunk.account_targets.is_empty() && chunk.storage_targets.is_empty() {
290 None
291 } else {
292 Some(chunk)
293 }
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use alloy_primitives::{Address, U256};
301 use evm2::evm::{AccountInfo, StorageOverlay, StorageSlot, Tracked};
302
303 fn overlay(slots: &[(u64, u64, u64)]) -> StorageOverlay {
304 let mut storage = StorageOverlay::default();
305 for &(key, original, current) in slots {
306 storage.slots.insert(
307 U256::from(key),
308 StorageSlot {
309 value: Tracked::from_parts(U256::from(original), U256::from(current)),
310 ..Default::default()
311 },
312 );
313 }
314 storage
315 }
316
317 fn changes<'a>(
318 address: Address,
319 original: &'a AccountInfo,
320 current: Option<&'a AccountInfo>,
321 selfdestructed: bool,
322 storage: &'a StorageOverlay,
323 ) -> AccountChanges<'a> {
324 AccountChanges {
325 address,
326 original: Some(original),
327 current,
328 created: true,
329 selfdestructed,
330 touched: false,
331 code: None,
332 storage,
333 }
334 }
335
336 #[test]
337 fn from_account_changes_ignores_unchanged_reads() {
338 let info = AccountInfo::default();
339 let storage = overlay(&[(7, 8, 8)]);
340 let (targets, count) = MultiProofTargetsV2::from_account_changes([AccountChanges {
341 created: false,
342 ..changes(Address::repeat_byte(1), &info, Some(&info), false, &storage)
343 }]);
344 assert!(targets.is_empty());
345 assert_eq!(count, 0);
346 }
347
348 #[test]
349 fn from_account_changes_includes_changes() {
350 let address = Address::repeat_byte(1);
351 let original = AccountInfo::default();
352 let current = AccountInfo { nonce: 1, ..original.clone() };
353 let storage = overlay(&[(7, 0, 8)]);
354 let (targets, count) = MultiProofTargetsV2::from_account_changes([changes(
355 address,
356 &original,
357 Some(¤t),
358 false,
359 &storage,
360 )]);
361 assert_eq!(
362 targets.account_targets.iter().map(ProofV2Target::key).collect::<Vec<_>>(),
363 vec![keccak256(address)]
364 );
365 assert_eq!(
366 targets.storage_targets[&keccak256(address)]
367 .iter()
368 .map(ProofV2Target::key)
369 .collect::<Vec<_>>(),
370 vec![keccak256(B256::from(U256::from(7)))]
371 );
372 assert_eq!(count, 1);
373 }
374
375 #[test]
376 fn from_account_changes_skips_selfdestructed() {
377 let address = Address::repeat_byte(1);
378 let retained = Address::repeat_byte(2);
379 let info = AccountInfo::default();
380 let storage = overlay(&[(7, 0, 8)]);
381 let (targets, count) = MultiProofTargetsV2::from_account_changes([
382 changes(address, &info, None, true, &storage),
383 changes(retained, &info, Some(&info), false, &storage),
385 ]);
386 assert!(targets.account_targets.is_empty());
387 assert_eq!(targets.storage_targets.len(), 1);
388 assert!(targets.storage_targets.contains_key(&keccak256(retained)));
389 assert_eq!(count, 1);
390 }
391}