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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: evmole
3
- Version: 0.3.2
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.0,<4.0.0) ; extra == "benchmark"
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.6s · 1.4s · 1.5s</td>
133
- <td>2.6s</td>
151
+ <td>0.7s · 1.5s · 2.0s</td>
152
+ <td>3.0s</td>
134
153
  <td>0.7s</td>
135
- <td>598.6s</td>
136
- <td>1.4s</td>
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>7.3s · 17.4s · 25.7s</td>
173
- <td>46.8s</td>
174
- <td>10.2s</td>
175
- <td>33.9s</td>
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="7"></td></tr>
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.5s · 0.8s · 1.0s</td>
214
- <td>1.9s</td>
215
- <td>0.5s</td>
216
- <td>15.5s</td>
217
- <td>1.0s</td>
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%, 3664 🥇</td>
236
- <td>42.6%, 10414</td>
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>0.8s · 6.0s · 9.6s</td>
242
- <td>602.9s</td>
243
- <td>0.6s</td>
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.4%, 63124 🥇</td>
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>17.2s · 179.2s · 300.9s</td>
277
+ <td>22.6s · 247.0s · 584.7s</td>
256
278
  <!-- -->
257
- <td>8.1s</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>52.4%, 11123 🥇</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.8s · 7.0s · 12.9s</td>
270
- <td>15.6s</td>
271
- <td>0.6s</td>
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.1; whatsabi v0.10.0; evm-hound-rs v0.1.4; heimdall-rs v0.7.3</i>
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.6s · 1.4s · 1.5s</td>
115
- <td>2.6s</td>
133
+ <td>0.7s · 1.5s · 2.0s</td>
134
+ <td>3.0s</td>
116
135
  <td>0.7s</td>
117
- <td>598.6s</td>
118
- <td>1.4s</td>
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>7.3s · 17.4s · 25.7s</td>
155
- <td>46.8s</td>
156
- <td>10.2s</td>
157
- <td>33.9s</td>
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="7"></td></tr>
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.5s · 0.8s · 1.0s</td>
196
- <td>1.9s</td>
197
- <td>0.5s</td>
198
- <td>15.5s</td>
199
- <td>1.0s</td>
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%, 3664 🥇</td>
218
- <td>42.6%, 10414</td>
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>0.8s · 6.0s · 9.6s</td>
224
- <td>602.9s</td>
225
- <td>0.6s</td>
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.4%, 63124 🥇</td>
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>17.2s · 179.2s · 300.9s</td>
259
+ <td>22.6s · 247.0s · 584.7s</td>
238
260
  <!-- -->
239
- <td>8.1s</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>52.4%, 11123 🥇</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.8s · 7.0s · 12.9s</td>
252
- <td>15.6s</td>
253
- <td>0.6s</td>
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.1; whatsabi v0.10.0; evm-hound-rs v0.1.4; heimdall-rs v0.7.3</i>
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: dict[int, str] = {}
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(8192)
86
+ vm.stack.push_uint(131072)
66
87
 
67
88
  case (Op.CALLDATALOAD, _, Element(Arg() as arg)):
68
- args[arg.offset] = 'bytes'
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.peek().label = Arg(offset=arg.offset, dynamic=True)
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 < 2**32:
78
- vm.stack.peek().label = Arg(offset=off)
79
- args[off] = ''
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[arg.offset] = 'uint256[]'
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[arg.offset] = 'uint256[]'
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[arg.offset] = f'{t}[]' if arg.dynamic else t
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[arg.offset] = f'{t}[]' if arg.dynamic else t
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
- args[arg.offset] = 'bool[]' if arg.dynamic else 'bool'
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[arg.offset] = f'{t}[]' if arg.dynamic else t
164
+ args.set(arg.offset, f'{t}[]' if arg.dynamic else t)
129
165
 
130
166
  case (Op.BYTE, _, _, Element(Arg() as arg)):
131
- if args[arg.offset] == '':
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 ','.join(v[1] if v[1] != '' else 'uint256' for v in sorted(args.items()))
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 2 stack pop()s
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 op if op in {
97
- Op.EQ,
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
- s0 = int.from_bytes(raws0.data, 'big', signed=False)
116
- s1 = int.from_bytes(raws1.data, 'big', signed=False)
111
+ case Op.LT:
112
+ return self._bop(lambda raws0, s0, raws1, s1: (3, 1 if s0 < s1 else 0))
117
113
 
118
- gas_used = 3
119
- match op:
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
- self.stack.push_uint(res)
155
- return (gas_used, raws0, raws1)
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(1)
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.2"
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.0", optional = true}
12
+ aiohttp = {version = "^3.9", optional = true}
13
13
 
14
14
  [tool.poetry.group.dev.dependencies]
15
- pytest = "^7.4.3"
16
- mypy = "^1.7.1"
17
- ruff = "^0.1.6"
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.flake8-quotes]
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