Skip to content

Commit

Permalink
add new contract_info API, add experimental storage layout extractor
Browse files Browse the repository at this point in the history
  • Loading branch information
cdump committed Dec 4, 2024
1 parent 647536d commit 287b79c
Show file tree
Hide file tree
Showing 39 changed files with 3,892 additions and 657 deletions.
42 changes: 32 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ exclude = ["/javascript", "/python", "/benchmark", "/.github"]
alloy-primitives = "0.8"
alloy-dyn-abi = "0.8"

pyo3 = { version = "0.22.2", features = ["extension-module"], optional = true }
pyo3 = { version = "0.23.2", features = ["extension-module"], optional = true }
wasm-bindgen = { version = "0.2", optional = true }
serde-wasm-bindgen = { version = "0.6.5", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }

[features]
python = ["dep:pyo3"]
javascript = ["dep:wasm-bindgen"]
javascript = ["dep:wasm-bindgen", "dep:serde-wasm-bindgen", "dep:serde"]

# for dev
trace_selectors = []
trace_arguments = []
trace_mutability = []
trace = ["trace_selectors", "trace_arguments", "trace_mutability"]
trace_storage = []
trace = ["trace_selectors", "trace_arguments", "trace_mutability", "trace_storage"]

[lib]
crate-type = ["cdylib", "lib"]
Expand Down
76 changes: 61 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# EVMole

