UNPKG

sevm

Version:

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

355 lines 13.5 kB
"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