evm
Version:
An ethereum virtual machine (EVM) bytecode decompiler
188 lines (173 loc) • 6.39 kB
text/typescript
const findOpcode = require('../../node_modules/ethereumjs-vm/dist/opcodes.js');
import * as functionHashes from '../../data/functionHashes.json';
import * as eventHashes from '../../data/eventHashes.json';
import opcodeFunctions from '../utils/opcodes';
import stringifyEvents from '../utils/stringifyEvents';
import stringifyStructs from '../utils/stringifyStructs';
import stringifyMappings from '../utils/stringifyMappings';
import stringifyVariables from '../utils/stringifyVariables';
import stringifyFunctions from '../utils/stringifyFunctions';
import stringifyInstructions from '../utils/stringifyInstructions';
import Opcode from '../interfaces/opcode.interface';
import Stack from './stack.class';
import Memory from '../interfaces/memory.interface';
import Storage from '../interfaces/storage.interface';
import Jumps from '../interfaces/jumps.interface';
class EVM {
pc: number;
stack: Stack;
memory: Memory;
opcodes: Opcode[];
instructions: any;
storage: Storage;
jumps: Jumps;
code: Buffer;
mappings: any;
layer: number;
halted: boolean;
functions: any;
variables: any;
events: any;
gasUsed: number;
constructor(code: string | Buffer) {
this.pc = 0;
this.opcodes = [];
this.instructions = [];
this.stack = new Stack();
this.memory = {};
this.storage = {};
this.jumps = {};
this.mappings = {};
this.layer = 0;
this.halted = false;
this.functions = {};
this.variables = {};
this.events = {};
this.gasUsed = 0;
if (code instanceof Buffer) {
this.code = code;
} else {
this.code = Buffer.from(code.replace('0x', ''), 'hex');
}
}
clone(): EVM {
const clone = new EVM(this.code);
clone.pc = this.pc;
clone.opcodes = this.opcodes;
clone.stack = this.stack.clone();
clone.memory = { ...this.memory };
clone.storage = this.storage;
clone.jumps = { ...this.jumps };
clone.mappings = this.mappings;
clone.layer = this.layer + 1;
clone.functions = this.functions;
clone.variables = this.variables;
clone.events = this.events;
clone.gasUsed = this.gasUsed;
return clone;
}
getBytecode(): string {
return '0x' + this.code.toString('hex');
}
getOpcodes(): Opcode[] {
if (this.opcodes.length === 0) {
for (let index = 0; index < this.code.length; index++) {
const currentOp = findOpcode(this.code[index], true);
currentOp.pc = index;
this.opcodes.push(currentOp);
if (currentOp.name.startsWith('PUSH')) {
const pushDataLength = this.code[index] - 0x5f;
const pushData = this.code.slice(index + 1, index + pushDataLength + 1);
currentOp.pushData = pushData;
index += pushDataLength;
}
}
}
return this.opcodes;
}
getFunctions(): string[] {
return [
...new Set(
this.getOpcodes()
.filter(opcode => opcode.name === 'PUSH4')
.map(opcode => (opcode.pushData ? opcode.pushData.toString('hex') : ''))
.filter(hash => hash in functionHashes)
.map(hash => (functionHashes as any)[hash])
)
];
}
getEvents(): string[] {
return [
...new Set(
this.getOpcodes()
.filter(opcode => opcode.name === 'PUSH32')
.map(opcode => (opcode.pushData ? opcode.pushData.toString('hex') : ''))
.filter(hash => hash in eventHashes)
.map(hash => (eventHashes as any)[hash])
)
];
}
getJumpDestinations(): number[] {
return this.getOpcodes()
.filter(opcode => opcode.name === 'JUMPDEST')
.map(opcode => opcode.pc);
}
getTotalGas(): number {
return this.getOpcodes()
.map(opcode => opcode.fee)
.reduce((a: number, b: number) => a + b);
}
getSwarmHash(): string | boolean {
const regex = /a165627a7a72305820([a-f0-9]{64})0029$/;
const bytecode = this.getBytecode();
const match = bytecode.match(regex);
if (match && match[1]) {
return 'bzzr://' + match[1];
} else {
return false;
}
}
reset(): void {
this.pc = 0;
this.instructions = [];
this.stack.reset();
this.memory = {};
this.storage = {};
this.jumps = {};
this.mappings = {};
this.functions = {};
this.variables = {};
this.events = {};
this.gasUsed = 0;
}
parse(): any[] {
if (this.instructions.length === 0) {
const opcodes = this.getOpcodes();
for (this.pc; this.pc < opcodes.length && !this.halted; this.pc++) {
const opcode = opcodes[this.pc];
this.gasUsed += opcode.fee;
if (!(opcode.name in opcodeFunctions)) {
throw new Error('Unknown OPCODE: ' + opcode.name);
} else {
(opcodeFunctions as any)[opcode.name](opcode, this);
}
}
}
return this.instructions;
}
decompile(): string {
const instructionTree = this.parse();
const events = stringifyEvents(this.events, this.getEvents());
const structs = stringifyStructs(this.mappings);
const mappings = stringifyMappings(this.mappings);
const variables = stringifyVariables(this.variables);
const functions = Object.keys(this.functions)
.map((functionName: string) =>
stringifyFunctions(functionName, this.functions[functionName])
)
.join('');
const code = stringifyInstructions(instructionTree);
return events + structs + mappings + variables + functions + code;
}
}
export default EVM;