[![try it online](https://img.shields.io/badge/Try_It_Online-github.io-brightgreen)](https://cdump.github.io/evmole/)
[![try it online](https://img.shields.io/badge/Try_It_Online-evmole.xyz-brightgreen)](https://evmole.xyz/)
[![npm](https://img.shields.io/npm/v/evmole)](https://www.npmjs.com/package/evmole)
[![Crates.io](https://img.shields.io/crates/v/evmole?color=e9b44f)](https://crates.io/crates/evmole)
[![PyPI](https://img.shields.io/pypi/v/evmole?color=006dad)](https://pypi.org/project/evmole)

EVMole is a powerful library that extracts information from Ethereum Virtual Machine (EVM) bytecode, including [function selectors](https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector), arguments, and [state mutability](https://docs.soliditylang.org/en/latest/contracts.html#state-mutability), even for unverified contracts.
EVMole is a powerful library that extracts information from Ethereum Virtual Machine (EVM) bytecode, including [function selectors](https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector), arguments, [state mutability](https://docs.soliditylang.org/en/latest/contracts.html#state-mutability), and storage layout, even for unverified contracts.


## Key Features
Expand All @@ -24,26 +24,59 @@ EVMole is a powerful library that extracts information from Ethereum Virtual Mac
$ npm i evmole
```
```javascript
import { functionSelectors, functionArguments, functionStateMutability } from 'evmole'
import { contractInfo } from 'evmole'

const code = '0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632125b65b146034578063b69ef8a8146044575b5f80fd5b6044603f3660046046565b505050565b005b5f805f606084860312156057575f80fd5b833563ffffffff811681146069575f80fd5b925060208401356001600160a01b03811681146083575f80fd5b915060408401356001600160e01b0381168114609d575f80fd5b80915050925092509256'

console.log(functionSelectors(code)); // [ '2125b65b', 'b69ef8a8' ]
console.log(functionArguments(code, '2125b65b')); // 'uint32,address,uint224'
console.log(functionStateMutability(code, '2125b65b')); // 'pure'
console.log( contractInfo(code, {selectors:true, arguments:true, stateMutability:true}) )
// {
// functions: [
// {
// selector: '2125b65b',
// bytecodeOffset: 52,
// arguments: 'uint32,address,uint224',
// stateMutability: 'pure'
// },
// {
// selector: 'b69ef8a8',
// bytecodeOffset: 68,
// arguments: '',
// stateMutability: 'pure'
// }
// ],
// storage: undefined
// }
```

### Rust
Documentation is available on [docs.rs](https://docs.rs/evmole/latest/evmole/)
```rust
let code = hex::decode("6080604052348015600e575f80fd5b50600436106030575f3560e01c80632125b65b146034578063b69ef8a8146044575b5f80fd5b6044603f3660046046565b505050565b005b5f805f606084860312156057575f80fd5b833563ffffffff811681146069575f80fd5b925060208401356001600160a01b03811681146083575f80fd5b915060408401356001600160e01b0381168114609d575f80fd5b80915050925092509256").unwrap();

println!("{:x?} | {} | {:?}",
evmole::function_selectors(&code, 0),
evmole::function_arguments(&code, &[0x21, 0x25, 0xb6, 0x5b], 0),
evmole::function_state_mutability(&code, &[0x21, 0x25, 0xb6, 0x5b], 0),
println!("{:?}", evmole::contract_info(
evmole::ContractInfoArgs::new(&code)
.with_selectors()
.with_arguments()
.with_state_mutability()
)
);
// [[21, 25, b6, 5b], [b6, 9e, f8, a8]] | uint32,address,uint224 | Pure
// Contract {
// functions: Some([
// Function {
// selector: [33, 37, 182, 91],
// bytecode_offset: 52,
// arguments: Some([Uint(32), Address, Uint(224)]),
// state_mutability: Some(Pure)
// },
// Function {
// selector: [182, 158, 248, 168],
// bytecode_offset: 68,
// arguments: Some([]),
// state_mutability: Some(Pure)
// }
// ]),
// storage: None
// }
```

### Python
Expand All @@ -52,13 +85,26 @@ println!("{:x?} | {} | {:?}",
$ pip install evmole --upgrade
```
```python
from evmole import function_selectors, function_arguments, function_state_mutability
from evmole import contract_info

code = '0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632125b65b146034578063b69ef8a8146044575b5f80fd5b6044603f3660046046565b505050565b005b5f805f606084860312156057575f80fd5b833563ffffffff811681146069575f80fd5b925060208401356001600160a01b03811681146083575f80fd5b915060408401356001600160e01b0381168114609d575f80fd5b80915050925092509256'

print(function_selectors(code)) # ['2125b65b', 'b69ef8a8']
print(function_arguments(code, '2125b65b')) # uint32,address,uint224
print(function_state_mutability(code, '2125b65b')) # pure
print( contract_info(code, selectors=True, arguments=True, state_mutability=True) )
# Contract(
# functions=[
# Function(
# selector=2125b65b,
# bytecode_offset=52,
# arguments=uint32,address,uint224,
# state_mutability=pure),
# Function(
# selector=b69ef8a8,
# bytecode_offset=68,
# arguments=,
# state_mutability=pure)
# ],
# storage=None
# )
```

### Foundry
Expand Down
18 changes: 12 additions & 6 deletions benchmark/providers/evmole-js/main.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readdirSync, readFileSync, writeFileSync } from 'fs'
import { hrtime } from 'process'

import { functionArguments, functionSelectors, functionStateMutability } from 'evmole'
import { contractInfo } from 'evmole'

const argv = process.argv;
if (argv.length < 5) {
Expand All @@ -24,14 +24,20 @@ function timeit(fn) {

function extract(code, mode, fname) {
if (mode === 'selectors') {
let [duration_ms, r] = timeit(() => functionSelectors(code));
let [duration_ms, r] = timeit(() => contractInfo(code, {selectors: true}));
return [duration_ms, r];
} else if (mode === 'arguments') {
let [duration_ms, r] = timeit(() => selectors[fname][1].map((s) => [s, functionArguments(code, s)]));
return [duration_ms, Object.fromEntries(r)];
let [duration_ms, r] = timeit(() => contractInfo(code, {arguments: true}));
const by_sel = new Map(r.functions.map((f) => [f.selector, f.arguments]));
return [duration_ms, Object.fromEntries(
selectors[fname][1].map((s) => [s, by_sel.get(s) ?? 'notfound'])
)];
} else if (mode === 'mutability') {
let [duration_ms, r] = timeit(() => selectors[fname][1].map((s) => [s, functionStateMutability(code, s)]));
return [duration_ms, Object.fromEntries(r)];
let [duration_ms, r] = timeit(() => contractInfo(code, {stateMutability: true}));
const by_sel = new Map(r.functions.map((f) => [f.selector, f.stateMutability]));
return [duration_ms, Object.fromEntries(
selectors[fname][1].map((s) => [s, by_sel.get(s) ?? 'notfound'])
)];
} else {
throw 'unsupported mode';
}
Expand Down
22 changes: 16 additions & 6 deletions benchmark/providers/evmole-py/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import time

from evmole import function_arguments, function_selectors, function_state_mutability
from evmole import contract_info

parser = argparse.ArgumentParser()
parser.add_argument('mode', choices=['selectors', 'arguments', 'mutability'])
Expand All @@ -24,16 +24,26 @@
code = d['code']
t0 = time.perf_counter()
if cfg.mode == 'selectors':
r = function_selectors(code)
r = contract_info(code, selectors=True)
elif cfg.mode == 'arguments':
fsel = selectors[fname][1]
r = {s: function_arguments(code, s) for s in fsel}
r = contract_info(code, arguments=True)
elif cfg.mode == 'mutability':
fsel = selectors[fname][1]
r = {s: function_state_mutability(code, s) for s in fsel}
r = contract_info(code, state_mutability=True)
else:
raise Exception(f'Unknown mode {cfg.mode}')
duration_ms = int((time.perf_counter() - t0) * 1000)

if cfg.mode == 'selectors':
r = [f.selector for f in r.functions]
elif cfg.mode == 'arguments':
by_sel = {f.selector: f.arguments for f in r.functions}
r = {s: by_sel.get(s, 'notfound') for s in selectors[fname][1]}
elif cfg.mode == 'mutability':
by_sel = {f.selector: f.state_mutability for f in r.functions}
r = {s: by_sel.get(s, 'notfound') for s in selectors[fname][1]}
else:
raise Exception(f'Unknown mode {cfg.mode}')

ret[fname] = [duration_ms, r]

with open(cfg.output_file, 'w') as fh:
Expand Down
Loading

0 comments on commit 287b79c

Please sign in to comment.