-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathstorage_layout.py
144 lines (106 loc) · 4.33 KB
/
storage_layout.py
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
import json
from typing import Dict, List, Tuple
import requests
from ape import chain, networks
from devtools import debug
from typer import Typer
from evm_trace import vmtrace
from hexbytes import HexBytes
from eth_utils import keccak, encode_hex
from toolz import valfilter
from eth_abi import encode_single
app = Typer()
def make_request(method: str, params: List) -> Dict:
response = chain.provider.web3.provider.make_request(method, params)
if "error" in response:
raise ValueError(response["error"]["message"])
return response["result"]
def raw_request(method: str, params: List) -> bytes:
payload = {"method": method, "params": params, "id": None, "jsonrpc": "2.0"}
response = requests.post(chain.provider.uri, json=payload)
if response.status_code != 200:
raise ValueError(response.json()["error"]["message"])
return response.content
def batch_request(calls: List[Tuple[str, List]]):
payload = [
{"method": method, "params": params, "id": None, "jsonrpc": "2.0"}
for method, params in calls
]
batch_repsonse = requests.post(chain.provider.uri, json=payload).json()
for response in batch_repsonse:
if "error" in response:
raise ValueError(response["error"]["message"])
yield response["result"]
def get_storage_keys(account):
return make_request("parity_listStorageKeys", [account, 1_000_000, None, "latest"])
def get_storage_values(account, keys):
values = batch_request([("eth_getStorageAt", [account, key, "latest"]) for key in keys])
return list(values)
def get_storage_diff(txhash: str):
state_diff = make_request("trace_replayTransaction", [txhash, ["stateDiff"]])["stateDiff"]
storage_diff = {
contract: {slot: item["*"]["to"] for slot, item in diff["storage"].items()}
for contract, diff in state_diff.items()
}
return valfilter(bool, storage_diff)
def to_int(value):
if isinstance(value, str):
return int(value, 16)
if isinstance(value, bytes):
return int.from_bytes(value, "big")
raise ValueError("invalid type %s", type(value))
@app.command()
def find_preimages(txhash: str):
response = raw_request("trace_replayTransaction", [txhash, ["vmTrace"]])
trace = vmtrace.from_rpc_response(response)
preimages = {}
for frame in vmtrace.to_trace_frames(trace):
if frame.op == "SHA3":
size, offset = [to_int(x) for x in frame.stack[-2:]]
preimage = HexBytes(frame.memory[offset : offset + size])
hashed = HexBytes(keccak(preimage))
key, slot = preimage[:32], preimage[32:]
preimages[hashed.hex()] = {"key": key.hex(), "slot": slot.hex()}
if frame.op == "SSTORE":
value, slot = frame.stack[-2:]
return preimages
@app.command()
def storage(contract: str):
keys = get_storage_keys(contract)
values = get_storage_values(contract, keys)
kv = dict(zip(keys, values))
debug(kv)
def int_to_bytes32(value):
return encode_hex(encode_single("uint256", value))
def unwrap_slot(slot, value, preimages, slot_lookup):
def unwrap(slot, path):
if slot in slot_lookup:
return {**slot_lookup[slot], "path": path, "value": value}
if slot in preimages:
p_slot, p_key = preimages[slot]["slot"], preimages[slot]["key"]
from_slot = unwrap(p_slot, path + [p_key])
from_key = unwrap(p_key, path + [p_slot])
return from_slot or from_key
return unwrap(slot, [])
@app.command()
def layout(txhash: str):
storage_layout = {"0xda816459f1ab5631232fe5e97a05bbbb94970c95": json.load(open("layout.json"))}
slot_lookup = {
contract: {int_to_bytes32(item["pos"]): item for item in data}
for contract, data in storage_layout.items()
}
# debug(storage_layout)
storage_diff = get_storage_diff(txhash)
# debug(storage_diff)
preimages = find_preimages(txhash)
debug(preimages)
for contract, storage in storage_diff.items():
if contract not in slot_lookup:
print(f"no layout avaiable for {contract}")
continue
for slot, value in storage.items():
item = unwrap_slot(slot, value, preimages, slot_lookup[contract])
debug(item)
if __name__ == "__main__":
with networks.ethereum.mainnet.use_default_provider():
app()