"use strict"; // Yellow Paper // See: https://ethereum.github.io/yellowpaper/paper.pdf // SHL / SHR / SAR: // See: https://eips.ethereum.org/EIPS/eip-145 // CREATE2: // See: https://eips.ethereum.org/EIPS/eip-1014 // EXTCODEHASH // See: https://eips.ethereum.org/EIPS/eip-1052 import { ethers } from "ethers"; export enum OpcodeMemoryAccess { write = "write", read = "read", full = "full" }; export class Opcode { readonly value: number; readonly mnemonic: string readonly delta: number; readonly alpha: number; readonly doc: string; constructor(mnemonic: string, value: number, delta: number, alpha: number, doc?: string) { ethers.utils.defineReadOnly(this, "mnemonic", mnemonic); ethers.utils.defineReadOnly(this, "value", value); ethers.utils.defineReadOnly(this, "delta", delta); ethers.utils.defineReadOnly(this, "alpha", alpha); ethers.utils.defineReadOnly(this, "doc", doc || null); } // Returns if this opcode is a jump isJump(): boolean { return (this.mnemonic === "JUMP" || this.mnemonic === "JUMPI"); } isValidJumpDest(): boolean { return (this.mnemonic === "JUMPDEST"); } // Returns the number of of bytes this performs if a PUSH; 0 otherwise isPush(): number { if (this.mnemonic.substring(0, 4) === "PUSH") { return this.value - 0x60 + 1; } return 0; } // Returns true if this operation writes to memory contents (or if readOrWrite, reads memory) // Unknown opcodes return null isMemoryAccess(readOrWrite?: boolean): OpcodeMemoryAccess { switch ((_Opcodes[this.mnemonic.toLowerCase()] || { memory: null }).memory) { case "read": return OpcodeMemoryAccess.read; case "write": return OpcodeMemoryAccess.write; case "full": return OpcodeMemoryAccess.full; } return null; } // Returns true if this opcode does not affect state // Unknown opcodes return false isStatic(): boolean { return !(_Opcodes[this.mnemonic.toLowerCase()] || { nonStatic: true }).nonStatic; } static from(valueOrMnemonic: number | string) { if (typeof(valueOrMnemonic) === "string") { return OpcodeMap[valueOrMnemonic.toLowerCase()] || null; } return (Opcodes[valueOrMnemonic] || null); } } type _Opcode = { value: number; delta: number; alpha: number; doc?: string; nonStatic?: boolean; memory?: "read" | "write" | "full"; }; const _Opcodes: { [ name: string ]: _Opcode } = { // Stop and Arithmetic Operations stop: { value: 0x00, delta: 0, alpha: 0, doc: "stop" }, add: { value: 0x01, delta: 2, alpha: 1, doc: "v = add(a, b)" }, mul: { value: 0x02, delta: 2, alpha: 1, doc: "v = mul(a, b)" }, sub: { value: 0x03, delta: 2, alpha: 1, doc: "v = sub(a, b)" }, div: { value: 0x04, delta: 2, alpha: 1, doc: "v = div(top, bottom)" }, sdiv: { value: 0x05, delta: 2, alpha: 1, doc: "v = sdiv(top, bottom)" }, mod: { value: 0x06, delta: 2, alpha: 1, doc: "v = mod(a, modulo)" }, smod: { value: 0x07, delta: 2, alpha: 1, doc: "v = smod(a, modulo)" }, addmod: { value: 0x08, delta: 3, alpha: 1, doc: "v = addmod(a, b, modulo)" }, mulmod: { value: 0x09, delta: 3, alpha: 1, doc: "v = mul(a, b, modulo)" }, exp: { value: 0x0a, delta: 2, alpha: 1, doc: "v = exp(base, exponent)" }, signextend: { value: 0x0b, delta: 2, alpha: 1, doc: "v = signextend(value, byteWidth)" }, // Comparison & Bitwise Logic Operations lt: { value: 0x10, delta: 2, alpha: 1, doc: "v = lt(a, b)" }, gt: { value: 0x11, delta: 2, alpha: 1, doc: "v = gt(a, b)" }, slt: { value: 0x12, delta: 2, alpha: 1, doc: "v = slt(a, b)" }, sgt: { value: 0x13, delta: 2, alpha: 1, doc: "v = sgt(a, b)" }, eq: { value: 0x14, delta: 2, alpha: 1, doc: "v = eq(a, b)" }, iszero: { value: 0x15, delta: 1, alpha: 1, doc: "v = iszero(a)" }, and: { value: 0x16, delta: 2, alpha: 1, doc: "v = and(a, b)" }, or: { value: 0x17, delta: 2, alpha: 1, doc: "v = or(a, b)" }, xor: { value: 0x18, delta: 2, alpha: 1, doc: "v = xor(a, b)" }, not: { value: 0x19, delta: 1, alpha: 1, doc: "v = not(a, b)" }, byte: { value: 0x1a, delta: 2, alpha: 1, doc: "v = byte(msbByteIndex, value)" }, shl: { value: 0x1b, delta: 2, alpha: 1, doc: "v = shl(shiftBits, value)" }, shr: { value: 0x1c, delta: 2, alpha: 1, doc: "v = shr(shiftBits, value)" }, sar: { value: 0x1d, delta: 2, alpha: 1, doc: "v = sar(shiftBits, value)" }, // SHA3 sha3: { value: 0x20, delta: 2, alpha: 1, doc: "v = sha3(offset, length)", memory: "read" }, // Environmental Information address: { value: 0x30, delta: 0, alpha: 1, doc: "myAddr = address" }, balance: { value: 0x31, delta: 1, alpha: 1, doc: "wei = balance(address)" }, origin: { value: 0x32, delta: 0, alpha: 1, doc: "txOrigin = origin" }, caller: { value: 0x33, delta: 0, alpha: 1, doc: "msgSender = caller" }, callvalue: { value: 0x34, delta: 0, alpha: 1, doc: "msgValue = callvalue" }, calldataload: { value: 0x35, delta: 1, alpha: 1, doc: "calldataWordValue = calldataload(byteOffet)" }, calldatasize: { value: 0x36, delta: 0, alpha: 1, doc: "calldataLength = calldatasize" }, calldatacopy: { value: 0x37, delta: 3, alpha: 0, doc: "calldatacopy(dstMemoryIndex, dataIndex, length)", memory: "write" }, codesize: { value: 0x38, delta: 0, alpha: 1, doc: "myCodeLength = codesize" }, codecopy: { value: 0x39, delta: 3, alpha: 0, doc: "codecopy(dstMemoryIndex, codeIndex, length)", memory: "write" }, gasprice: { value: 0x3a, delta: 0, alpha: 1, doc: "txGasPrice = gasprice" }, extcodesize: { value: 0x3b, delta: 1, alpha: 1, doc: "otherCodeLength = extcodesize(address)" }, extcodecopy: { value: 0x3c, delta: 4, alpha: 0, doc: "extcodecopy(address, dstMemoryIndex, extcodeIndex, length)", memory: "write" }, returndatasize: { value: 0x3d, delta: 0, alpha: 1, doc: "v = returndatasize" }, returndatacopy: { value: 0x3e, delta: 3, alpha: 0, doc: "returndatacopy(dstMemoryOffset, returndataIndex, length)", memory: "write" }, extcodehash: { value: 0x3f, delta: 1, alpha: 1, doc: "hash = extcodehash(address)" }, // Block Information blockhash: { value: 0x40, delta: 1, alpha: 1, doc: "hash = blockhash(blockNumber)" }, coinbase: { value: 0x41, delta: 0, alpha: 1, doc: "miner = coinbase" }, timestamp: { value: 0x42, delta: 0, alpha: 1, doc: "now = timestamp" }, number: { value: 0x43, delta: 0, alpha: 1, doc: "blockNumber = number" }, difficulty: { value: 0x44, delta: 0, alpha: 1, doc: "diff = difficulty" }, gaslimit: { value: 0x45, delta: 0, alpha: 1, doc: "gas = gaslimit" }, // Stack, Memory, Storage and Flow Operations pop: { value: 0x50, delta: 1, alpha: 0, doc: "stackTopValue = pop" }, mload: { value: 0x51, delta: 1, alpha: 1, doc: "memoryWordValue = mload(memoryByteIndex)", memory: "read" }, mstore: { value: 0x52, delta: 2, alpha: 0, doc: "mstore(memoryByteIndex, valueOut)", memory: "write" }, mstore8: { value: 0x53, delta: 2, alpha: 0, doc: "mstore8(memoryByteIndex, valueOut [ & 0xff ])", memory: "write" }, sload: { value: 0x54, delta: 1, alpha: 1, doc: "storageWordValue = sload(storageWordIndex)" }, sstore: { value: 0x55, delta: 2, alpha: 0, doc: "sstore(storageWordIndex, valueOut)", nonStatic: true }, jump: { value: 0x56, delta: 1, alpha: 0, doc: "jump(target)" }, jumpi: { value: 0x57, delta: 2, alpha: 0, doc: "jumpi(target, notZero)" }, pc: { value: 0x58, delta: 0, alpha: 1, doc: "programCounter = pc" }, msize: { value: 0x59, delta: 0, alpha: 1, doc: "currentMemorySize = msize" }, gas: { value: 0x5a, delta: 0, alpha: 1, doc: "remainingGas = gas" }, jumpdest: { value: 0x5b, delta: 0, alpha: 0, doc: "jumpdest" }, // Push Operations push1: { value: 0x60, delta: 0, alpha: 1 }, push2: { value: 0x61, delta: 0, alpha: 1 }, push3: { value: 0x62, delta: 0, alpha: 1 }, push4: { value: 0x63, delta: 0, alpha: 1 }, push5: { value: 0x64, delta: 0, alpha: 1 }, push6: { value: 0x65, delta: 0, alpha: 1 }, push7: { value: 0x66, delta: 0, alpha: 1 }, push8: { value: 0x67, delta: 0, alpha: 1 }, push9: { value: 0x68, delta: 0, alpha: 1 }, push10: { value: 0x69, delta: 0, alpha: 1 }, push11: { value: 0x6a, delta: 0, alpha: 1 }, push12: { value: 0x6b, delta: 0, alpha: 1 }, push13: { value: 0x6c, delta: 0, alpha: 1 }, push14: { value: 0x6d, delta: 0, alpha: 1 }, push15: { value: 0x6e, delta: 0, alpha: 1 }, push16: { value: 0x6f, delta: 0, alpha: 1 }, push17: { value: 0x70, delta: 0, alpha: 1 }, push18: { value: 0x71, delta: 0, alpha: 1 }, push19: { value: 0x72, delta: 0, alpha: 1 }, push20: { value: 0x73, delta: 0, alpha: 1 }, push21: { value: 0x74, delta: 0, alpha: 1 }, push22: { value: 0x75, delta: 0, alpha: 1 }, push23: { value: 0x76, delta: 0, alpha: 1 }, push24: { value: 0x77, delta: 0, alpha: 1 }, push25: { value: 0x78, delta: 0, alpha: 1 }, push26: { value: 0x79, delta: 0, alpha: 1 }, push27: { value: 0x7a, delta: 0, alpha: 1 }, push28: { value: 0x7b, delta: 0, alpha: 1 }, push29: { value: 0x7c, delta: 0, alpha: 1 }, push30: { value: 0x7d, delta: 0, alpha: 1 }, push31: { value: 0x7e, delta: 0, alpha: 1 }, push32: { value: 0x7f, delta: 0, alpha: 1 }, // Duplicate Operations dup1: { value: 0x80, delta: 0, alpha: 1 }, dup2: { value: 0x81, delta: 0, alpha: 1 }, dup3: { value: 0x82, delta: 0, alpha: 1 }, dup4: { value: 0x83, delta: 0, alpha: 1 }, dup5: { value: 0x84, delta: 0, alpha: 1 }, dup6: { value: 0x85, delta: 0, alpha: 1 }, dup7: { value: 0x86, delta: 0, alpha: 1 }, dup8: { value: 0x87, delta: 0, alpha: 1 }, dup9: { value: 0x88, delta: 0, alpha: 1 }, dup10: { value: 0x89, delta: 0, alpha: 1 }, dup11: { value: 0x8a, delta: 0, alpha: 1 }, dup12: { value: 0x8b, delta: 0, alpha: 1 }, dup13: { value: 0x8c, delta: 0, alpha: 1 }, dup14: { value: 0x8d, delta: 0, alpha: 1 }, dup15: { value: 0x8e, delta: 0, alpha: 1 }, dup16: { value: 0x8f, delta: 0, alpha: 1 }, // Exchange Operations swap1: { value: 0x90, delta: 0, alpha: 0 }, swap2: { value: 0x91, delta: 0, alpha: 0 }, swap3: { value: 0x92, delta: 0, alpha: 0 }, swap4: { value: 0x93, delta: 0, alpha: 0 }, swap5: { value: 0x94, delta: 0, alpha: 0 }, swap6: { value: 0x95, delta: 0, alpha: 0 }, swap7: { value: 0x96, delta: 0, alpha: 0 }, swap8: { value: 0x97, delta: 0, alpha: 0 }, swap9: { value: 0x98, delta: 0, alpha: 0 }, swap10: { value: 0x99, delta: 0, alpha: 0 }, swap11: { value: 0x9a, delta: 0, alpha: 0 }, swap12: { value: 0x9b, delta: 0, alpha: 0 }, swap13: { value: 0x9c, delta: 0, alpha: 0 }, swap14: { value: 0x9d, delta: 0, alpha: 0 }, swap15: { value: 0x9e, delta: 0, alpha: 0 }, swap16: { value: 0x9f, delta: 0, alpha: 0 }, // Loggin Operations log0: { value: 0xa0, delta: 2, alpha: 0, nonStatic: true, memory: "read" }, log1: { value: 0xa1, delta: 3, alpha: 0, nonStatic: true, memory: "read" }, log2: { value: 0xa2, delta: 4, alpha: 0, nonStatic: true, memory: "read" }, log3: { value: 0xa3, delta: 5, alpha: 0, nonStatic: true, memory: "read" }, log4: { value: 0xa4, delta: 6, alpha: 0, nonStatic: true, memory: "read" }, // System Operations create: { value: 0xf0, delta: 3, alpha: 1, doc: "address = create(value, index, length)", nonStatic: true, memory: "read" }, call: { value: 0xf1, delta: 7, alpha: 1, doc: "v = call(gasLimit, address, value, inputIndex, inputLength, outputIndex, outputLength)", nonStatic: true, memory: "full" }, callcode: { value: 0xf2, delta: 7, alpha: 1, doc: "v = callcode(@TODO)", nonStatic: true, memory: "full" }, "return": { value: 0xf3, delta: 2, alpha: 0, doc: "return(index, length)", memory: "read" }, delegatecall: { value: 0xf4, delta: 6, alpha: 1, doc: "v = delegatecall(gasLimit, address, inputIndex, inputLength, outputIndex, outputLength)", nonStatic: true, memory: "full" }, create2: { value: 0xf5, delta: 4, alpha: 1, doc: "address = create2(value, index, length, salt)", nonStatic: true, memory: "read" }, staticcall: { value: 0xfa, delta: 6, alpha: 1, doc: "v = staticcall(gasLimit, address, inputIndex, inputLength, outputIndex, outputLength)", memory: "full" }, revert: { value: 0xfd, delta: 2, alpha: 0, doc: "revert(returnDataOffset, returnDataLength)", memory: "read" }, invalid: { value: 0xfe, delta: 0, alpha: 0, doc: "invalid" }, suicide: { value: 0xff, delta: 1, alpha: 0, doc: "suicide(targetAddress)", nonStatic: true }, }; const OpcodeMap: { [ mnemonic: string ]: Opcode } = { }; const Opcodes: Array = [ ]; for (let i = 0; i < 256; i++) { Opcodes.push(null); } Object.keys(_Opcodes).forEach((mnemonic) => { const info = _Opcodes[mnemonic]; const opcode = new Opcode(mnemonic.toUpperCase(), info.value, info.delta, info.alpha, info.doc); const key = opcode.mnemonic.toLowerCase(); const value = opcode.value; if (OpcodeMap[key] || Opcodes[value]) { console.log(key, OpcodeMap[key], value, Opcodes[value]); throw new Error("There is a type in the above table."); } OpcodeMap[key] = opcode; Opcodes[value] = opcode; }); Object.freeze(Opcodes);