UNPKG

sevm

Version:

A Symbolic Ethereum Virtual Machine (EVM) bytecode decompiler & analyzer library & CLI

201 lines 6.97 kB
"use strict"; 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