sevm
Version:
A Symbolic Ethereum Virtual Machine (EVM) bytecode decompiler & analyzer library & CLI
355 lines • 13.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.reduce0 = exports.build = exports.PublicFunction = exports.isRevertBlock = exports.Contract = exports.ERCIds = void 0;
const ast_1 = require("./ast");
const alu_1 = require("./ast/alu");
const storage_1 = require("./ast/storage");
const _bytes_1 = require("./.bytes");
const ercs_1 = __importDefault(require("./ercs"));
const evm_1 = require("./evm");
const metadata_1 = require("./metadata");
const state_1 = require("./state");
const step_1 = require("./step");
/**
*
*/
exports.ERCIds = Object.keys(ercs_1.default);
/**
*
*/
class Contract {
/**
*
* @param bytecode the bytecode to analyze in hexadecimal format.
*/
constructor(bytecode, step = new step_1.Shanghai(), main = new state_1.State()) {
this.events = {};
this.variables = new Map();
this.mappings = {};
this.functionBranches = new Map();
this.reverts = {};
/**
*
*/
this.functions = {};
this.bytecode = (0, _bytes_1.arrayify)(bytecode);
const evm = new evm_1.EVM(this.bytecode, step);
evm.run(0, main);
this.main = build(main);
this.payable = !requiresNoValue(this.main, true);
for (const [selector, branch] of evm.step.functionBranches) {
evm.run(branch.pc, branch.state);
this.functions[selector] = new PublicFunction(this, build(branch.state), selector);
}
this.events = evm.step.events;
this.variables = evm.step.variables;
this.mappings = evm.step.mappings;
this.functionBranches = evm.step.functionBranches;
this.reverts = evm.step.reverts;
this.metadata = (0, metadata_1.splitMetadataHash)(this.bytecode).metadata;
this.errors = evm.errors;
this.blocks = evm.blocks;
this.chunks = () => evm.chunks();
this.opcodes = () => evm.opcodes();
}
reduce() {
const obj = Object.create(Contract.prototype);
const contract = Object.assign(obj, this);
contract.main = (0, ast_1.reduce)(this.main);
contract.payable = !requiresNoValue(contract.main);
contract.functions = {};
for (const [selector, fn] of Object.entries(this.functions)) {
const newfn = new PublicFunction(contract, (0, ast_1.reduce)(fn.stmts), selector, fn.payable);
newfn.label = fn.label;
contract.functions[selector] = newfn;
}
return contract;
}
/**
*
* @returns
*/
getFunctions() {
return Object.values(this.functions).flatMap(fn => fn.label === undefined ? [] : [fn.label]);
}
/**
*
* @returns
*/
getEvents() {
return Object.values(this.events).flatMap(event => event.sig === undefined ? [] : [event.sig]);
}
// getABI() {
// return Object.values(this.contract).map(fn => {
// return {
// type: 'function',
// name: fn.label.split('(')[0],
// payable: fn.payable,
// constant: fn.constant,
// };
// });
// }
/**
* https://eips.ethereum.org/EIPS/eip-165
* https://eips.ethereum.org/EIPS/eip-20
* https://eips.ethereum.org/EIPS/eip-20
* https://eips.ethereum.org/EIPS/eip-721
*
* @param ercid
* @returns
*/
isERC(ercid, checkEvents = true) {
return (ercs_1.default[ercid].selectors.every(s => this.functionBranches.has(s)) &&
(!checkEvents || ercs_1.default[ercid].topics.every(t => t in this.events)));
}
}
exports.Contract = Contract;
function isRevertBlock(falseBlock) {
return (falseBlock.length >= 1 &&
falseBlock.slice(0, -1).every(stmt => stmt.name === 'Local' || stmt.name === 'MStore') &&
falseBlock.at(-1).name === 'Revert' &&
falseBlock.at(-1).isRequireOrAssert());
}
exports.isRevertBlock = isRevertBlock;
class PublicFunction {
constructor(contract, stmts, selector, payable) {
this.contract = contract;
this.stmts = stmts;
this.selector = selector;
/**
*
*/
this._label = undefined;
this.returns = [];
this.visibility = 'public';
if (payable === undefined) {
if (requiresNoValue(this.stmts)) {
this.payable = false;
// this.stmts.shift();
}
else if (!contract.payable) {
this.payable = false;
}
else {
this.payable = true;
}
}
else {
this.payable = payable;
}
this.constant = this.stmts.length === 1 && this.stmts[0].name === 'Return';
const returns = [];
PublicFunction.findReturns(stmts, returns);
if (returns.length > 0 &&
returns.every(args => args.length === returns[0].length &&
args.map(arg => arg.type).join('') === returns[0].map(arg => arg.type).join(''))) {
returns[0].forEach(arg => {
if (arg.isVal()) {
this.returns.push('uint256');
}
else if (arg.type) {
this.returns.push(arg.type);
}
else {
this.returns.push('unknown');
}
});
}
else if (returns.length > 0) {
this.returns.push('<unknown>');
}
}
get label() {
return this._label;
}
set label(value) {
this._label = value;
if (value !== undefined) {
const functionName = value.split('(')[0];
if (this.isGetter()) {
const ret = this.stmts.at(-1);
const location = ret.args[0].slot.val;
const variable = this.contract.variables.get(location);
if (variable !== undefined) {
variable.label = functionName;
}
else {
this.contract.variables.set(location, new storage_1.Variable(functionName, [], this.contract.variables.size + 1));
}
// this.contract.variables[this.selector] = new Variable(
// functionName,
// variable ? variable.types : []
// );
}
if (this.isMappingGetter()) {
const ret = this.stmts.at(-1);
const location = ret.args[0].location;
this.contract.mappings[location].name = functionName;
}
const paramTypes = value.replace(functionName, '').slice(1, -1).split(',');
if (paramTypes.length > 1 || (paramTypes.length === 1 && paramTypes[0] !== '')) {
this.stmts.forEach(stmt => PublicFunction.patchCallDataLoad(stmt, paramTypes));
}
}
}
isGetter() {
const exit = this.stmts.at(-1);
return (this.stmts.length >= 1 &&
this.stmts.slice(0, -1).every(stmt => stmt.name === 'Local' || stmt.name === 'MStore' || stmt.name === 'Require') &&
exit.name === 'Return' &&
exit.args !== undefined &&
exit.args.length === 1 &&
exit.args[0].tag === 'SLoad' &&
exit.args[0].slot.isVal());
}
isMappingGetter() {
const exit = this.stmts.find(stmt => stmt.name !== 'Local' && stmt.name !== 'MStore' && stmt.name !== 'Require');
return (exit !== undefined &&
this.stmts.at(-1) === exit &&
exit.name === 'Return' &&
exit.args !== undefined &&
exit.args.every(arg => arg.tag === 'MappingLoad'));
}
static findReturns(stmts, returns) {
for (const stmt of stmts) {
if (stmt.name === 'Return' && stmt.args && stmt.args.length > 0) {
returns.push(stmt.args);
}
else if (stmt.name === 'If') {
[stmt.trueBlock, stmt.falseBlock].forEach(stmts => {
if (stmts !== undefined) {
PublicFunction.findReturns(stmts, returns);
}
});
}
}
}
static patchCallDataLoad(stmtOrExpr, paramTypes, visited = new Set()) {
if (stmtOrExpr === null || visited.has(stmtOrExpr))
return;
visited.add(stmtOrExpr);
for (const propKey in stmtOrExpr) {
if (propKey === 'mappings')
continue;
if (Object.prototype.hasOwnProperty.call(stmtOrExpr, propKey)) {
const expr = stmtOrExpr[propKey];
if (expr && expr.tag === 'CallDataLoad' && expr.location.isVal()) {
const argNumber = Number((expr.location.val - 4n) / 32n);
expr.type = paramTypes[argNumber];
}
if (typeof expr === 'object' &&
!(expr instanceof storage_1.Variable) &&
!(expr instanceof ast_1.Throw)) {
PublicFunction.patchCallDataLoad(expr, paramTypes, visited);
}
}
}
}
}
exports.PublicFunction = PublicFunction;
function build(state) {
const visited = new WeakSet();
const res = buildState(state);
// mem(res);
return res;
function buildState(state) {
if (visited.has(state)) {
return [];
}
visited.add(state);
const last = state.last;
if (last === undefined)
return [];
for (let i = 0; i < state.stmts.length; i++) {
// state.stmts[i] = state.stmts[i].eval();
}
switch (last.name) {
case 'Jumpi': {
if (last.evalCond.isVal()) {
if (last.evalCond.val === 0n) {
return [...state.stmts.slice(0, -1), ...buildState(last.fallBranch.state)];
}
else {
return [...state.stmts.slice(0, -1), ...buildState(last.destBranch.state)];
}
}
const trueBlock = buildState(last.destBranch.state);
const falseBlock = buildState(last.fallBranch.state);
return [
...state.stmts.slice(0, -1),
...(isRevertBlock(falseBlock)
? [
new ast_1.Require(
// last.cond.eval(),
last.cond,
// ((falseBlock.at(-1) as Revert).args ?? []).map(e => e.eval())
falseBlock.at(-1).selector, falseBlock.at(-1).args ?? []),
...trueBlock,
]
: [new ast_1.If(new alu_1.IsZero(last.cond), falseBlock), ...trueBlock]),
];
}
case 'SigCase': {
const falseBlock = buildState(last.fallBranch.state);
return [
...state.stmts.slice(0, -1),
new ast_1.If(last.condition, [new ast_1.CallSite(last.condition.selector)], falseBlock),
];
}
case 'Jump':
return [...state.stmts.slice(0, -1), ...buildState(last.destBranch.state)];
case 'JumpDest':
return [...state.stmts.slice(0, -1), ...buildState(last.fallBranch.state)];
default:
return state.stmts;
}
}
}
exports.build = build;
function reduce0(stmts) {
const result = [];
for (const stmt of stmts) {
if (stmt.name !== 'Local' || stmt.local.nrefs > 0) {
result.push(stmt);
}
}
return result;
}
exports.reduce0 = reduce0;
/**
*
* @param stmts
* @param allowMStoreInit
* @returns
*/
function requiresNoValue(stmts, allowMStoreInit = false) {
stmts = allowMStoreInit && stmts[0] instanceof ast_1.MStore ? stmts.slice(1) : stmts;
const first = stmts.find(stmt => stmt.name !== 'Local');
return first instanceof ast_1.Require && (first => first.condition.tag === 'IsZero' &&
first.condition.value.tag === 'CallValue')(first.eval());
}
__exportStar(require("./abi"), exports);
__exportStar(require("./evm"), exports);
__exportStar(require("./metadata"), exports);
__exportStar(require("./sol"), exports);
__exportStar(require("./state"), exports);
__exportStar(require("./step"), exports);
__exportStar(require("./yul"), exports);
//# sourceMappingURL=index.js.map