UNPKG

sevm

Version:

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

209 lines 8.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.yulStmts = exports.yul = void 0; const _1 = require("."); const abi_1 = require("./abi"); const ast_1 = require("./ast"); /** * Returns the Yul `string` representation of `nodes` that are either * `Expr`essions or `Inst`ructions. * * https://docs.soliditylang.org/en/v0.8.21/yul.html */ function yul(strings, ...nodes) { const result = [strings[0]]; nodes.forEach((node, i) => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions const str = (0, ast_1.isExpr)(node) ? yulExpr(node) : (0, ast_1.isInst)(node) ? yulInst(node) : `${node}`; result.push(str, strings[i + 1]); }); return result.join(''); } exports.yul = yul; function yulExpr(expr) { switch (expr.tag) { case 'Val': return `${expr.isJumpDest() ? '[J]' : ''}0x${expr.val.toString(16)}`; case 'Local': return `local${expr.index}`; case 'Add': case 'Mul': case 'Sub': case 'Div': case 'Mod': case 'Exp': case 'Lt': case 'Gt': case 'Eq': case 'And': case 'Or': case 'Xor': return yul `${expr.tag.toLowerCase()}(${expr.left}, ${expr.right})`; case 'IsZero': case 'Not': return yul `${expr.tag.toLowerCase()}(${expr.value})`; case 'Byte': return yul `byte(${expr.pos}, ${expr.data})`; case 'Shl': case 'Shr': case 'Sar': return yul `${expr.tag.toLowerCase()}(${expr.value}, ${expr.shift})`; case 'Sig': return `eq(msg.sig, ${expr.selector})`; case 'CallValue': return 'callvalue()'; case 'CallDataLoad': return yul `calldataload(${expr.location})`; case 'Prop': switch (expr.symbol) { case 'msg.sender': return 'caller()'; case 'msg.data.length': return 'calldatasize()'; case 'msize()': return expr.symbol; default: { const i = expr.symbol.indexOf('.'); return (i >= -1 ? expr.symbol.substring(i + 1) : expr.symbol) + '()'; } } case 'Fn': return ast_1.FNS[expr.mnemonic][0](yulExpr(expr.value)); case 'DataCopy': switch (expr.kind) { case 'calldatacopy': return yul `calldatacopy(${expr.offset}, ${expr.size})`; case 'codecopy': return yul `codecopy(${expr.offset}, ${expr.size})`; case 'extcodecopy': return yul `extcodecopy(${expr.address}, ${expr.offset}, ${expr.size})`; case 'returndatacopy': return yul `returndatacopy(${expr.offset}, ${expr.size})`; } case 'MLoad': return yul `mload(${expr.location})`; case 'Sha3': return yul `keccak256(${expr.offset}, ${expr.size} /*${expr.args?.map(yulExpr).join('.') ?? 'no args'}*/)`; case 'Create': // create(v, p, n) | F | create new contract with code mem[p…(p+n)) and send v wei and return the new address; returns 0 on error return yul `create(${expr.value}, ${expr.offset}, ${expr.size})`; case 'Call': // call(g, a, v, in, insize, out, outsize) | F | call contract at address a with input mem[in…(in+insize)) providing g gas and v wei and output area mem[out…(out+outsize)) returning 0 on error (eg. out of gas) and 1 on success See more return yul `call(${expr.gas},${expr.address},${expr.value},${expr.argsStart},${expr.argsLen},${expr.retStart},${expr.retLen})`; case 'ReturnData': throw new Error('Not implemented yet: "ReturnData" case'); case 'CallCode': throw new Error('Not implemented yet: "CallCode" case'); case 'Create2': // create2(v, p, n, s) | C | create new contract with code mem[p…(p+n)) at address keccak256(0xff . this . s . keccak256(mem[p…(p+n))) and // send v wei and return the new address, where 0xff is a 1 byte value, // this is the current contract’s address as a 20 byte value and s is a big-endian 256-bit value; returns 0 on error return yul `create2(${expr.value}, ${expr.offset}, ${expr.size})`; case 'StaticCall': // staticcall(g, a, in, insize, out, outsize) return yul `staticcall(${expr.gas}, ${expr.address}, ${expr.memoryStart}, ${expr.memoryLength}, ${expr.outputStart}, ${expr.outputLength})`; case 'DelegateCall': return yul `delegatecall(${expr.gas},${expr.address},${expr.memoryStart},${expr.memoryLength},${expr.outputStart},${expr.outputLength})`; case 'SLoad': return yul `sload(${expr.slot})`; case 'MappingLoad': return yul `sload(${expr.slot}/*base${expr.location}${yulMapArgs(expr)}*/)`; } } function yulInst(inst) { switch (inst.name) { case 'Local': return yul `let local${inst.local.index} := ${inst.local.value} // #refs ${inst.local.nrefs}`; case 'Log': return yul `log${inst.topics.length}(${[ inst.offset, inst.size, ...inst.topics ].map(yulExpr).join(', ')})`; case 'MStore': { const location = inst.location.isVal() ? '' : yul `/*=${inst.location.eval()}*/`; return yul `mstore(${inst.location}${location}, ${inst.data})`; } case 'Stop': return 'stop()'; case 'Return': return yul `return(${inst.offset}, ${inst.size}) // ${inst.args?.map(yulExpr).join('.')}`; case 'Revert': return yul `revert(${inst.offset}, ${inst.size})`; case 'SelfDestruct': return yul `selfdestruct(${inst.address})`; case 'Invalid': return 'invalid()'; case 'Jump': return yul `jump(${inst.offset}, ${inst.destBranch.pc})`; case 'Jumpi': return yul `jumpi(${inst.cond}, ${inst.offset}})`; case 'JumpDest': return `jumpdesp(${inst.fallBranch.pc})`; case 'SigCase': return yul `case when ${inst.condition} goto ${inst.offset} or fall ${inst.fallBranch.pc}`; case 'SStore': return yul `sstore(${inst.slot}, ${inst.data})`; case 'MappingStore': return yul `sstore(${inst.slot}, ${inst.data}) /*${inst.location}${yulMapArgs(inst)}*/`; case 'Throw': return `throw('${inst.reason}');`; } } function yulStmt(stmt) { switch (stmt.name) { case 'If': return yul `(${stmt.condition})`; case 'CallSite': return yul `$${stmt.selector}();`; case 'Require': { const args = stmt.selector !== undefined ? [new ast_1.Val(BigInt('0x' + stmt.selector)), ...stmt.args] : stmt.args; return `require(${[stmt.condition, ...args].map(yulExpr).join(', ')})`; } default: return yulInst(stmt); } } function yulMapArgs(mapping) { return mapping.items.map(e => yul `[${e}]`).join(''); } function yulStmts(stmts, spaces = 0) { let text = ''; for (const stmt of stmts) { if (stmt.name === 'If') { const condition = yulStmt(stmt); text += ' '.repeat(spaces) + 'if ' + condition + ' {\n'; text += yulStmts(stmt.trueBlock, spaces + 4); if (stmt.falseBlock) { text += ' '.repeat(spaces) + '} else {\n'; text += yulStmts(stmt.falseBlock, spaces + 4); } text += ' '.repeat(spaces) + '}\n'; } else { if (stmt.name === 'Local' && stmt.local.nrefs <= 0) { // continue; } if (stmt.name === 'MStore') { // continue; } text += ' '.repeat(spaces) + yulStmt(stmt) + '\n'; } } return text; } exports.yulStmts = yulStmts; _1.Contract.prototype.yul = function () { let text = ''; text += 'object "runtime" {\n'; text += ' code {\n'; text += yulStmts(this.main, 8); text += '\n'; for (const [selector, fn] of Object.entries(this.functions)) { const name = fn.label !== undefined ? (0, abi_1.fnsig)((0, abi_1.parseSig)(fn.label)) : `__$${selector}(/*unknown*/)`; const view = fn.constant ? ' view' : ''; const payable = fn.payable ? ' payable' : ''; text += ' '.repeat(8) + `function ${name} { // public${view}${payable}\n`; text += yulStmts(fn.stmts, 12); text += ' '.repeat(8) + '}\n'; text += '\n'; } text += ' }\n'; text += '}\n'; return text; }; ast_1.Tag.prototype.yul = function () { return yulExpr(this); }; //# sourceMappingURL=yul.js.map