-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbatch_slot_diff.py
More file actions
158 lines (134 loc) · 5.76 KB
/
batch_slot_diff.py
File metadata and controls
158 lines (134 loc) · 5.76 KB
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
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python3
import csv, sys, os, time
from web3 import Web3
# CLI usage:
# python batch_slot_diff.py input.csv > report.csv
RPC_URL = os.getenv("RPC_URL", "https://mainnet.infura.io/v3/your_api_key")
if "your_api_key" in RPC_URL:
print(
"⚠️ RPC_URL still uses an Infura placeholder — set RPC_URL or pass a real --rpc.",
file=sys.stderr,
)
def checksum(addr: str) -> str:
if not Web3.is_address(addr):
print(f"❌ Invalid Ethereum address: {addr!r}", file=sys.stderr)
sys.exit(2)
return Web3.to_checksum_address(addr)
def parse_slot(s: str) -> int:
try:
v = int(s, 0) # accepts "5" or "0x5"
except Exception:
print(f"❌ Invalid slot format: {s!r} (use decimal or 0xHEX).", file=sys.stderr)
sys.exit(2)
if v < 0 or v >= 2**256:
print("❌ Slot out of range [0, 2^256).", file=sys.stderr)
sys.exit(2)
return v
def leaf_commitment(chain_id: int, address: str, slot: int, block_number: int, value: bytes) -> bytes:
addr_hex = address[2:]
if len(addr_hex) != 40:
print(f"❌ Unexpected address length for {address}", file=sys.stderr)
sys.exit(2)
payload = (
chain_id.to_bytes(8, "big")
+ bytes.fromhex(addr_hex)
+ slot.to_bytes(32, "big")
+ block_number.to_bytes(8, "big")
+ value.rjust(32, b"\x00")
)
return Web3.keccak(payload)
def pair_root(a: bytes, b: bytes) -> str:
first, second = (a, b) if a.hex() < b.hex() else (b, a)
return "0x" + Web3.keccak(first + second).hex()
def to_hex(b: bytes) -> str:
return "0x" + b.hex()
def main():
if len(sys.argv) != 2:
print("Usage: python batch_slot_diff.py <input.csv>", file=sys.stderr)
sys.exit(2)
inp = sys.argv[1]
if not os.path.exists(inp):
print(f"Input not found: {inp}", file=sys.stderr)
sys.exit(2)
w3 = Web3(Web3.HTTPProvider(RPC_URL, request_kwargs={"timeout": 20}))
if not w3.is_connected():
print("❌ Failed to connect to RPC. Check RPC_URL.", file=sys.stderr)
sys.exit(1)
chain_id = w3.eth.chain_id
tip = w3.eth.block_number
print(f"🌐 Connected (chainId={chain_id}, tip={tip})", file=sys.stderr)
reader = csv.DictReader(open(inp, newline=""))
required = {"address","slot","block_a","block_b"}
if not required.issubset(reader.fieldnames or set()): print(f"❌ CSV must contain: {sorted(required)}"); sys.exit(2)
fieldnames = ["address","slot","block_a","block_b","value_a","value_b","leaf_a","leaf_b","pair_root","changed"]
writer = csv.DictWriter(sys.stdout, fieldnames=fieldnames)
writer.writeheader()
processed = 0
for row in reader:
...
writer.writerow({...})
processed += 1
for row in reader:
try:
address = checksum(row["address"].strip())
if not Web3.is_address(address): print(f"❌ Invalid address: {row['address']}"); continue
slot = parse_slot(row["slot"].strip())
block_a = int(row["block_a"])
block_b = int(row["block_b"])
if block_a > block_b:
block_a, block_b = block_b, block_a
print(f"🔄 Swapped blocks for {address}", file=sys.stderr)
except Exception as e:
print(f"⚠️ Skipping invalid row {row}: {e}", file=sys.stderr)
continue
# ensure ascending order for consistency
if block_a > block_b:
block_a, block_b = block_b, block_a
code = w3.eth.get_code(address)
if not code:
print(
f"⚠️ Target address {address} has no contract code (likely EOA) — storage slot results may be misleading.",
file=sys.stderr,
)
try:
tip = w3.eth.block_number
if block_a > tip or block_b > tip:
print(f"⚠️ {address}: requested block beyond tip {tip}", file=sys.stderr)
continue
v_a = w3.eth.get_storage_at(address, slot, block_identifier=block_a)
v_b = w3.eth.get_storage_at(address, slot, block_identifier=block_b)
except Exception as e:
print(f"⚠️ RPC error on {address} slot {slot}: {e}", file=sys.stderr)
continue
if v_a is None or v_b is None:
print(f"⚠️ Missing storage data for {address}", file=sys.stderr)
continue
leaf_a = leaf_commitment(chain_id, address, slot, block_a, v_a)
leaf_b = leaf_commitment(chain_id, address, slot, block_b, v_b)
fee_a = w3.eth.get_block(block_a).get("baseFeePerGas", 0)
fee_b = w3.eth.get_block(block_b).get("baseFeePerGas", 0)
print(
f"⛽ BaseFee Gwei for {address}: A={Web3.from_wei(fee_a, 'gwei'):.2f}, B={Web3.from_wei(fee_b, 'gwei'):.2f}",
file=sys.stderr,
)
root = pair_root(leaf_a, leaf_b)
changed = "YES" if v_a != v_b else "NO"
if v_a == v_b == b"\x00" * 32:
print(f"ℹ️ Slot {slot} for {address} is zero at both blocks.", file=sys.stderr)
row_time = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
print(f"✅ {address} done @ {row_time}", file=sys.stderr)
writer.writerow({
"address": address,
"slot": row["slot"].strip(),
"block_a": block_a,
"block_b": block_b,
"value_a": to_hex(v_a),
"value_b": to_hex(v_b),
"leaf_a": to_hex(leaf_a),
"leaf_b": to_hex(leaf_b),
"pair_root": root,
"changed": changed,
})
print(f"\n📊 Processed {processed} rows total.", file=sys.stderr)
if __name__ == "__main__":
main()