evmole 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {evmole-0.3.2 → evmole-0.3.4}/PKG-INFO +51 -29
- {evmole-0.3.2 → evmole-0.3.4}/README.md +49 -27
- {evmole-0.3.2 → evmole-0.3.4}/evmole/arguments.py +52 -17
- {evmole-0.3.2 → evmole-0.3.4}/evmole/evm/element.py +2 -2
- {evmole-0.3.2 → evmole-0.3.4}/evmole/evm/memory.py +5 -0
- {evmole-0.3.2 → evmole-0.3.4}/evmole/evm/opcodes.py +5 -0
- {evmole-0.3.2 → evmole-0.3.4}/evmole/evm/vm.py +110 -61
- {evmole-0.3.2 → evmole-0.3.4}/pyproject.toml +10 -8
- {evmole-0.3.2 → evmole-0.3.4}/LICENSE +0 -0
- {evmole-0.3.2 → evmole-0.3.4}/evmole/README.md +0 -0
- {evmole-0.3.2 → evmole-0.3.4}/evmole/__init__.py +0 -0
- {evmole-0.3.2 → evmole-0.3.4}/evmole/evm/__init__.py +0 -0
- {evmole-0.3.2 → evmole-0.3.4}/evmole/evm/stack.py +0 -0
- {evmole-0.3.2 → evmole-0.3.4}/evmole/selectors.py +0 -0
- {evmole-0.3.2 → evmole-0.3.4}/evmole/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: evmole
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Extracts function selectors and arguments from EVM bytecode
|
|
5
5
|
Home-page: https://github.com/cdump/evmole
|
|
6
6
|
License: MIT
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Provides-Extra: benchmark
|
|
15
|
-
Requires-Dist: aiohttp (>=3.9
|
|
15
|
+
Requires-Dist: aiohttp (>=3.9,<4.0) ; extra == "benchmark"
|
|
16
16
|
Project-URL: Repository, https://github.com/cdump/evmole
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
|
|
@@ -77,6 +77,25 @@ print( function_arguments(code, '2125b65b') )
|
|
|
77
77
|
# Output(str): 'uint32,address,uint224'
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
### Foundry
|
|
81
|
+
<a href="https://getfoundry.sh/">Foundy's cast</a> uses the Rust implementation of EVMole
|
|
82
|
+
```sh
|
|
83
|
+
|
|
84
|
+
$ cast selectors $(cast code 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)
|
|
85
|
+
0x06fdde03
|
|
86
|
+
0x095ea7b3 address,uint256
|
|
87
|
+
0x18160ddd
|
|
88
|
+
0x23b872dd address,address,uint256
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
$ cast selectors --resolve $(cast code 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)
|
|
92
|
+
0x06fdde03 name()
|
|
93
|
+
0x095ea7b3 address,uint256 approve(address,uint256)
|
|
94
|
+
0x18160ddd totalSupply()
|
|
95
|
+
0x23b872dd address,address,uint256 transferFrom(address,address,uint256)
|
|
96
|
+
...
|
|
97
|
+
```
|
|
98
|
+
|
|
80
99
|
See [examples](./examples) for more
|
|
81
100
|
|
|
82
101
|
## Benchmark
|
|
@@ -129,11 +148,11 @@ See [examples](./examples) for more
|
|
|
129
148
|
</tr>
|
|
130
149
|
<tr>
|
|
131
150
|
<td><i>Time</i></td>
|
|
132
|
-
<td>0.
|
|
133
|
-
<td>
|
|
151
|
+
<td>0.7s · 1.5s · 2.0s</td>
|
|
152
|
+
<td>3.0s</td>
|
|
134
153
|
<td>0.7s</td>
|
|
135
|
-
<td>
|
|
136
|
-
<td>1.
|
|
154
|
+
<td>727.2s</td>
|
|
155
|
+
<td>1.9s</td>
|
|
137
156
|
</tr>
|
|
138
157
|
<tr><td colspan="7"></td></tr>
|
|
139
158
|
<tr>
|
|
@@ -158,6 +177,7 @@ See [examples](./examples) for more
|
|
|
158
177
|
<td>3 🥇</td>
|
|
159
178
|
<td>51</td>
|
|
160
179
|
<td>10798</td>
|
|
180
|
+
<!-- -->
|
|
161
181
|
<td>14652</td>
|
|
162
182
|
</tr>
|
|
163
183
|
<tr>
|
|
@@ -165,16 +185,18 @@ See [examples](./examples) for more
|
|
|
165
185
|
<td>10 🥇</td>
|
|
166
186
|
<td>32</td>
|
|
167
187
|
<td>3538</td>
|
|
188
|
+
<!-- -->
|
|
168
189
|
<td>96</td>
|
|
169
190
|
</tr>
|
|
170
191
|
<tr>
|
|
171
192
|
<td><i>Time</i></td>
|
|
172
|
-
<td>
|
|
173
|
-
<td>
|
|
174
|
-
<td>
|
|
175
|
-
|
|
193
|
+
<td>9.8s · 19.6s · 36.4s</td>
|
|
194
|
+
<td>52.3s</td>
|
|
195
|
+
<td>11.5s</td>
|
|
196
|
+
<!-- -->
|
|
197
|
+
<td>46.3s</td>
|
|
176
198
|
</tr>
|
|
177
|
-
<tr><td colspan="
|
|
199
|
+
<tr><td colspan="8"></td></tr>
|
|
178
200
|
<tr>
|
|
179
201
|
<td rowspan="5"><b>vyper</b><br><sub>780<br>contracts<br><br>21244<br>functions</sub></td>
|
|
180
202
|
<td><i>FP <sub>contracts</sub></i></td>
|
|
@@ -210,11 +232,11 @@ See [examples](./examples) for more
|
|
|
210
232
|
</tr>
|
|
211
233
|
<tr>
|
|
212
234
|
<td><i>Time</i></td>
|
|
213
|
-
<td>0.
|
|
214
|
-
<td>
|
|
215
|
-
<td>0.
|
|
216
|
-
<td>
|
|
217
|
-
<td>1.
|
|
235
|
+
<td>0.6s · 0.9s · 1.4s</td>
|
|
236
|
+
<td>2.3s</td>
|
|
237
|
+
<td>0.6s</td>
|
|
238
|
+
<td>17.0s</td>
|
|
239
|
+
<td>1.2s</td>
|
|
218
240
|
</tr>
|
|
219
241
|
</table>
|
|
220
242
|
|
|
@@ -232,49 +254,49 @@ See [examples](./examples) for more
|
|
|
232
254
|
<tr>
|
|
233
255
|
<td rowspan="2"><b>largest1k</b><br><sub>1000<br>contracts<br><br>24427<br>functions</sub></td>
|
|
234
256
|
<td><i>Errors</i></td>
|
|
235
|
-
<td>15.0%,
|
|
236
|
-
<td>42.
|
|
257
|
+
<td>15.0%, 3652 🥇</td>
|
|
258
|
+
<td>42.7%, 10438</td>
|
|
237
259
|
<td>58.3%, 14242</td>
|
|
238
260
|
</tr>
|
|
239
261
|
<tr>
|
|
240
262
|
<td><i>Time</i></td>
|
|
241
|
-
<td>
|
|
242
|
-
<td>
|
|
243
|
-
<td>0.
|
|
263
|
+
<td>1.1s · 7.5s · 15.7s</td>
|
|
264
|
+
<td>731.4s</td>
|
|
265
|
+
<td>0.8s</td>
|
|
244
266
|
</tr>
|
|
245
267
|
<tr><td colspan="5"></td></tr>
|
|
246
268
|
<tr>
|
|
247
269
|
<td rowspan="2"><b>random50k</b><br><sub>50000<br>contracts<br><br>1171102<br>functions</sub></td>
|
|
248
270
|
<td><i>Errors</i></td>
|
|
249
|
-
<td>5.
|
|
271
|
+
<td>5.1%, 59484 🥇</td>
|
|
250
272
|
<td rowspan="2">waiting fixes</td>
|
|
251
273
|
<td>54.9%, 643213</td>
|
|
252
274
|
</tr>
|
|
253
275
|
<tr>
|
|
254
276
|
<td><i>Time</i></td>
|
|
255
|
-
<td>
|
|
277
|
+
<td>22.6s · 247.0s · 584.7s</td>
|
|
256
278
|
<!-- -->
|
|
257
|
-
<td>
|
|
279
|
+
<td>9.5s</td>
|
|
258
280
|
</tr>
|
|
259
281
|
<tr><td colspan="5"></td></tr>
|
|
260
282
|
<tr>
|
|
261
283
|
<td rowspan="2"><b>vyper</b><br><sub>780<br>contracts<br><br>21244<br>functions</sub></td>
|
|
262
284
|
<td><i>Errors</i></td>
|
|
263
|
-
<td>
|
|
285
|
+
<td>50.9%, 10805 🥇</td>
|
|
264
286
|
<td>100.0%, 21244</td>
|
|
265
287
|
<td>56.8%, 12077</td>
|
|
266
288
|
</tr>
|
|
267
289
|
<tr>
|
|
268
290
|
<td><i>Time</i></td>
|
|
269
|
-
<td>0.
|
|
270
|
-
<td>
|
|
271
|
-
<td>0.
|
|
291
|
+
<td>0.9s · 7.3s · 13.9s</td>
|
|
292
|
+
<td>16.8s</td>
|
|
293
|
+
<td>0.7s</td>
|
|
272
294
|
</tr>
|
|
273
295
|
</table>
|
|
274
296
|
|
|
275
297
|
See [benchmark/README.md](./benchmark/) for the methodology and commands to reproduce these results
|
|
276
298
|
|
|
277
|
-
<i>versions: evmole v0.3.
|
|
299
|
+
<i>versions: evmole v0.3.3; whatsabi v0.11.0; evm-hound-rs v0.1.4; heimdall-rs v0.7.3</i>
|
|
278
300
|
|
|
279
301
|
## How it works
|
|
280
302
|
|
|
@@ -59,6 +59,25 @@ print( function_arguments(code, '2125b65b') )
|
|
|
59
59
|
# Output(str): 'uint32,address,uint224'
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
### Foundry
|
|
63
|
+
<a href="https://getfoundry.sh/">Foundy's cast</a> uses the Rust implementation of EVMole
|
|
64
|
+
```sh
|
|
65
|
+
|
|
66
|
+
$ cast selectors $(cast code 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)
|
|
67
|
+
0x06fdde03
|
|
68
|
+
0x095ea7b3 address,uint256
|
|
69
|
+
0x18160ddd
|
|
70
|
+
0x23b872dd address,address,uint256
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
$ cast selectors --resolve $(cast code 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)
|
|
74
|
+
0x06fdde03 name()
|
|
75
|
+
0x095ea7b3 address,uint256 approve(address,uint256)
|
|
76
|
+
0x18160ddd totalSupply()
|
|
77
|
+
0x23b872dd address,address,uint256 transferFrom(address,address,uint256)
|
|
78
|
+
...
|
|
79
|
+
```
|
|
80
|
+
|
|
62
81
|
See [examples](./examples) for more
|
|
63
82
|
|
|
64
83
|
## Benchmark
|
|
@@ -111,11 +130,11 @@ See [examples](./examples) for more
|
|
|
111
130
|
</tr>
|
|
112
131
|
<tr>
|
|
113
132
|
<td><i>Time</i></td>
|
|
114
|
-
<td>0.
|
|
115
|
-
<td>
|
|
133
|
+
<td>0.7s · 1.5s · 2.0s</td>
|
|
134
|
+
<td>3.0s</td>
|
|
116
135
|
<td>0.7s</td>
|
|
117
|
-
<td>
|
|
118
|
-
<td>1.
|
|
136
|
+
<td>727.2s</td>
|
|
137
|
+
<td>1.9s</td>
|
|
119
138
|
</tr>
|
|
120
139
|
<tr><td colspan="7"></td></tr>
|
|
121
140
|
<tr>
|
|
@@ -140,6 +159,7 @@ See [examples](./examples) for more
|
|
|
140
159
|
<td>3 🥇</td>
|
|
141
160
|
<td>51</td>
|
|
142
161
|
<td>10798</td>
|
|
162
|
+
<!-- -->
|
|
143
163
|
<td>14652</td>
|
|
144
164
|
</tr>
|
|
145
165
|
<tr>
|
|
@@ -147,16 +167,18 @@ See [examples](./examples) for more
|
|
|
147
167
|
<td>10 🥇</td>
|
|
148
168
|
<td>32</td>
|
|
149
169
|
<td>3538</td>
|
|
170
|
+
<!-- -->
|
|
150
171
|
<td>96</td>
|
|
151
172
|
</tr>
|
|
152
173
|
<tr>
|
|
153
174
|
<td><i>Time</i></td>
|
|
154
|
-
<td>
|
|
155
|
-
<td>
|
|
156
|
-
<td>
|
|
157
|
-
|
|
175
|
+
<td>9.8s · 19.6s · 36.4s</td>
|
|
176
|
+
<td>52.3s</td>
|
|
177
|
+
<td>11.5s</td>
|
|
178
|
+
<!-- -->
|
|
179
|
+
<td>46.3s</td>
|
|
158
180
|
</tr>
|
|
159
|
-
<tr><td colspan="
|
|
181
|
+
<tr><td colspan="8"></td></tr>
|
|
160
182
|
<tr>
|
|
161
183
|
<td rowspan="5"><b>vyper</b><br><sub>780<br>contracts<br><br>21244<br>functions</sub></td>
|
|
162
184
|
<td><i>FP <sub>contracts</sub></i></td>
|
|
@@ -192,11 +214,11 @@ See [examples](./examples) for more
|
|
|
192
214
|
</tr>
|
|
193
215
|
<tr>
|
|
194
216
|
<td><i>Time</i></td>
|
|
195
|
-
<td>0.
|
|
196
|
-
<td>
|
|
197
|
-
<td>0.
|
|
198
|
-
<td>
|
|
199
|
-
<td>1.
|
|
217
|
+
<td>0.6s · 0.9s · 1.4s</td>
|
|
218
|
+
<td>2.3s</td>
|
|
219
|
+
<td>0.6s</td>
|
|
220
|
+
<td>17.0s</td>
|
|
221
|
+
<td>1.2s</td>
|
|
200
222
|
</tr>
|
|
201
223
|
</table>
|
|
202
224
|
|
|
@@ -214,49 +236,49 @@ See [examples](./examples) for more
|
|
|
214
236
|
<tr>
|
|
215
237
|
<td rowspan="2"><b>largest1k</b><br><sub>1000<br>contracts<br><br>24427<br>functions</sub></td>
|
|
216
238
|
<td><i>Errors</i></td>
|
|
217
|
-
<td>15.0%,
|
|
218
|
-
<td>42.
|
|
239
|
+
<td>15.0%, 3652 🥇</td>
|
|
240
|
+
<td>42.7%, 10438</td>
|
|
219
241
|
<td>58.3%, 14242</td>
|
|
220
242
|
</tr>
|
|
221
243
|
<tr>
|
|
222
244
|
<td><i>Time</i></td>
|
|
223
|
-
<td>
|
|
224
|
-
<td>
|
|
225
|
-
<td>0.
|
|
245
|
+
<td>1.1s · 7.5s · 15.7s</td>
|
|
246
|
+
<td>731.4s</td>
|
|
247
|
+
<td>0.8s</td>
|
|
226
248
|
</tr>
|
|
227
249
|
<tr><td colspan="5"></td></tr>
|
|
228
250
|
<tr>
|
|
229
251
|
<td rowspan="2"><b>random50k</b><br><sub>50000<br>contracts<br><br>1171102<br>functions</sub></td>
|
|
230
252
|
<td><i>Errors</i></td>
|
|
231
|
-
<td>5.
|
|
253
|
+
<td>5.1%, 59484 🥇</td>
|
|
232
254
|
<td rowspan="2">waiting fixes</td>
|
|
233
255
|
<td>54.9%, 643213</td>
|
|
234
256
|
</tr>
|
|
235
257
|
<tr>
|
|
236
258
|
<td><i>Time</i></td>
|
|
237
|
-
<td>
|
|
259
|
+
<td>22.6s · 247.0s · 584.7s</td>
|
|
238
260
|
<!-- -->
|
|
239
|
-
<td>
|
|
261
|
+
<td>9.5s</td>
|
|
240
262
|
</tr>
|
|
241
263
|
<tr><td colspan="5"></td></tr>
|
|
242
264
|
<tr>
|
|
243
265
|
<td rowspan="2"><b>vyper</b><br><sub>780<br>contracts<br><br>21244<br>functions</sub></td>
|
|
244
266
|
<td><i>Errors</i></td>
|
|
245
|
-
<td>
|
|
267
|
+
<td>50.9%, 10805 🥇</td>
|
|
246
268
|
<td>100.0%, 21244</td>
|
|
247
269
|
<td>56.8%, 12077</td>
|
|
248
270
|
</tr>
|
|
249
271
|
<tr>
|
|
250
272
|
<td><i>Time</i></td>
|
|
251
|
-
<td>0.
|
|
252
|
-
<td>
|
|
253
|
-
<td>0.
|
|
273
|
+
<td>0.9s · 7.3s · 13.9s</td>
|
|
274
|
+
<td>16.8s</td>
|
|
275
|
+
<td>0.7s</td>
|
|
254
276
|
</tr>
|
|
255
277
|
</table>
|
|
256
278
|
|
|
257
279
|
See [benchmark/README.md](./benchmark/) for the methodology and commands to reproduce these results
|
|
258
280
|
|
|
259
|
-
<i>versions: evmole v0.3.
|
|
281
|
+
<i>versions: evmole v0.3.3; whatsabi v0.11.0; evm-hound-rs v0.1.4; heimdall-rs v0.7.3</i>
|
|
260
282
|
|
|
261
283
|
## How it works
|
|
262
284
|
|
|
@@ -29,12 +29,33 @@ class IsZeroResult:
|
|
|
29
29
|
dynamic: bool = False
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
class ArgsResult:
|
|
33
|
+
args: dict[int, str]
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
self.args = {}
|
|
37
|
+
|
|
38
|
+
def set(self, offset: int, atype: str):
|
|
39
|
+
self.args[offset] = atype
|
|
40
|
+
|
|
41
|
+
def set_if(self, offset: int, if_val: str, atype: str):
|
|
42
|
+
v = self.args.get(offset)
|
|
43
|
+
if v is not None:
|
|
44
|
+
if v == if_val:
|
|
45
|
+
self.args[offset] = atype
|
|
46
|
+
elif atype == '':
|
|
47
|
+
self.args[offset] = atype
|
|
48
|
+
|
|
49
|
+
def join_to_string(self) -> str:
|
|
50
|
+
return ','.join(v[1] if v[1] != '' else 'uint256' for v in sorted(self.args.items()))
|
|
51
|
+
|
|
52
|
+
|
|
32
53
|
def function_arguments(code: bytes | str, selector: bytes | str, gas_limit: int = int(1e4)) -> str:
|
|
33
54
|
bytes_selector = to_bytes(selector)
|
|
34
55
|
vm = Vm(code=to_bytes(code), calldata=Element(data=bytes_selector, label='calldata'))
|
|
35
56
|
gas_used = 0
|
|
36
57
|
inside_function = False
|
|
37
|
-
args
|
|
58
|
+
args = ArgsResult()
|
|
38
59
|
while not vm.stopped:
|
|
39
60
|
try:
|
|
40
61
|
ret = vm.step()
|
|
@@ -44,7 +65,7 @@ def function_arguments(code: bytes | str, selector: bytes | str, gas_limit: int
|
|
|
44
65
|
break
|
|
45
66
|
|
|
46
67
|
if inside_function:
|
|
47
|
-
# print(vm, '\n')
|
|
68
|
+
# print(vm, '\n', sep='')
|
|
48
69
|
# print(ret)
|
|
49
70
|
pass
|
|
50
71
|
except (StackIndexError, UnsupportedOpError) as ex:
|
|
@@ -62,21 +83,26 @@ def function_arguments(code: bytes | str, selector: bytes | str, gas_limit: int
|
|
|
62
83
|
match ret:
|
|
63
84
|
case (Op.CALLDATASIZE, _):
|
|
64
85
|
vm.stack.pop()
|
|
65
|
-
vm.stack.push_uint(
|
|
86
|
+
vm.stack.push_uint(131072)
|
|
66
87
|
|
|
67
88
|
case (Op.CALLDATALOAD, _, Element(Arg() as arg)):
|
|
68
|
-
args
|
|
89
|
+
args.set(arg.offset, 'bytes')
|
|
69
90
|
vm.stack.pop()
|
|
70
91
|
vm.stack.push(Element(data=(1).to_bytes(32, 'big'), label=ArgDynamicLength(offset=arg.offset)))
|
|
71
92
|
|
|
72
93
|
case (Op.CALLDATALOAD, _, Element(ArgDynamic() as arg)):
|
|
73
|
-
vm.stack.
|
|
94
|
+
vm.stack.pop()
|
|
95
|
+
vm.stack.push(Element(data=(0).to_bytes(32, 'big'), label=Arg(offset=arg.offset, dynamic=True)))
|
|
74
96
|
|
|
75
97
|
case (Op.CALLDATALOAD, _, Element() as offset):
|
|
76
98
|
off = int.from_bytes(offset.data, 'big')
|
|
77
|
-
if off >= 4 and off <
|
|
78
|
-
vm.stack.
|
|
79
|
-
|
|
99
|
+
if off >= 4 and off < (131072 - 1024):
|
|
100
|
+
vm.stack.pop()
|
|
101
|
+
vm.stack.push(Element(data=(0).to_bytes(32, 'big'), label=Arg(offset=off)))
|
|
102
|
+
args.set_if(off, '', '')
|
|
103
|
+
|
|
104
|
+
case (Op.MUL, _, Element(Arg() as arg), Element()) | (Op.MUL, _, Element(), Element(Arg() as arg)):
|
|
105
|
+
args.set_if(arg.offset, 'bool', '')
|
|
80
106
|
|
|
81
107
|
case (Op.ADD, _, Element(Arg() as arg), Element() as ot) | (Op.ADD, _, Element() as ot, Element(Arg() as arg)):
|
|
82
108
|
vm.stack.peek().label = (
|
|
@@ -88,14 +114,14 @@ def function_arguments(code: bytes | str, selector: bytes | str, gas_limit: int
|
|
|
88
114
|
|
|
89
115
|
case (Op.SHL, _, Element() as ot, Element(ArgDynamicLength() as arg)):
|
|
90
116
|
if int.from_bytes(ot.data, 'big') == 5:
|
|
91
|
-
args
|
|
117
|
+
args.set(arg.offset, 'uint256[]')
|
|
92
118
|
|
|
93
119
|
case (
|
|
94
120
|
(Op.MUL, _, Element(ArgDynamicLength() as arg), Element() as ot)
|
|
95
121
|
| (Op.MUL, _, Element() as ot, Element(ArgDynamicLength() as arg))
|
|
96
122
|
):
|
|
97
123
|
if int.from_bytes(ot.data, 'big') == 32:
|
|
98
|
-
args
|
|
124
|
+
args.set(arg.offset, 'uint256[]')
|
|
99
125
|
|
|
100
126
|
case (Op.AND, _, Element(Arg() as arg), Element() as ot) | (Op.AND, _, Element() as ot, Element(Arg() as arg)):
|
|
101
127
|
v = int.from_bytes(ot.data, 'big')
|
|
@@ -106,7 +132,7 @@ def function_arguments(code: bytes | str, selector: bytes | str, gas_limit: int
|
|
|
106
132
|
bl = v.bit_length()
|
|
107
133
|
if bl % 8 == 0:
|
|
108
134
|
t = 'address' if bl == 160 else f'uint{bl}'
|
|
109
|
-
args
|
|
135
|
+
args.set(arg.offset, f'{t}[]' if arg.dynamic else t)
|
|
110
136
|
else:
|
|
111
137
|
# 0xffff0000
|
|
112
138
|
v = int.from_bytes(ot.data, 'little')
|
|
@@ -114,24 +140,33 @@ def function_arguments(code: bytes | str, selector: bytes | str, gas_limit: int
|
|
|
114
140
|
bl = v.bit_length()
|
|
115
141
|
if bl % 8 == 0:
|
|
116
142
|
t = f'bytes{bl // 8}'
|
|
117
|
-
args
|
|
143
|
+
args.set(arg.offset, f'{t}[]' if arg.dynamic else t)
|
|
118
144
|
|
|
119
145
|
case (Op.ISZERO, _, Element(Arg() as arg)):
|
|
120
146
|
vm.stack.peek().label = IsZeroResult(offset=arg.offset, dynamic=arg.dynamic)
|
|
121
147
|
|
|
122
148
|
case (Op.ISZERO, _, Element(IsZeroResult() as arg)):
|
|
123
|
-
|
|
149
|
+
# Detect check for 0 in DIV, it's not bool in that case: ISZERO, ISZERO, PUSH off, JUMPI, JUMPDEST, DIV
|
|
150
|
+
is_bool = True
|
|
151
|
+
op = vm.code[vm.pc]
|
|
152
|
+
if op >= Op.PUSH1 and op <= Op.PUSH4:
|
|
153
|
+
n = op - Op.PUSH0
|
|
154
|
+
if vm.code[vm.pc + n + 1] == Op.JUMPI:
|
|
155
|
+
jumpdest = int.from_bytes(vm.code[(vm.pc + 1) : (vm.pc + 1 + n)], signed=False)
|
|
156
|
+
if jumpdest + 1 < len(vm.code) and vm.code[jumpdest] == Op.JUMPDEST and vm.code[jumpdest + 1] == Op.DIV:
|
|
157
|
+
is_bool = False
|
|
158
|
+
if is_bool:
|
|
159
|
+
args.set(arg.offset, 'bool[]' if arg.dynamic else 'bool')
|
|
124
160
|
|
|
125
161
|
case (Op.SIGNEXTEND, _, s0, Element(Arg() as arg)):
|
|
126
162
|
if s0 < 32:
|
|
127
163
|
t = f'int{(s0+1)*8}'
|
|
128
|
-
args
|
|
164
|
+
args.set(arg.offset, f'{t}[]' if arg.dynamic else t)
|
|
129
165
|
|
|
130
166
|
case (Op.BYTE, _, _, Element(Arg() as arg)):
|
|
131
|
-
|
|
132
|
-
args[arg.offset] = 'bytes32'
|
|
167
|
+
args.set_if(arg.offset, '', 'bytes32')
|
|
133
168
|
|
|
134
169
|
# case (Op.LT, _, CallDataArgument() as arg, _):
|
|
135
170
|
# args[arg.offset] = 'uint8' # enum
|
|
136
171
|
|
|
137
|
-
return
|
|
172
|
+
return args.join_to_string()
|
|
@@ -5,9 +5,9 @@ class Element:
|
|
|
5
5
|
__match_args__ = ('label',)
|
|
6
6
|
|
|
7
7
|
data: bytes
|
|
8
|
-
label: Any|None
|
|
8
|
+
label: Any | None
|
|
9
9
|
|
|
10
|
-
def __init__(self, data: bytes, label: Any|None = None):
|
|
10
|
+
def __init__(self, data: bytes, label: Any | None = None):
|
|
11
11
|
self.data = data
|
|
12
12
|
self.label = label
|
|
13
13
|
|
|
@@ -12,6 +12,11 @@ class Memory:
|
|
|
12
12
|
def store(self, offset: int, value: Element):
|
|
13
13
|
self._data.append((offset, value))
|
|
14
14
|
|
|
15
|
+
def size(self) -> int:
|
|
16
|
+
if len(self._data) == 0:
|
|
17
|
+
return 0
|
|
18
|
+
return max(off + len(val.data) for off, val in self._data)
|
|
19
|
+
|
|
15
20
|
def load(self, offset: int) -> tuple[bytes, set]:
|
|
16
21
|
used = set()
|
|
17
22
|
res = [b'\x00'] * 32
|
|
@@ -60,6 +60,8 @@ class Op:
|
|
|
60
60
|
CHAINID = 0x46
|
|
61
61
|
SELFBALANCE = 0x47
|
|
62
62
|
BASEFEE = 0x48
|
|
63
|
+
BLOBHASH = 0x49
|
|
64
|
+
BLOBBASEFEE = 0x4A
|
|
63
65
|
|
|
64
66
|
POP = 0x50
|
|
65
67
|
MLOAD = 0x51
|
|
@@ -73,6 +75,9 @@ class Op:
|
|
|
73
75
|
MSIZE = 0x59
|
|
74
76
|
GAS = 0x5A
|
|
75
77
|
JUMPDEST = 0x5B
|
|
78
|
+
TLOAD = 0x5C
|
|
79
|
+
TSTORE = 0x5D
|
|
80
|
+
MCOPY = 0x5E
|
|
76
81
|
PUSH0 = 0x5F
|
|
77
82
|
|
|
78
83
|
PUSH1 = 0x60
|
|
@@ -60,6 +60,18 @@ class Vm:
|
|
|
60
60
|
self.stopped = True
|
|
61
61
|
return (op, *ret)
|
|
62
62
|
|
|
63
|
+
def _bop(self, cb):
|
|
64
|
+
raws0 = self.stack.pop()
|
|
65
|
+
raws1 = self.stack.pop()
|
|
66
|
+
|
|
67
|
+
s0 = int.from_bytes(raws0.data, 'big', signed=False)
|
|
68
|
+
s1 = int.from_bytes(raws1.data, 'big', signed=False)
|
|
69
|
+
|
|
70
|
+
gas_used, res = cb(raws0, s0, raws1, s1)
|
|
71
|
+
|
|
72
|
+
self.stack.push_uint(res)
|
|
73
|
+
return (gas_used, raws0, raws1)
|
|
74
|
+
|
|
63
75
|
def _exec_opcode(self, op: OpCode) -> tuple[int, *tuple[Any, ...]]:
|
|
64
76
|
match op:
|
|
65
77
|
case op if op >= Op.PUSH0 and op <= Op.PUSH32:
|
|
@@ -88,71 +100,57 @@ class Vm:
|
|
|
88
100
|
case Op.JUMPDEST:
|
|
89
101
|
return (1,)
|
|
90
102
|
|
|
91
|
-
case Op.REVERT:
|
|
92
|
-
# skip
|
|
103
|
+
case Op.REVERT | Op.STOP | Op.RETURN:
|
|
104
|
+
# skip stack pop()s
|
|
93
105
|
self.stopped = True
|
|
94
106
|
return (4,)
|
|
95
107
|
|
|
96
|
-
case
|
|
97
|
-
|
|
98
|
-
Op.LT,
|
|
99
|
-
Op.GT,
|
|
100
|
-
Op.SUB,
|
|
101
|
-
Op.ADD,
|
|
102
|
-
Op.DIV,
|
|
103
|
-
Op.MUL,
|
|
104
|
-
Op.EXP,
|
|
105
|
-
Op.XOR,
|
|
106
|
-
Op.AND,
|
|
107
|
-
Op.OR,
|
|
108
|
-
Op.SHR,
|
|
109
|
-
Op.SHL,
|
|
110
|
-
Op.BYTE,
|
|
111
|
-
}:
|
|
112
|
-
raws0 = self.stack.pop()
|
|
113
|
-
raws1 = self.stack.pop()
|
|
108
|
+
case Op.EQ:
|
|
109
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, 1 if s0 == s1 else 0))
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
111
|
+
case Op.LT:
|
|
112
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, 1 if s0 < s1 else 0))
|
|
117
113
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
case Op.EQ:
|
|
121
|
-
res = 1 if s0 == s1 else 0
|
|
122
|
-
case Op.LT:
|
|
123
|
-
res = 1 if s0 < s1 else 0
|
|
124
|
-
case Op.GT:
|
|
125
|
-
res = 1 if s0 > s1 else 0
|
|
126
|
-
case Op.SUB:
|
|
127
|
-
res = (s0 - s1) & E256M1
|
|
128
|
-
case Op.ADD:
|
|
129
|
-
res = (s0 + s1) & E256M1
|
|
130
|
-
case Op.DIV:
|
|
131
|
-
res = 0 if s1 == 0 else s0 // s1
|
|
132
|
-
gas_used = 5
|
|
133
|
-
case Op.MUL:
|
|
134
|
-
res = (s0 * s1) & E256M1
|
|
135
|
-
gas_used = 5
|
|
136
|
-
case Op.EXP:
|
|
137
|
-
res = pow(s0, s1, E256)
|
|
138
|
-
gas_used = 50 * (1 + (s1.bit_length() // 8)) # ~approx
|
|
139
|
-
case Op.XOR:
|
|
140
|
-
res = s0 ^ s1
|
|
141
|
-
case Op.AND:
|
|
142
|
-
res = s0 & s1
|
|
143
|
-
case Op.OR:
|
|
144
|
-
res = s0 | s1
|
|
145
|
-
case Op.SHR:
|
|
146
|
-
res = 0 if s0 >= 256 else (s1 >> s0) & E256M1
|
|
147
|
-
case Op.SHL:
|
|
148
|
-
res = 0 if s0 >= 256 else (s1 << s0) & E256M1
|
|
149
|
-
case Op.BYTE:
|
|
150
|
-
res = 0 if s0 >= 32 else raws1.data[s0]
|
|
151
|
-
case _:
|
|
152
|
-
raise Exception(f'BUG: op {op} not handled in match')
|
|
114
|
+
case Op.GT:
|
|
115
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, 1 if s0 > s1 else 0))
|
|
153
116
|
|
|
154
|
-
|
|
155
|
-
return (
|
|
117
|
+
case Op.SUB:
|
|
118
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, (s0 - s1) & E256M1))
|
|
119
|
+
|
|
120
|
+
case Op.ADD:
|
|
121
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, (s0 + s1) & E256M1))
|
|
122
|
+
|
|
123
|
+
case Op.DIV:
|
|
124
|
+
return self._bop(lambda raws0, s0, raws1, s1: (5, 0 if s1 == 0 else s0 // s1))
|
|
125
|
+
|
|
126
|
+
case Op.MOD:
|
|
127
|
+
return self._bop(lambda raws0, s0, raws1, s1: (5, 0 if s1 == 0 else s0 % s1))
|
|
128
|
+
|
|
129
|
+
case Op.MUL:
|
|
130
|
+
return self._bop(lambda raws0, s0, raws1, s1: (5, (s0 * s1) & E256M1))
|
|
131
|
+
|
|
132
|
+
case Op.EXP:
|
|
133
|
+
return self._bop(
|
|
134
|
+
lambda raws0, s0, raws1, s1: (50 * (1 + (s1.bit_length() // 8)), pow(s0, s1, E256))
|
|
135
|
+
) # ~approx gas
|
|
136
|
+
|
|
137
|
+
case Op.XOR:
|
|
138
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, s0 ^ s1))
|
|
139
|
+
|
|
140
|
+
case Op.AND:
|
|
141
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, s0 & s1))
|
|
142
|
+
|
|
143
|
+
case Op.OR:
|
|
144
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, s0 | s1))
|
|
145
|
+
|
|
146
|
+
case Op.SHR:
|
|
147
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, 0 if s0 >= 256 else (s1 >> s0) & E256M1))
|
|
148
|
+
|
|
149
|
+
case Op.SHL:
|
|
150
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, 0 if s0 >= 256 else (s1 << s0) & E256M1))
|
|
151
|
+
|
|
152
|
+
case Op.BYTE:
|
|
153
|
+
return self._bop(lambda raws0, s0, raws1, s1: (3, 0 if s0 >= 32 else raws1.data[s0]))
|
|
156
154
|
|
|
157
155
|
case op if op in {Op.SLT, Op.SGT}:
|
|
158
156
|
raws0 = self.stack.pop()
|
|
@@ -196,6 +194,17 @@ class Vm:
|
|
|
196
194
|
self.stack.swap(op - Op.SWAP1 + 1)
|
|
197
195
|
return (3,)
|
|
198
196
|
|
|
197
|
+
case Op.MSIZE:
|
|
198
|
+
self.stack.push_uint(self.memory.size())
|
|
199
|
+
return (2,)
|
|
200
|
+
|
|
201
|
+
case Op.MSTORE8:
|
|
202
|
+
offset = self.stack.pop_uint()
|
|
203
|
+
value = self.stack.pop()
|
|
204
|
+
el = Element(data=value.data[31:32], label=value.label)
|
|
205
|
+
self.memory.store(offset, el)
|
|
206
|
+
return (3,)
|
|
207
|
+
|
|
199
208
|
case Op.MSTORE:
|
|
200
209
|
offset = self.stack.pop_uint()
|
|
201
210
|
value = self.stack.pop()
|
|
@@ -229,8 +238,8 @@ class Vm:
|
|
|
229
238
|
self.stack.push_uint(res)
|
|
230
239
|
return (5, s0, raws1)
|
|
231
240
|
|
|
232
|
-
case Op.ADDRESS:
|
|
233
|
-
self.stack.push_uint(
|
|
241
|
+
case Op.ADDRESS | Op.ORIGIN | Op.CALLER:
|
|
242
|
+
self.stack.push_uint(0)
|
|
234
243
|
return (2,)
|
|
235
244
|
|
|
236
245
|
case Op.CALLDATACOPY:
|
|
@@ -243,5 +252,45 @@ class Vm:
|
|
|
243
252
|
self.memory.store(mem_off, value)
|
|
244
253
|
return (4,)
|
|
245
254
|
|
|
255
|
+
case Op.SLOAD:
|
|
256
|
+
slot = self.stack.pop()
|
|
257
|
+
self.stack.push_uint(0)
|
|
258
|
+
return (100, slot)
|
|
259
|
+
|
|
260
|
+
case Op.SSTORE:
|
|
261
|
+
slot = self.stack.pop()
|
|
262
|
+
sval = self.stack.pop()
|
|
263
|
+
return (100, slot, sval)
|
|
264
|
+
|
|
265
|
+
case Op.BALANCE:
|
|
266
|
+
self.stack.pop()
|
|
267
|
+
self.stack.push_uint(1)
|
|
268
|
+
return (100,)
|
|
269
|
+
|
|
270
|
+
case Op.SELFBALANCE:
|
|
271
|
+
self.stack.push_uint(1)
|
|
272
|
+
return (5,)
|
|
273
|
+
|
|
274
|
+
case Op.GAS:
|
|
275
|
+
self.stack.push_uint(1_000_000)
|
|
276
|
+
return (2,)
|
|
277
|
+
|
|
278
|
+
case Op.CALL | Op.DELEGATECALL | Op.STATICCALL:
|
|
279
|
+
self.stack.pop()
|
|
280
|
+
p1 = self.stack.pop()
|
|
281
|
+
p2 = self.stack.pop()
|
|
282
|
+
self.stack.pop()
|
|
283
|
+
self.stack.pop()
|
|
284
|
+
self.stack.pop()
|
|
285
|
+
|
|
286
|
+
if op == Op.CALL:
|
|
287
|
+
self.stack.pop()
|
|
288
|
+
|
|
289
|
+
self.stack.push_uint(0) # failure
|
|
290
|
+
|
|
291
|
+
if op == Op.CALL:
|
|
292
|
+
return (100, p1, p2)
|
|
293
|
+
return (100, p1)
|
|
294
|
+
|
|
246
295
|
case _:
|
|
247
296
|
raise UnsupportedOpError(op)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "evmole"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.4"
|
|
4
4
|
description = "Extracts function selectors and arguments from EVM bytecode"
|
|
5
5
|
authors = ["Maxim Andreev <andreevmaxim@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -9,12 +9,12 @@ repository = "https://github.com/cdump/evmole"
|
|
|
9
9
|
|
|
10
10
|
[tool.poetry.dependencies]
|
|
11
11
|
python = "^3.11"
|
|
12
|
-
aiohttp = {version = "^3.9
|
|
12
|
+
aiohttp = {version = "^3.9", optional = true}
|
|
13
13
|
|
|
14
14
|
[tool.poetry.group.dev.dependencies]
|
|
15
|
-
pytest = "^
|
|
16
|
-
mypy = "^1.
|
|
17
|
-
ruff = "^0.
|
|
15
|
+
pytest = "^8.2"
|
|
16
|
+
mypy = "^1.10"
|
|
17
|
+
ruff = "^0.4"
|
|
18
18
|
|
|
19
19
|
[tool.poetry.extras]
|
|
20
20
|
benchmark = ["aiohttp"]
|
|
@@ -24,15 +24,17 @@ requires = ["poetry-core"]
|
|
|
24
24
|
build-backend = "poetry.core.masonry.api"
|
|
25
25
|
|
|
26
26
|
[tool.ruff]
|
|
27
|
-
extend-select = ["B", "Q", "I"]
|
|
28
27
|
line-length = 130
|
|
29
28
|
target-version = "py311"
|
|
30
29
|
|
|
31
|
-
[tool.ruff.
|
|
30
|
+
[tool.ruff.lint]
|
|
31
|
+
extend-select = ["B", "Q", "I"]
|
|
32
|
+
|
|
33
|
+
[tool.ruff.lint.flake8-quotes]
|
|
32
34
|
inline-quotes = "single"
|
|
33
35
|
|
|
34
36
|
[tool.ruff.format]
|
|
35
37
|
quote-style = "single"
|
|
36
38
|
|
|
37
|
-
[tool.ruff.per-file-ignores]
|
|
39
|
+
[tool.ruff.lint.per-file-ignores]
|
|
38
40
|
"__init__.py" = ["F401"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|