421 lines
15 KiB
Python
421 lines
15 KiB
Python
# BSD 3-Clause License
|
|
#
|
|
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice, this
|
|
# list of conditions and the following disclaimer.
|
|
#
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
#
|
|
# * Neither the name of the copyright holder nor the names of its
|
|
# contributors may be used to endorse or promote products derived from
|
|
# this software without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
"""opcodes.py: Definitions of all EVM opcodes, and related utility functions."""
|
|
|
|
|
|
class OpCode:
|
|
"""An EVM opcode."""
|
|
|
|
def __init__(self, name: str, code: int, pop: int, push: int):
|
|
"""
|
|
Args:
|
|
name (str): Human-readable opcode, such as opcode PUSH1.
|
|
code (int): The instruction byte itself.
|
|
pop (int): The number of stack elements this op pops.
|
|
push (int): The number of stack elements this op pushes.
|
|
"""
|
|
self.name = name
|
|
self.code = code
|
|
self.pop = pop
|
|
self.push = push
|
|
|
|
def stack_delta(self) -> int:
|
|
"""Return the net effect on the stack size of running this operation."""
|
|
return self.push - self.pop
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
def __repr__(self) -> str:
|
|
return "<{0} object {1}, {2}>".format(
|
|
self.__class__.__name__,
|
|
hex(id(self)),
|
|
self.__str__()
|
|
)
|
|
|
|
def __eq__(self, other) -> bool:
|
|
return self.code == other.code
|
|
|
|
def __hash__(self) -> int:
|
|
return self.code.__hash__()
|
|
|
|
# Special cases for kind one, such as CALLVALUE
|
|
# Those opcodes do not need anything from stack, but will give related dynamic info
|
|
def is_kind_one(self) -> bool:
|
|
special = {ADDRESS.name, ORIGIN.name, CALLER.name, CALLVALUE.name, CALLDATASIZE.name,
|
|
CODESIZE.name, GASPRICE.name, RETURNDATASIZE.name, COINBASE.name, TIMESTAMP.name,
|
|
NUMBER.name, DIFFICULTY.name, GASLIMIT.name, PC.name, MSIZE.name, GAS.name}
|
|
return self.name in special
|
|
|
|
# Special cases for kind two, such as CALLDATALOAD
|
|
# Need one or more stack arguments and related dynamic info
|
|
def is_kind_two(self) -> bool:
|
|
special = {SHA3.name, BALANCE.name, CALLDATALOAD.name, EXTCODESIZE.name, BLOCKHASH.name}
|
|
return self.name in special
|
|
|
|
# Special cases for part of kind three load, SLOAD
|
|
def is_kind_three_load(self) -> bool:
|
|
special = {SLOAD.name}
|
|
return self.name in special
|
|
|
|
# Special cases for part of kind three store, like MSTORE
|
|
def is_kind_three_store_one(self) -> bool:
|
|
special = {MSTORE.name, MSTORE8.name, SSTORE.name}
|
|
return self.name in special
|
|
|
|
# Special cases for some other store operations, they are special
|
|
# since they do need some arguments from the stack and then get the related data
|
|
# to store them into the memory
|
|
def is_kind_three_store_two(self) -> bool:
|
|
special = {CALLDATACOPY.name, CODECOPY.name, EXTCODECOPY.name, RETURNDATACOPY.name}
|
|
return self.name in special
|
|
|
|
# Special cases for four call opcodes
|
|
def is_kind_four(self) -> bool:
|
|
special = {CALL.name, CALLCODE.name, DELEGATECALL.name, STATICCALL.name}
|
|
return self.name in special
|
|
|
|
def is_kind_five(self) -> bool:
|
|
special = {CREATE.name, CREATE2.name}
|
|
return self.name in special
|
|
|
|
def op_pc_gap(self) -> int:
|
|
if self.is_push():
|
|
return int(self.name[4]) + 1
|
|
else:
|
|
return 1
|
|
|
|
def is_push(self) -> bool:
|
|
"""Predicate: opcode is a push operation."""
|
|
return PUSH1.code <= self.code <= PUSH32.code
|
|
|
|
def is_swap(self) -> bool:
|
|
"""Predicate: opcode is a swap operation."""
|
|
return SWAP1.code <= self.code <= SWAP16.code
|
|
|
|
def is_dup(self) -> bool:
|
|
"""Predicate: opcode is a dup operation."""
|
|
return DUP1.code <= self.code <= DUP16.code
|
|
|
|
def is_log(self) -> bool:
|
|
"""Predicate: opcode is a log operation."""
|
|
return LOG0.code <= self.code <= LOG4.code
|
|
|
|
def is_missing(self) -> bool:
|
|
return self.code not in BYTECODES
|
|
|
|
def is_invalid(self) -> bool:
|
|
return (self.code == INVALID.code) or self.is_missing()
|
|
|
|
# Zoey - adding shift operation
|
|
# def is_shift(self) -> bool:
|
|
# """Predicate: opcode is a shift operation."""
|
|
# return SHL.code <= self.code <= SAR.code
|
|
|
|
def is_arithmetic(self) -> bool:
|
|
"""Predicate: opcode's result can be calculated from its inputs alone."""
|
|
return (ADD.code <= self.code <= SIGNEXTEND.code) or \
|
|
(LT.code <= self.code <= BYTE.code) or \
|
|
(SHA3.code <= self.code <= SHA3.code) \
|
|
# or self.is_shift() # Zoey - adding shift operation
|
|
|
|
|
|
|
|
def is_memory(self) -> bool:
|
|
"""Predicate: opcode operates on memory"""
|
|
return MLOAD.code <= self.code <= MSTORE8.code
|
|
|
|
def is_storage(self) -> bool:
|
|
"""Predicate: opcode operates on storage ('the tape')"""
|
|
return SLOAD.code <= self.code <= SSTORE.code
|
|
|
|
def is_call(self) -> bool:
|
|
"""Predicate: opcode calls an external contract"""
|
|
return self in (CALL, CALLCODE, DELEGATECALL, STATICCALL,)
|
|
|
|
def alters_flow(self) -> bool:
|
|
"""Predicate: opcode alters EVM control flow."""
|
|
return (self.code in (JUMP.code, JUMPI.code,)) or self.possibly_halts()
|
|
|
|
def is_exception(self) -> bool:
|
|
"""Predicate: opcode causes the EVM to throw an exception."""
|
|
return (self.code in (THROW.code, THROWI.code, REVERT.code)) \
|
|
or self.is_invalid()
|
|
|
|
def halts(self) -> bool:
|
|
"""Predicate: opcode causes the EVM to halt."""
|
|
halt_codes = (
|
|
STOP.code,
|
|
RETURN.code,
|
|
SELFDESTRUCT.code,
|
|
THROW.code,
|
|
REVERT.code,
|
|
)
|
|
return (self.code in halt_codes) or self.is_invalid()
|
|
|
|
def possibly_halts(self) -> bool:
|
|
"""Predicate: opcode MAY cause the EVM to halt. (halts + THROWI)"""
|
|
return self.halts() or self.code == THROWI.code
|
|
|
|
def push_len(self) -> int:
|
|
"""Return the number of bytes the given PUSH instruction pushes."""
|
|
return self.code - PUSH1.code + 1 if self.is_push() else 0
|
|
|
|
def log_len(self) -> int:
|
|
"""Return the number of topics the given LOG instruction includes."""
|
|
return self.code - LOG0.code if self.is_log() else 0
|
|
|
|
|
|
# Construct all EVM opcodes
|
|
# Arithmetic Ops and STOP
|
|
STOP = OpCode("STOP", 0x00, 0, 0)
|
|
ADD = OpCode("ADD", 0x01, 2, 1)
|
|
MUL = OpCode("MUL", 0x02, 2, 1)
|
|
SUB = OpCode("SUB", 0x03, 2, 1)
|
|
DIV = OpCode("DIV", 0x04, 2, 1)
|
|
SDIV = OpCode("SDIV", 0x05, 2, 1)
|
|
MOD = OpCode("MOD", 0x06, 2, 1)
|
|
SMOD = OpCode("SMOD", 0x07, 2, 1)
|
|
ADDMOD = OpCode("ADDMOD", 0x08, 3, 1)
|
|
MULMOD = OpCode("MULMOD", 0x09, 3, 1)
|
|
EXP = OpCode("EXP", 0x0a, 2, 1)
|
|
SIGNEXTEND = OpCode("SIGNEXTEND", 0x0b, 2, 1)
|
|
|
|
# Comparison and Bitwise Logic
|
|
LT = OpCode("LT", 0x10, 2, 1)
|
|
GT = OpCode("GT", 0x11, 2, 1)
|
|
SLT = OpCode("SLT", 0x12, 2, 1)
|
|
SGT = OpCode("SGT", 0x13, 2, 1)
|
|
EQ = OpCode("EQ", 0x14, 2, 1)
|
|
ISZERO = OpCode("ISZERO", 0x15, 1, 1)
|
|
AND = OpCode("AND", 0x16, 2, 1)
|
|
OR = OpCode("OR", 0x17, 2, 1)
|
|
XOR = OpCode("XOR", 0x18, 2, 1)
|
|
NOT = OpCode("NOT", 0x19, 1, 1)
|
|
BYTE = OpCode("BYTE", 0x1a, 2, 1)
|
|
|
|
SHA3 = OpCode("SHA3", 0x20, 2, 1)
|
|
# ZOEY - adding shift operation
|
|
SHL = OpCode("SHL", 0x1b, 2, 1) # 0x1b is the opcode for SHL
|
|
SHR = OpCode("SHR", 0x1c, 2, 1) # 0x1c is the opcode for SHR
|
|
SAR = OpCode("SAR", 0x1d, 2, 1) # 0x1d is the opcode for SAR
|
|
# Update OPCODES dictionary
|
|
|
|
|
|
# Environmental Information
|
|
ADDRESS = OpCode("ADDRESS", 0x30, 0, 1)
|
|
BALANCE = OpCode("BALANCE", 0x31, 1, 1)
|
|
ORIGIN = OpCode("ORIGIN", 0x32, 0, 1)
|
|
CALLER = OpCode("CALLER", 0x33, 0, 1)
|
|
CALLVALUE = OpCode("CALLVALUE", 0x34, 0, 1)
|
|
CALLDATALOAD = OpCode("CALLDATALOAD", 0x35, 1, 1)
|
|
CALLDATASIZE = OpCode("CALLDATASIZE", 0x36, 0, 1)
|
|
CALLDATACOPY = OpCode("CALLDATACOPY", 0x37, 3, 0)
|
|
CODESIZE = OpCode("CODESIZE", 0x38, 0, 1)
|
|
CODECOPY = OpCode("CODECOPY", 0x39, 3, 0)
|
|
GASPRICE = OpCode("GASPRICE", 0x3a, 0, 1)
|
|
EXTCODESIZE = OpCode("EXTCODESIZE", 0x3b, 1, 1)
|
|
EXTCODECOPY = OpCode("EXTCODECOPY", 0x3c, 4, 0)
|
|
|
|
# Block Information
|
|
BLOCKHASH = OpCode("BLOCKHASH", 0x40, 1, 1)
|
|
COINBASE = OpCode("COINBASE", 0x41, 0, 1)
|
|
TIMESTAMP = OpCode("TIMESTAMP", 0x42, 0, 1)
|
|
NUMBER = OpCode("NUMBER", 0x43, 0, 1)
|
|
DIFFICULTY = OpCode("DIFFICULTY", 0x44, 0, 1)
|
|
GASLIMIT = OpCode("GASLIMIT", 0x45, 0, 1)
|
|
|
|
# Stack, Memory, Storage, Flow
|
|
POP = OpCode("POP", 0x50, 1, 0)
|
|
MLOAD = OpCode("MLOAD", 0x51, 1, 1)
|
|
MSTORE = OpCode("MSTORE", 0x52, 2, 0)
|
|
MSTORE8 = OpCode("MSTORE8", 0x53, 2, 0)
|
|
SLOAD = OpCode("SLOAD", 0x54, 1, 1)
|
|
SSTORE = OpCode("SSTORE", 0x55, 2, 0)
|
|
JUMP = OpCode("JUMP", 0x56, 1, 0)
|
|
JUMPI = OpCode("JUMPI", 0x57, 2, 0)
|
|
PC = OpCode("PC", 0x58, 0, 1)
|
|
MSIZE = OpCode("MSIZE", 0x59, 0, 1)
|
|
GAS = OpCode("GAS", 0x5a, 0, 1)
|
|
JUMPDEST = OpCode("JUMPDEST", 0x5b, 0, 0)
|
|
|
|
PUSH1 = OpCode("PUSH1", 0x60, 0, 1)
|
|
PUSH2 = OpCode("PUSH2", 0x61, 0, 1)
|
|
PUSH3 = OpCode("PUSH3", 0x62, 0, 1)
|
|
PUSH4 = OpCode("PUSH4", 0x63, 0, 1)
|
|
PUSH5 = OpCode("PUSH5", 0x64, 0, 1)
|
|
PUSH6 = OpCode("PUSH6", 0x65, 0, 1)
|
|
PUSH7 = OpCode("PUSH7", 0x66, 0, 1)
|
|
PUSH8 = OpCode("PUSH8", 0x67, 0, 1)
|
|
PUSH9 = OpCode("PUSH9", 0x68, 0, 1)
|
|
PUSH10 = OpCode("PUSH10", 0x69, 0, 1)
|
|
PUSH11 = OpCode("PUSH11", 0x6a, 0, 1)
|
|
PUSH12 = OpCode("PUSH12", 0x6b, 0, 1)
|
|
PUSH13 = OpCode("PUSH13", 0x6c, 0, 1)
|
|
PUSH14 = OpCode("PUSH14", 0x6d, 0, 1)
|
|
PUSH15 = OpCode("PUSH15", 0x6e, 0, 1)
|
|
PUSH16 = OpCode("PUSH16", 0x6f, 0, 1)
|
|
PUSH17 = OpCode("PUSH17", 0x70, 0, 1)
|
|
PUSH18 = OpCode("PUSH18", 0x71, 0, 1)
|
|
PUSH19 = OpCode("PUSH19", 0x72, 0, 1)
|
|
PUSH20 = OpCode("PUSH20", 0x73, 0, 1)
|
|
PUSH21 = OpCode("PUSH21", 0x74, 0, 1)
|
|
PUSH22 = OpCode("PUSH22", 0x75, 0, 1)
|
|
PUSH23 = OpCode("PUSH23", 0x76, 0, 1)
|
|
PUSH24 = OpCode("PUSH24", 0x77, 0, 1)
|
|
PUSH25 = OpCode("PUSH25", 0x78, 0, 1)
|
|
PUSH26 = OpCode("PUSH26", 0x79, 0, 1)
|
|
PUSH27 = OpCode("PUSH27", 0x7a, 0, 1)
|
|
PUSH28 = OpCode("PUSH28", 0x7b, 0, 1)
|
|
PUSH29 = OpCode("PUSH29", 0x7c, 0, 1)
|
|
PUSH30 = OpCode("PUSH30", 0x7d, 0, 1)
|
|
PUSH31 = OpCode("PUSH31", 0x7e, 0, 1)
|
|
PUSH32 = OpCode("PUSH32", 0x7f, 0, 1)
|
|
|
|
DUP1 = OpCode("DUP1", 0x80, 1, 2)
|
|
DUP2 = OpCode("DUP2", 0x81, 2, 3)
|
|
DUP3 = OpCode("DUP3", 0x82, 3, 4)
|
|
DUP4 = OpCode("DUP4", 0x83, 4, 5)
|
|
DUP5 = OpCode("DUP5", 0x84, 5, 6)
|
|
DUP6 = OpCode("DUP6", 0x85, 6, 7)
|
|
DUP7 = OpCode("DUP7", 0x86, 7, 8)
|
|
DUP8 = OpCode("DUP8", 0x87, 8, 9)
|
|
DUP9 = OpCode("DUP9", 0x88, 9, 10)
|
|
DUP10 = OpCode("DUP10", 0x89, 10, 11)
|
|
DUP11 = OpCode("DUP11", 0x8a, 11, 12)
|
|
DUP12 = OpCode("DUP12", 0x8b, 12, 13)
|
|
DUP13 = OpCode("DUP13", 0x8c, 13, 14)
|
|
DUP14 = OpCode("DUP14", 0x8d, 14, 15)
|
|
DUP15 = OpCode("DUP15", 0x8e, 15, 16)
|
|
DUP16 = OpCode("DUP16", 0x8f, 16, 17)
|
|
|
|
SWAP1 = OpCode("SWAP1", 0x90, 2, 2)
|
|
SWAP2 = OpCode("SWAP2", 0x91, 3, 3)
|
|
SWAP3 = OpCode("SWAP3", 0x92, 4, 4)
|
|
SWAP4 = OpCode("SWAP4", 0x93, 5, 5)
|
|
SWAP5 = OpCode("SWAP5", 0x94, 6, 6)
|
|
SWAP6 = OpCode("SWAP6", 0x95, 7, 7)
|
|
SWAP7 = OpCode("SWAP7", 0x96, 8, 8)
|
|
SWAP8 = OpCode("SWAP8", 0x97, 9, 9)
|
|
SWAP9 = OpCode("SWAP9", 0x98, 10, 10)
|
|
SWAP10 = OpCode("SWAP10", 0x99, 11, 11)
|
|
SWAP11 = OpCode("SWAP11", 0x9a, 12, 12)
|
|
SWAP12 = OpCode("SWAP12", 0x9b, 13, 13)
|
|
SWAP13 = OpCode("SWAP13", 0x9c, 14, 14)
|
|
SWAP14 = OpCode("SWAP14", 0x9d, 15, 15)
|
|
SWAP15 = OpCode("SWAP15", 0x9e, 16, 16)
|
|
SWAP16 = OpCode("SWAP16", 0x9f, 17, 17)
|
|
|
|
# Logging
|
|
LOG0 = OpCode("LOG0", 0xa0, 2, 0)
|
|
LOG1 = OpCode("LOG1", 0xa1, 3, 0)
|
|
LOG2 = OpCode("LOG2", 0xa2, 4, 0)
|
|
LOG3 = OpCode("LOG3", 0xa3, 5, 0)
|
|
LOG4 = OpCode("LOG4", 0xa4, 6, 0)
|
|
|
|
# System Operations
|
|
CREATE = OpCode("CREATE", 0xf0, 3, 1)
|
|
CREATE2 = OpCode("CREATE2", 0xf5, 4, 1)
|
|
CALL = OpCode("CALL", 0xf1, 7, 1)
|
|
CALLCODE = OpCode("CALLCODE", 0xf2, 7, 1)
|
|
RETURN = OpCode("RETURN", 0xf3, 2, 0)
|
|
DELEGATECALL = OpCode("DELEGATECALL", 0xf4, 6, 1)
|
|
INVALID = OpCode("INVALID", 0xfe, 0, 0)
|
|
SELFDESTRUCT = OpCode("SELFDESTRUCT", 0xff, 1, 0)
|
|
|
|
# New Byzantinium OpCodes for block.number >= BYZANTIUM_FORK_BLKNUM
|
|
REVERT = OpCode("REVERT", 0xfd, 2, 0)
|
|
RETURNDATASIZE = OpCode("RETURNDATASIZE", 0x3d, 0, 1)
|
|
RETURNDATACOPY = OpCode("RETURNDATACOPY", 0x3e, 3, 0)
|
|
STATICCALL = OpCode("STATICCALL", 0xfa, 6, 1)
|
|
|
|
# TAC Operations
|
|
# These are not EVM opcodes, but they are used by the three-address code
|
|
NOP = OpCode("NOP", -1, 0, 0)
|
|
CONST = OpCode("CONST", -2, 0, 0)
|
|
LOG = OpCode("LOG", -3, 0, 0)
|
|
THROW = OpCode("THROW", -4, 0, 0)
|
|
THROWI = OpCode("THROWI", -5, 0, 0)
|
|
|
|
|
|
|
|
|
|
# Produce mappings from names and instruction codes to opcode objects
|
|
OPCODES = {
|
|
code.name: code
|
|
for code in globals().values()
|
|
if isinstance(code, OpCode)
|
|
}
|
|
"""Dictionary mapping of opcode string names to EVM OpCode objects"""
|
|
|
|
# Handle incorrect opcode name from go-ethereum disasm
|
|
OPCODES["TXGASPRICE"] = OPCODES["GASPRICE"]
|
|
|
|
BYTECODES = {code.code: code for code in OPCODES.values()}
|
|
"""Dictionary mapping of byte values to EVM OpCode objects"""
|
|
|
|
|
|
def opcode_by_name(name: str) -> OpCode:
|
|
"""
|
|
Mapping: Retrieves the named OpCode object (case-insensitive).
|
|
|
|
Throws:
|
|
LookupError: if there is no opcode defined with the given name.
|
|
"""
|
|
name = name.upper()
|
|
if name not in OPCODES:
|
|
raise LookupError("No opcode named '{}'.".format(name))
|
|
return OPCODES[name]
|
|
|
|
|
|
def opcode_by_value(val: int) -> OpCode:
|
|
"""
|
|
Mapping: Retrieves the OpCode object with the given value.
|
|
|
|
Throws:
|
|
LookupError: if there is no opcode defined with the given value.
|
|
"""
|
|
if val not in BYTECODES:
|
|
raise LookupError("No opcode with value '0x{:02X}'.".format(val))
|
|
return BYTECODES[val]
|
|
|
|
|
|
def missing_opcode(val: int) -> OpCode:
|
|
"""
|
|
Produces a new OpCode with the given value, as long as that is
|
|
an unknown code.
|
|
|
|
Throws:
|
|
ValueError: if there is an opcode defined with the given value.
|
|
"""
|
|
if val in BYTECODES:
|
|
raise ValueError("Opcode {} exists.")
|
|
return OpCode("MISSING", val, 0, 0)
|