sevm
Version:
A Symbolic Ethereum Virtual Machine (EVM) bytecode decompiler & analyzer library & CLI
901 lines • 37.2 kB
JavaScript
;
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Shanghai = exports.Paris = exports.London = exports.JUMPDEST = exports.Undef = exports.Members = exports.Opcode = void 0;
const _bytes_1 = require("./.bytes");
const state_1 = require("./state");
const ast = __importStar(require("./ast"));
const ast_1 = require("./ast");
/**
* Represents an opcode found in the bytecode augmented with
* offset and operand information as defined by the EVM.
*
* It can be either a unary opcode, _which does not take any operand data_,
* or either a `PUSHn` mnemonic augmented with its push `data`.
* That is, all but `PUSHn` `n >= 1` opcodes are unary opcodes.
*
* `PUSHn` `n >= 1` opcodes takes an `n`-byte argument from the bytecode.
* Note that `PUSH0`[^1] does not take any data argument from the bytecode (just pushes `0` onto the `Stack`).
* Thus it can be considered as an unary opcode.
*
* [^1]: https://eips.ethereum.org/EIPS/eip-3855
*/
class Opcode {
constructor(
/**
* This is the offset in the bytecode where this `Opcode` was found.
* Both jump instructions, _i.e._, `JUMP` and `JUMPI`,
* expects a stack operand referencing this `offset` in the bytecode.
*/
/**
* The Program Counter of this `Opcode`.
* The index in the `Opcode[]` where this `Opcode` is inserted.
*/
pc,
/**
* Any byte number, _i.e._, between 0 and 255 representing the opcode byte.
* The `opcode` may not be a valid opcode.
*/
opcode,
/**
* Represents a valid opcode.
*
* In https://www.evm.codes/ you can find an overview of each EVM opcode.
*
* If the `opcode` given is not a valid opcode,
* you can provide `INVALID` as `mnemonic`.
*
* A `PUSHn` opcode only permits a `PUSHn` opcode.
*/
mnemonic,
/**
* A `Unary` opcode does not include any `data`. For these opcodes `data` is `null`.
*
* If this `Opcode` is a `PUSHn` instruction or contains any operand data,
* then it contains the data attached to this instruction.
*/
data = null) {
this.pc = pc;
this.opcode = opcode;
this.mnemonic = mnemonic;
this.data = data;
}
/**
* Where the next opcode should be located at.
*/
get nextpc() {
return this.pc + (this.data?.length ?? 0) + 1;
}
/**
* Returns the hexadecimal representation of `this.data`.
*/
hexData() {
return this.data === null ? undefined : (0, _bytes_1.hexlify)(this.data);
}
/**
* Returns a `string` representation of `this` `Opcode`.
* Usually used for debugging purposes.
*
* @param includeDataAsNumeric whether to include `data` as numeric.
* @returns the `string` representation of `this` `Opcode`.
*/
format(includeDataAsNumeric = true) {
const pushData = this.data
? ` 0x${this.hexData()}` + (includeDataAsNumeric
? ` (${parseInt(this.hexData(), 16)})`
: '')
: '';
return `${this.mnemonic}(0x${this.opcode.toString(16)})@${this.pc}${pushData}`;
}
}
exports.Opcode = Opcode;
/**
*
*/
class Members {
constructor() {
this.events = {};
this.variables = new Map();
this.mappings = {};
/**
* Store selectors', _i.e._, public and external `function`s program counter entry.
*/
this.functionBranches = new Map();
/**
*
*/
this.reverts = {};
}
getError(selector) {
let error = this.reverts[selector];
if (error === undefined) {
error = {};
this.reverts[selector] = error;
}
return error;
}
}
exports.Members = Members;
/**
* This module is used to `decode` bytecode into `Opcode`.
*
* Maps numeric opcodes (byte between `0` and `255`) to decode configuration and string mnemonic.
* That is, for the given `opcode`,
* `size` indicates the size of the `opcode`'s operand to consume from bytecode in bytes.
* `halts` indicates where the step associated with this `opcode` should `halt` the EVM `State`.
* `mnemonic` indicates the step the `EVM` should execute.
*/
class Undef extends Members {
constructor() {
super();
this.UNDEF = (state, op) => state.halt(new ast.Invalid(op.opcode));
Object.assign(this, Object.fromEntries([...Array(256).keys()].map(k => [k, [0, true, 'UNDEF']])));
}
/**
* Retrieves the `mnemonic` of the steps which `halts` the EVM `State`.
*/
haltingSteps() {
return [...Array(256).keys()]
.map(o => this[o])
.filter(([, halts, mnemonic]) => halts && mnemonic !== 'UNDEF')
.map(([, , mnemonic]) => mnemonic);
}
/**
* Retrieves the opcodes by mnemonic.
*/
opcodes() {
return Object.fromEntries([...Array(256).keys()]
.map(o => [this[o][2], o])
.filter(([mnemonic,]) => mnemonic !== 'UNDEF'));
}
/**
* Decodes the input `bytecode` into `Opcode`s.
* `bytecode` may be a hexadecimal string,
* which may or may not begin with the hex prefix `0x`.
*
* ### Example
*
* ```typescript
* const opcodes = [...this.decode('0x6003600501')];
* ```
*
* @param bytecode hexadecimal string or array of numbers containing the bytecode to decode.
* @param begin the byte position where to start decoding the `input` bytecode,
* defaults to `0` if not provided.
* @returns a generator of the decoded `Opcode`s found in `bytecode`.
*/
*decode(bytecode, begin = 0) {
const buffer = (0, _bytes_1.arrayify)(bytecode);
for (let pc = begin; pc < buffer.length; pc++) {
const opcode = buffer[pc];
const [size, , mnemonic] = this[opcode];
yield new Opcode(pc, opcode, mnemonic, size === 0 ? null : function () {
const data = buffer.subarray(pc + 1, pc + size + 1);
if (data.length !== size) {
const op = new Opcode(pc, opcode, mnemonic, data).format(false);
throw new Error(`Trying to get \`${size}\` bytes but got only \`${data.length}\` while decoding \`${op}\` before reaching the end of bytecode`);
}
pc += size;
return data;
}());
}
}
}
exports.Undef = Undef;
const ALU = {
ADDMOD: [0x08, ({ stack }) => {
const left = stack.pop();
const right = stack.pop();
const mod = stack.pop();
stack.push(left.isVal() && right.isVal() && mod.isVal()
? new ast_1.Val((left.val + right.val) % mod.val)
: left.isVal() && right.isVal()
? new ast.Mod(new ast_1.Val(left.val + right.val), mod)
: new ast.Mod(new ast.Add(left, right), mod));
}],
MULMOD: [0x09, ({ stack }) => {
const left = stack.pop();
const right = stack.pop();
const mod = stack.pop();
stack.push(left.isVal() && right.isVal() && mod.isVal()
? new ast_1.Val((left.val * right.val) % mod.val)
: left.isVal() && right.isVal()
? new ast.Mod(new ast_1.Val(left.val * right.val), mod)
: new ast.Mod(new ast.Mul(left, right), mod));
}],
SIGNEXTEND: [0x0b, ({ stack }) => {
const left = stack.pop();
const right = stack.pop();
stack.push(left.isVal() && right.isVal()
? new ast.Val((right.val << (32n - left.val)) >> (32n - left.val))
: left.isVal()
? new ast.Sar(new ast.Shl(right, new ast.Val(32n - left.val)), new ast.Val(32n - left.val))
: new ast.Sar(new ast.Shl(right, new ast.Sub(new ast_1.Val(32n), left)), new ast.Sub(new ast_1.Val(32n), left)));
}],
EQ: [0x14, ({ stack }) => {
const DIVEXPsig = (left, right) => {
left = left.eval();
right = right.eval();
if (left.isVal() && right.tag === 'Div' && right.right.isVal()) {
const selector = left.val * right.right.val;
right = right.left;
if (selector % (1n << 0xe0n) === 0n &&
right.tag === 'CallDataLoad' &&
right.location.isZero()) {
return new ast.Sig(selector
.toString(16)
.substring(0, 8 - (64 - selector.toString(16).length))
.padStart(8, '0'));
}
}
return undefined;
};
const left = stack.pop();
const right = stack.pop();
stack.push(left.isVal() && right.isVal()
? left.val === right.val
? new ast_1.Val(1n)
: new ast_1.Val(0n)
: DIVEXPsig(left, right) ?? DIVEXPsig(right, left) ?? new ast.Eq(left, right));
}],
ISZERO: [0x15, ({ stack }) => {
const value = stack.pop();
stack.push(new ast.IsZero(value));
}],
NOT: [0x19, ({ stack }) => {
const value = stack.pop();
stack.push(new ast.Not(value));
}],
BYTE: [0x1a, ({ stack }) => {
const position = stack.pop();
const data = stack.pop();
stack.push(new ast.Byte(position, data));
}],
};
const prop = (symbol) => ({ stack }) => stack.push(ast.Props[symbol]);
const SPECIAL = {
COINBASE: [0x41, prop('block.coinbase')],
TIMESTAMP: [0x42, prop('block.timestamp')],
NUMBER: [0x43, prop('block.number')],
DIFFICULTY: [0x44, prop('block.difficulty')],
GASLIMIT: [0x45, prop('block.gaslimit')],
CALLER: [0x33, prop('msg.sender')],
CALLDATASIZE: [0x36, prop('msg.data.length')],
ORIGIN: [0x32, prop('tx.origin')],
GASPRICE: [0x3a, prop('tx.gasprice')],
ADDRESS: [0x30, prop('address(this)')],
CODESIZE: [0x38, prop('codesize()')],
RETURNDATASIZE: [0x3d, prop('returndatasize()')],
GAS: [0x5a, prop('gasleft()')],
CALLVALUE: [0x34, ({ stack }) => stack.push(new ast.CallValue())],
CALLDATALOAD: [0x35, ({ stack }) => stack.push(new ast.CallDataLoad(stack.pop()))],
};
const datacopy = (kind) => function datacopy({ stack, memory }, address) {
const dest = stack.pop();
const offset = stack.pop();
const size = stack.pop();
if (!dest.isVal()) {
// throw new Error('expected number in returndatacopy');
}
else {
memory.set(dest.val, new ast.DataCopy(kind, offset, size, address));
}
// stmts.push(new MStore(location, data));
};
const DATACOPY = {
CALLDATACOPY: [0x37, state => datacopy('calldatacopy')(state)],
CODECOPY: [0x39, function codecopy({ stack, memory }, _opcode, { bytecode }) {
const dest = stack.pop().eval();
const offset = stack.pop();
const size = stack.pop();
if (!dest.isVal() || dest.val >= 1024 * 32) {
throw new state_1.ExecError('Memory destination for CODECOPY is not reducible to Val');
}
else {
memory.set(dest.val, new ast.DataCopy('codecopy', offset, size, undefined, ((offset, size) => offset.isVal() && size.isVal()
? bytecode.subarray(Number(offset.val), Number(offset.val + size.val))
: undefined)(offset.eval(), size.eval())));
}
}],
EXTCODECOPY: [0x3c, state => {
const address = state.stack.pop();
datacopy('extcodecopy')(state, address);
}],
RETURNDATACOPY: [0x3e, state => datacopy('returndatacopy')(state)],
};
const MAXSIZE = 32 * 1024;
function memArgs({ stack, memory }, Klass) {
const offset_ = stack.pop();
const size_ = stack.pop();
return new Klass(offset_, size_, ((offset, size) => {
if (offset.isVal() && size.isVal() && size.val <= MAXSIZE) {
return new ArgsFetcher(memory).range(offset, size).args;
}
else {
if (size.isVal() && size.val > MAXSIZE) {
throw new state_1.ExecError(`Memory size too large creating ${Klass.name}: ${size.val} in \`${size_.yul()}\``);
}
return undefined;
}
})(offset_.eval(), size_.eval()));
}
const getVar = (slot, values, self) => {
let variable;
if (slot.isVal()) {
variable = self.variables.get(slot.val);
if (variable === undefined) {
variable = new ast.Variable(null, values, self.variables.size + 1);
self.variables.set(slot.val, variable);
}
else {
variable.types.push(...values);
}
}
return variable;
};
const STORAGE = {
SLOAD: [0x54, function sload({ stack }) {
const slot = stack.pop();
let base, parts;
const check = (slot) => ([base, parts] = parseSha3(slot), base !== undefined && parts.length > 0);
if (slot.tag === 'Sha3' && check(slot)) {
stack.push(new ast.MappingLoad(slot, this.mappings, base, parts));
}
else if (slot.tag === 'Add' && slot.left.tag === 'Sha3' && slot.right.isVal() && check(slot.left)) {
stack.push(new ast.MappingLoad(slot, this.mappings, base, parts, slot.right.val));
}
else if (slot.tag === 'Add' && slot.left.isVal() && slot.right.tag === 'Sha3' && check(slot.right)) {
stack.push(new ast.MappingLoad(slot, this.mappings, base, parts, slot.left.val));
}
else {
stack.push(new ast.SLoad(slot, getVar(slot.eval(), [], this)));
}
}],
SSTORE: [0x55, function sstore({ stack, stmts }) {
const slot = stack.pop();
const value = stack.pop();
if (slot.tag === 'Local')
slot.nrefs--;
let base, parts;
const check = (slot) => ([base, parts] = parseSha3(slot), base !== undefined && parts.length > 0);
if (slot.tag === 'Sha3' && check(slot)) {
stmts.push(new ast.MappingStore(slot, this.mappings, base, parts, value));
}
else if (slot.tag === 'Add' && slot.left.tag === 'Sha3' && slot.right.isVal() && check(slot.left)) {
stmts.push(new ast.MappingStore(slot, this.mappings, base, parts, value, slot.right.val));
}
else if (slot.tag === 'Add' && slot.left.isVal() && slot.right.tag === 'Sha3' && check(slot.right)) {
stmts.push(new ast.MappingStore(slot, this.mappings, base, parts, value, slot.left.val));
}
else {
stmts.push(new ast.SStore(slot, value, getVar(slot.eval(), [value], this)));
}
}],
};
function parseSha3(sha) {
const shas = [sha];
const mappings = [];
let base = undefined;
while (shas.length > 0) {
const sha = shas.shift();
for (const arg of sha.args ?? []) {
if (arg.tag === 'Sha3' && arg.args) {
shas.unshift(arg);
}
else if (base === undefined && arg.tag === 'Val') {
base = Number(arg.val);
}
else {
mappings.unshift(arg);
}
}
}
return [base, mappings];
}
exports.JUMPDEST = 0x5b;
function getJumpDest(offset, opcode, bytecode) {
const unwrapVal = (expr) => expr.isVal()
? expr
: expr.tag === 'Local' && expr.value.isVal()
? expr.value
: undefined;
const unwrapMask = (lhs, rhs) => lhs.tag === 'Val' && lhs.val === 0xffffffffn
? unwrapVal(rhs)
: undefined;
const offsetVal = offset.tag === 'Local'
? offset.value
: offset.tag === 'And'
? unwrapMask(offset.left, offset.right) ?? unwrapMask(offset.right, offset.left) ?? offset
: offset;
if (!offsetVal.isVal()) {
throw new state_1.ExecError(`${opcode.format()} offset should be numeric but found \`${offset.yul()}\``);
}
const destpc = Number(offsetVal.val);
if (bytecode[destpc] === exports.JUMPDEST) {
// Checks whether `offsetVal` has actually been pushed onto the stack
// by a `PUSHn` instruction instead of having been `eval`uated.
if (offsetVal.pushStateId === undefined)
throw new Error('offset `Val` should be a PUSHn');
offsetVal.jumpDest = destpc;
return { destpc, pushStateId: offsetVal.pushStateId };
}
else {
throw new state_1.ExecError(`${opcode.format()} destination should be JUMPDEST@${destpc} but ${bytecode[destpc] === undefined
? `'${destpc}' is out-of-bounds`
: `found '0x${bytecode[destpc].toString(16)}'`}`);
}
}
class ArgsFetcher {
constructor(memory) {
this.memory = memory;
this.args = [];
}
fetch(i) {
this.args.push(this.memory.get(i)?.eval() ?? new ast.MLoad(new ast_1.Val(i)));
}
range(offset, size) {
for (let i = offset.val; i < offset.val + size.val; i += 32n) {
this.fetch(i);
}
return this;
}
}
const zip = Object.fromEntries;
const FrontierStep = {
/* Stack operations */
POP: [0x50, ({ stack }) => stack.pop()],
...zip([...Array(32).keys()].map(size => [`PUSH${size + 1}`, [
{ opcode: 0x60 + size, size: size + 1 },
(state, opcode) => state.stack.push(new ast_1.Val(BigInt('0x' + opcode.hexData()), state.id))
]])),
...zip([...Array(16).keys()].map(position => [`DUP${position + 1}`, [
0x80 + position,
(state) => {
if (position >= state.stack.values.length) {
throw new state_1.ExecError('Invalid duplication operation, position was not found');
}
const expr = state.stack.values[position];
if (expr.tag !== 'Local') {
const local = new ast.Local(state.nlocals++, expr);
state.stack.values[position] = local;
state.stmts.push(new ast.Locali(local));
}
else {
expr.nrefs++;
}
state.stack.push(state.stack.values[position]);
}
]])),
...zip([...Array(16).keys()].map(position => [`SWAP${position + 1}`, [
0x90 + position,
({ stack }) => stack.swap(position + 1)
]
])),
/* ALU operations */
...zip([
['ADD', 0x01, ast.Add],
['MUL', 0x02, ast.Mul],
['SUB', 0x03, ast.Sub],
['DIV', 0x04, ast.Div],
['SDIV', 0x05, ast.Div],
['MOD', 0x06, ast.Mod],
['SMOD', 0x07, ast.Mod],
['EXP', 0x0a, ast.Exp],
['LT', 0x10, ast.Lt],
['GT', 0x11, ast.Gt],
['SLT', 0x12, ast.Lt],
['SGT', 0x13, ast.Gt],
['AND', 0x16, ast.And],
['OR', 0x17, ast.Or],
['XOR', 0x18, ast.Xor],
].map(([mnemonic, opcode, Klass]) => [mnemonic, [opcode, ({ stack }) => {
const lhs = stack.pop();
const rhs = stack.pop();
stack.push(new Klass(lhs, rhs));
}]])),
...ALU,
...SPECIAL,
PC: [0x58, ({ stack }, op) => stack.push(new ast_1.Val(BigInt(op.pc)))],
...zip(Object.entries(ast.FNS).map(([m, [, , o]]) => [m, [
o, ({ stack }) => stack.push(new ast.Fn(m, stack.pop()))
]])),
...DATACOPY,
/* Memory operations */
MLOAD: [0x51, ({ stack, memory }) => {
const location = stack.pop();
stack.push(new ast.MLoad(location, (location => location.isVal()
? memory.get(location.val)
: undefined)(location.eval())));
}],
...zip([
['MSTORE', 0x52],
['MSTORE8', 0x53],
].map(([m, o]) => [m, [o, ({ stack, memory, stmts }) => {
let location = stack.pop();
const data = stack.pop();
if (location.tag === 'Local')
location.nrefs--;
if (data.tag === 'Local')
data.nrefs--;
stmts.push(new ast.MStore(location, data));
location = location.eval();
if (location.isVal()) {
memory.set(location.val, data);
}
}]])),
MSIZE: [0x59, ({ stack }) => stack.push(new ast.Prop('msize()', 'uint'))],
/* System operations */
SHA3: [0x20, (state) => state.stack.push(memArgs(state, ast_1.Sha3))],
STOP: [{ opcode: 0x00, halts: true }, state => state.halt(new ast.Stop())],
CREATE: [0xf0, function create({ stack, memory }) {
const value = stack.pop();
const offset = stack.pop();
const size = stack.pop();
stack.push(new ast.Create(value, offset, size, ((offset, size) => {
if (offset.isVal() && size.isVal() && memory.has(offset.val)) {
const data = memory.get(offset.val);
if (data.tag === 'DataCopy' && data.bytecode !== undefined && data.bytecode.length === Number(size.val)) {
return data.bytecode;
}
}
return null;
})(offset.eval(), size.eval())));
}],
CALL: [0xf1, function call({ stack, memory }) {
const gas = stack.pop();
const address = stack.pop();
const value = stack.pop();
const argsStart = stack.pop();
const argsLen = stack.pop();
const retStart = stack.pop();
const retLen = stack.pop();
stack.push(new ast.Call(gas, address, value, argsStart, argsLen, retStart, retLen));
memory.invalidateRange(retStart, retLen);
if (retStart.isVal()) {
memory.set(retStart.val, new ast.ReturnData(retStart, retLen));
}
}],
CALLCODE: [0xf2, function callcode({ stack, memory }) {
const gas = stack.pop();
const address = stack.pop();
const value = stack.pop();
const argsStart = stack.pop();
const argsLen = stack.pop();
const retStart = stack.pop();
const retLen = stack.pop();
stack.push(new ast.CallCode(gas, address, value, argsStart, argsLen, retStart, retLen));
memory.invalidateRange(retStart, retLen);
}],
RETURN: [{ opcode: 0xf3, halts: true }, state => state.halt(memArgs(state, ast.Return))],
DELEGATECALL: [0xf4, function delegatecall({ stack, memory }) {
const gas = stack.pop();
const address = stack.pop();
const argsStart = stack.pop();
const argsLen = stack.pop();
const retStart = stack.pop();
const retLen = stack.pop();
stack.push(new ast.DelegateCall(gas, address, argsStart, argsLen, retStart, retLen));
memory.invalidateRange(retStart, retLen);
}],
STATICCALL: [0xfa, function staticcall({ stack, memory }) {
const gas = stack.pop();
const address = stack.pop();
const argsStart = stack.pop();
const argsLen = stack.pop();
const retStart = stack.pop();
const retLen = stack.pop();
stack.push(new ast.StaticCall(gas, address, argsStart, argsLen, retStart, retLen));
memory.invalidateRange(retStart, retLen);
}],
REVERT: [{ opcode: 0xfd, halts: true }, function (state) {
const offset_ = state.stack.pop();
const size_ = state.stack.pop();
state.halt(new ast.Revert(offset_, size_, ...(({ memory }, offset, size) => {
if (offset.isVal() && size.isVal() && size.val <= MAXSIZE) {
const fetcher = new ArgsFetcher(memory);
// https://docs.soliditylang.org/en/latest/control-structures.html#revert
if (size.val % 32n === 4n) {
let selector;
const selectorVal = memory.get(offset.val)?.eval();
if (selectorVal?.isVal() && selectorVal.val % (1n << 224n) === 0n) {
selector = (selectorVal.val >> 224n).toString(16).padStart(8, '0');
}
else {
const selectorVal = memory.get(offset.val - 28n)?.eval();
if (selectorVal?.isVal() && selectorVal.val <= 0xffffffffn) {
selector = selectorVal.val.toString(16).padStart(8, '0');
}
}
if (selector !== undefined) {
for (let i = offset.val + 4n; i < offset.val + size.val; i += 32n) {
fetcher.fetch(i);
}
return [selector, this.getError(selector), fetcher.args];
}
}
return [undefined, undefined, fetcher.range(offset, size).args];
}
else {
if (size.isVal() && size.val > MAXSIZE) {
throw new state_1.ExecError(`Memory size too large creating Revert: ${size.val} in \`${size_.yul()}\``);
}
return [undefined, undefined, undefined];
}
})(state, offset_.eval(), size_.eval())));
}],
SELFDESTRUCT: [{ opcode: 0xff, halts: true }, function selfdestruct(state) {
const address = state.stack.pop();
state.halt(new ast.SelfDestruct(address));
}],
INVALID: [{ opcode: 0xfe, halts: true }, (state, op) => state.halt(new ast.Invalid(op.opcode))],
/* Log operations */
...zip([0, 1, 2, 3, 4].map(ntopics => [`LOG${ntopics}`, [
0xa0 + ntopics, function log({ stack, memory, stmts }) {
const offset_ = stack.pop();
const size_ = stack.pop();
const topics = [];
for (let i = 0; i < ntopics; i++) {
topics.push(stack.pop());
}
let event = undefined;
if (topics.length > 0 && topics[0].isVal()) {
const eventTopic = topics[0].val.toString(16).padStart(64, '0');
event = this.events[eventTopic];
if (event === undefined) {
event = { indexedCount: topics.length - 1 };
this.events[eventTopic] = event;
}
}
stmts.push(new ast.Log(event, offset_, size_, topics, function (offset, size) {
if (offset.isVal() && size.isVal() && size.val <= MAXSIZE) {
return memory.range(offset.val, size.val, i => new ast.MLoad(new ast_1.Val(i)));
}
else {
if (size.isVal() && size.val > MAXSIZE) {
throw new state_1.ExecError(`Memory size too large creating Log: ${size.val} in \`${size_.yul()}\``);
}
return undefined;
}
}(offset_.eval(), size_.eval())));
}
]
])),
...STORAGE,
/* Flow operations */
JUMPDEST: [exports.JUMPDEST, _state => { }],
JUMP: [{ opcode: 0x56, halts: true }, function (state, opcode, { bytecode }) {
const offset = state.stack.pop();
const { destpc, pushStateId } = getJumpDest(offset, opcode, bytecode);
const destBranch = ast.Branch.make(destpc, state);
state.halt(new ast.Jump(offset, destBranch, pushStateId));
}],
JUMPI: [{ opcode: 0x57, halts: true }, function (state, opcode, { bytecode }) {
const offset = state.stack.pop();
const cond = state.stack.pop();
const { destpc, pushStateId } = getJumpDest(offset, opcode, bytecode);
const fallBranch = ast.Branch.make(opcode.pc + 1, state);
const destBranch = ast.Branch.make(destpc, state);
let last;
if (cond.tag === 'Sig') {
const [pc, contBranch] = cond.positive ? [destpc, fallBranch] : [opcode.pc + 1, destBranch];
this.functionBranches.set(cond.selector, {
pc,
state: state.clone(),
});
last = new ast.SigCase(cond, offset, contBranch);
}
else {
last = new ast.Jumpi(cond, offset, fallBranch, destBranch, pushStateId);
}
state.halt(last);
}],
};
const isSelectorCallData = (expr) => expr.tag === 'Shr' &&
expr.shift.isVal() &&
expr.shift.val === 0xe0n &&
expr.value.tag === 'CallDataLoad' &&
expr.value.location.isZero();
/**
* `EXTCODEHASH` implemented in `FNS`.
*
* https://eips.ethereum.org/EIPS/eip-1052
*/
const ConstantinopleStep = {
...FrontierStep,
EQ: [FrontierStep.EQ[0], ({ stack }) => {
FrontierStep.EQ[1]({ stack });
if (stack.top?.tag === 'Eq') {
const SHRsig = (left, right) => {
left = left.eval();
right = right.eval();
return left.isVal() && isSelectorCallData(right)
? new ast_1.Sig(left.val.toString(16).padStart(8, '0'))
: undefined;
};
const { left, right } = stack.top;
const sig = SHRsig(left, right) ?? SHRsig(right, left);
if (sig !== undefined) {
stack.pop();
stack.push(sig);
}
}
}],
ISZERO: [FrontierStep.ISZERO[0], ({ stack }) => {
if (stack.top !== undefined && isSelectorCallData(stack.top.eval())) {
void stack.pop();
stack.push(new ast_1.Sig('00000000'));
}
else {
FrontierStep.ISZERO[1]({ stack });
}
}],
...zip([
['SHL', 0x1b, ast.Shl],
['SHR', 0x1c, ast.Shr],
['SAR', 0x1d, ast.Sar],
].map(([mnemonic, opcode, Cons]) => [mnemonic, [opcode, ({ stack }) => {
const shift = stack.pop();
const value = stack.pop();
stack.push(new Cons(value, shift));
}]])),
CREATE2: [0xf5, function create2({ stack }) {
const value = stack.pop();
const memoryStart = stack.pop();
const memoryLength = stack.pop();
stack.push(new ast.Create2(memoryStart, memoryLength, value));
}],
};
const IstanbulStep = {
...ConstantinopleStep,
CHAINID: [0x46, prop('block.chainid')],
SELFBALANCE: [0x47, prop('address(this).balance')],
};
const LondonStep = {
...IstanbulStep,
BASEFEE: [0x48, prop('block.basefee')],
};
const ParisStep = {
...LondonStep,
PREVRANDAO: [0x44, prop('block.prevrandao')],
};
const ShanghaiStep = {
...ParisStep,
PUSH0: [0x5f, (state) => state.stack.push(new ast_1.Val(0n, state.id))],
};
// https://github.com/vyperlang/vyper/issues/1603#issuecomment-529238275
const isSelectorMLoadCallData = (expr, memory) => expr.tag === 'MLoad' &&
expr.location.isZero() &&
!memory.has(0x0n) && (value => (value?.tag === 'CallDataLoad' &&
value.location.isZero()) || (value?.tag === 'DataCopy' &&
value.kind === 'calldatacopy' &&
value.offset.isZero() &&
value.size.isVal() && value.size.val === 0x4n))(memory.get(0x1cn));
const VyperFunctionSelector = {
ISZERO: [ConstantinopleStep.ISZERO[0], ({ stack, memory }) => {
ConstantinopleStep.ISZERO[1]({ stack });
const top = stack.top;
if (top?.tag === 'IsZero' && top.value.tag === 'Eq') {
const sel = (left, right) => {
return left.isVal() && isSelectorMLoadCallData(right.unwrap(), memory)
? new ast_1.Sig(left.val.toString(16).padStart(8, '0'), false)
: undefined;
};
const { left, right } = top.value;
const sig = sel(left, right) ?? sel(right, left);
if (sig !== undefined) {
stack.pop();
stack.push(sig);
}
}
}],
XOR: [FrontierStep.XOR[0], ({ stack, memory }) => {
FrontierStep.XOR[1]({ stack });
const top = stack.top;
if (top?.tag !== 'Xor')
throw new Error('expected Xor');
const XORsig = (left, right) => {
right = right.unwrap();
return left.isVal() && (isSelectorMLoadCallData(right, memory) || isSelectorCallData(right))
? new ast_1.Sig(left.val.toString(16).padStart(8, '0'), false)
: undefined;
};
const { left, right } = top;
const sig = XORsig(left, right) ?? XORsig(right, left);
if (sig !== undefined) {
stack.pop();
stack.push(sig);
}
}],
};
const SubFunctionSelector = {
SUB: [FrontierStep.SUB[0], ({ stack }) => {
FrontierStep.SUB[1]({ stack });
const top = stack.top;
if (top?.tag !== 'Sub')
throw new Error('expected Sub');
const SUBsig = (left, right) => {
return left.isVal() && isSelectorCallData(right.eval())
? new ast_1.Sig(left.val.toString(16).padStart(8, '0'), false)
: undefined;
};
const { left, right } = top;
const sig = SUBsig(left, right) ?? SUBsig(right, left);
if (sig !== undefined) {
stack.pop();
stack.push(sig);
}
}],
};
/**
*
*/
function ForkFactory(steps) {
const lookup = Object.fromEntries(Object.entries(steps).map(([m, [o,]]) => typeof o === 'number'
? [o, [0, false, m]]
: [o.opcode, [o.size ?? 0, !!o.halts, m]]));
class Fork extends Undef {
constructor() {
super();
Object.assign(this, lookup);
}
}
const props = Object.fromEntries(Object.entries(steps).map(([m, value]) => [m, value[1]]));
Object.assign(Fork.prototype, props);
return Fork;
}
/**
* https://eips.ethereum.org/EIPS/eip-3198
*
* Keep track of https://eips.ethereum.org/EIPS/eip-4200
*/
exports.London = ForkFactory(LondonStep);
/**
* Defines the `Paris` hardfork.
* It includes the `PREVRANDAO` instruction.
*
* Solidity `0.8.18` includes _Support for Paris Hardfork_,
* which introduces the global `block.prevrandao` built-in in Solidity and `prevrandao()`
* instruction in inline assembly and Yul for EVM versions >= Paris.
*
* @see https://ethereum.github.io/execution-specs/diffs/gray_glacier_paris.html
* @see https://eips.ethereum.org/EIPS/eip-4399
* @see https://soliditylang.org/blog/2023/02/01/solidity-0.8.18-release-announcement
*/
exports.Paris = ForkFactory(ParisStep);
/**
* Defines the `Shanghai` hardfork.
* It includes the `PUSH0` instruction.
*
* Solidity `0.8.20` uses `push0` for placing `0` on the Stack.
* This decreases the deployment and runtime costs.
*
* Keep track of https://eips.ethereum.org/EIPS/eip-6780
*
* @see https://ethereum.github.io/execution-specs/diffs/paris_shanghai.html
* @see https://eips.ethereum.org/EIPS/eip-3855
* @see https://soliditylang.org/blog/2023/05/10/solidity-0.8.20-release-announcement/
*/
exports.Shanghai = ForkFactory({ ...ShanghaiStep, ...VyperFunctionSelector, ...SubFunctionSelector });
//# sourceMappingURL=step.js.map