sevm
Version:
A Symbolic Ethereum Virtual Machine (EVM) bytecode decompiler & analyzer library & CLI
201 lines • 6.97 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EVM = void 0;
const _bytes_1 = require("./.bytes");
const ast_1 = require("./ast");
const state_1 = require("./state");
const step_1 = require("./step");
/**
* https://ethereum.github.io/execution-specs/autoapi/ethereum/index.html
*/
class EVM {
constructor(bytecode,
/**
* The `STEP` function that updates the `State`
* after executing the opcode pointed by `pc`.
*
* Maps `mnemonic` keys of `STEP` to their corresponding `opcode`
* in the byte range, _i.e._, `0-255`.
*
* For elements in the range `0-255` that do not have a corresponding `mnemonic`,
* `INVALID` is used instead.
*/
step) {
this.step = step;
/**
* Reacheable `blocks` found in `this.bytecode`.
*/
this.blocks = new Map();
/**
* Symbolic execution `errors` found during interpretation of `this.bytecode`.
*/
this.errors = [];
/**
*
*/
this._ids = {
_count: 0,
push(elem) {
if (elem.id === undefined)
elem.id = this._count++;
}
};
this.bytecode = (0, _bytes_1.arrayify)(bytecode);
}
/**
* Creates a new `EVM` with the latest defined execution fork.
*/
static new(bytecode) {
return new EVM(bytecode, new step_1.Shanghai());
}
/**
*
*/
chunks() {
let lastPc = 0;
const result = [];
const pcs = [...this.blocks.keys()];
pcs.sort((a, b) => a - b);
for (const pc of pcs) {
const block = this.blocks.get(pc);
if (lastPc !== pc) {
result.push({ pcbegin: lastPc, pcend: pc, content: this.bytecode.subarray(lastPc, pc) });
}
lastPc = block.pcend;
const opcodes = block.opcodes.map(({ opcode, }) => opcode);
result.push({ pcbegin: pc, pcend: block.pcend, content: opcodes, states: block.states });
}
if (lastPc !== this.bytecode.length) {
result.push({ pcbegin: lastPc, pcend: this.bytecode.length, content: this.bytecode.subarray(lastPc) });
}
return result;
}
/**
*
*/
start() {
const state = new state_1.State();
this.run(0, state);
for (const [, branch] of this.step.functionBranches) {
this.run(branch.pc, branch.state);
}
return state;
}
run(pc0, state) {
const branches = [new ast_1.Branch(pc0, state)];
while (branches.length > 0) {
const branch = branches.shift();
const chunk = this.blocks.get(branch.pc);
if (chunk !== undefined && chunk.states.length > 10) {
continue;
}
this.exec(branch.pc, branch.state);
const last = branch.state.last;
for (const next of last.next ? last.next() : []) {
// const s = gc(b, this.chunks);
// if (s === undefined) {
branches.unshift(next);
// const chunk = this.chunks.get(next.pc);
// if (chunk === undefined) {
// this.chunks.set(next.pc, { pcend: -1, states: [branch.state] });
// } else {
// chunk.states.push(branch.state);
// }
// } else {
// next.state = s;
// }
}
// branches.unshift(...last.next());
}
}
exec(pc0, state) {
this._ids.push(state);
if (state.halted)
throw new Error(`State at ${pc0} must be non-halted to be \`exec\``);
const opcodes = [];
let opcode;
for (opcode of this.step.decode(this.bytecode, pc0)) {
const [, halts, mnemonic] = this.step[opcode.opcode];
const entry = { opcode };
opcodes.push(entry);
try {
if (!state.halted) {
this.step[mnemonic](state, opcode, this);
entry.stack = state.stack.clone();
}
}
catch (error) {
if (!(error instanceof state_1.ExecError))
throw error;
const err = new ast_1.Throw(error.message, opcode);
state.halt(err);
this.errors.push({ err, state });
}
if (!halts && this.bytecode[opcode.nextpc] === step_1.JUMPDEST) {
const fallBranch = ast_1.Branch.make(opcode.nextpc, state);
if (!state.halted)
state.halt(new ast_1.JumpDest(fallBranch));
break;
}
if (halts) {
if (!state.halted)
throw Error('asfdasdf 12123');
break;
}
}
if (opcode === undefined)
throw new Error(`Executing block at ${pc0} cannot be empty`);
if (!state.halted) {
const err = new ast_1.Throw(`State must be halted after executing block at ${pc0}..${opcode.pc}`, opcode);
state.halt(err);
this.errors.push({ err, state });
}
const block = this.blocks.get(pc0);
if (block === undefined) {
this.blocks.set(pc0, { pcend: opcode.nextpc, opcodes, states: [state] });
}
else {
block.states.push(state);
}
}
/**
* Returns the `opcode`s present in the **reacheable blocks** of `this` EVM's `bytecode`.
*
* **NOTE**. You must call either the `start`, `run` or `exec` methods first.
* This is to populate the `bytecode`'s reacheable `blocks`.
*/
opcodes() {
if (this.blocks.size === 0)
throw new Error('`blocks` is empty, call `start`, `run` or `exec` first');
return [...this.blocks.values()]
.flatMap(block => block.opcodes.map(o => o.opcode));
}
gc(b) {
const chunk = this.blocks.get(b.pc);
if (chunk !== undefined) {
for (const s of chunk.states) {
if (cmp(b.state, s)) {
return s;
}
}
}
return undefined;
}
}
exports.EVM = EVM;
function cmp({ stack: lhs }, { stack: rhs }) {
const cmpval = (lhs, rhs) => !(lhs.isVal() && lhs.pushStateId) || !(rhs.isVal() && rhs.pushStateId) || lhs.val === rhs.val;
// console.log('????', lhs.values, rhs.values);
if (lhs.values.length !== rhs.values.length) {
// console.log('asdadsdsadsads', lhs.values, rhs.values);
return false;
}
for (let i = 0; i < lhs.values.length; i++) {
if (!cmpval(lhs.values[i], rhs.values[i])) {
// console.log('212112');
return false;
}
}
return true;
}
//# sourceMappingURL=evm.js.map