1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! Generic ERC-20 `Transfer` decoder (generic core).
//!
//! [`Erc20TransferDecoder`] turns a standard ERC-20
//! `Transfer(from, to, value)` log into two relative balance updates — a
//! [`SlotDelta::Sub`] on the sender's balance slot and a
//! [`SlotDelta::Add`] on the recipient's — so the cache
//! tracks balances from the event stream without ever reading the resulting
//! absolute balances. It is the log-driven form of the Phase 3 reactive-balance
//! case.
//!
//! # Balance-slot derivation
//!
//! An ERC-20 `balanceOf` is a `mapping(address => uint256)` at some base slot.
//! The decoder hashes the owner into that mapping the canonical Solidity way:
//! `keccak256(abi.encode(owner, balance_slot))`. The base slot is configurable
//! per token ([`with_token`](Erc20TransferDecoder::with_token)) with a default
//! fallback ([`new`](Erc20TransferDecoder::new)), since different tokens place
//! `balanceOf` at different slots.
//!
//! # Mint / burn legs
//!
//! A mint (`from == 0`) or burn (`to == 0`) has no real holder on the
//! zero-address leg, so that leg is **skipped** — only the non-zero side emits a
//! delta. Cold balances follow the Phase 3 contract: the
//! [`SlotDelta`] is skipped at apply time and surfaced in
//! [`StateDiff::skipped`](crate::StateDiff::skipped) (the caller seeds the
//! balance, or the next read lazily fetches it). The decoder ignores the
//! [`StateView`] — it is stateless.
use HashMap;
use ;
use SolValue;
use crate;
use crateTransferInspector;
use crate;
/// Decodes ERC-20 `Transfer` logs into relative balance [`SlotDelta`] updates.
///
/// ```
/// use alloy_primitives::{Address, Bytes, Log, U256, keccak256};
/// use alloy_sol_types::SolValue;
/// use evm_fork_cache::events::{EventDecoder, StateView};
/// use evm_fork_cache::events::erc20::Erc20TransferDecoder;
/// use evm_fork_cache::{SlotDelta, StateUpdate};
///
/// // A read-only view that reports every slot cold (decoder is stateless anyway).
/// struct ColdView;
/// impl StateView for ColdView {
/// fn storage(&self, _: Address, _: U256) -> Option<U256> { None }
/// }
///
/// let token = Address::repeat_byte(0x20);
/// let from = Address::repeat_byte(0x21);
/// let to = Address::repeat_byte(0x22);
///
/// // Transfer(from, to, 100) log: balanceOf mapping at slot 3.
/// let sig = keccak256(b"Transfer(address,address,uint256)");
/// let log = Log::new_unchecked(
/// token,
/// vec![sig, from.into_word(), to.into_word()],
/// Bytes::copy_from_slice(&U256::from(100).to_be_bytes::<32>()),
/// );
///
/// let decoder = Erc20TransferDecoder::new(U256::from(3));
/// let updates = decoder.decode(&log, &ColdView);
///
/// let slot = |owner: Address| {
/// U256::from_be_bytes(keccak256((owner, U256::from(3)).abi_encode()).0)
/// };
/// assert_eq!(updates, vec![
/// StateUpdate::slot_delta(token, slot(from), SlotDelta::Sub(U256::from(100))),
/// StateUpdate::slot_delta(token, slot(to), SlotDelta::Add(U256::from(100))),
/// ]);
/// ```
/// The hashed storage slot of `balanceOf[owner]` for a `mapping(address =>
/// uint256)` at `mapping_slot`.