evm-opcodes-0.3.0: src/EVM/Opcode.hs
{-# LANGUAGE PatternSynonyms #-}
-- |
-- Module: EVM.Opcode
-- Copyright: 2018-2026 Simon Shine
-- Maintainer: Simon Shine <simon@simonshine.dk>
-- License: MIT
--
-- This module exposes the 'Opcode' type for expressing Ethereum VM opcodes
-- as extracted from the Ethereum Yellow Paper with amendments from various
-- EIPs. The Yellow Paper is available at:
--
-- https://ethereum.github.io/yellowpaper/paper.pdf
--
-- The list of opcodes is found in appendix H.2.
--
-- But it is not always up-to-date, so keeping track of EIPs that add or
-- modify instructions is necessary. See comments in this module for the
-- references to these additions.
module EVM.Opcode
( -- * Types
Opcode
, Opcode'(..)
, OpcodeSpec(..)
, opcodeSpec
-- ** Pseudo-instructions and helper functions
, jump
, jumpi
, jumpdest
-- Extraction
, jumpAnnot
, jumpdestAnnot
-- ** Conversion and printing
, concrete
, opcodeText
, opcodeSize
, toHex
, pack
, toBytes
-- ** Parse and validate instructions
, isDUP
, isSWAP
, isLOG
, isPUSH
, readDUP
, readSWAP
, readLOG
, readPUSH
, readOp
-- ** Pattern synonyms
, pattern DUP1
, pattern DUP2
, pattern DUP3
, pattern DUP4
, pattern DUP5
, pattern DUP6
, pattern DUP7
, pattern DUP8
, pattern DUP9
, pattern DUP10
, pattern DUP11
, pattern DUP12
, pattern DUP13
, pattern DUP14
, pattern DUP15
, pattern DUP16
, pattern SWAP1
, pattern SWAP2
, pattern SWAP3
, pattern SWAP4
, pattern SWAP5
, pattern SWAP6
, pattern SWAP7
, pattern SWAP8
, pattern SWAP9
, pattern SWAP10
, pattern SWAP11
, pattern SWAP12
, pattern SWAP13
, pattern SWAP14
, pattern SWAP15
, pattern SWAP16
, pattern LOG0
, pattern LOG1
, pattern LOG2
, pattern LOG3
, pattern LOG4
)
where
import Control.Applicative ((<|>))
import Control.Monad (guard)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.DoubleWord (Word256, fromHiAndLo)
import qualified Data.List as List
import Data.Maybe (isJust)
import qualified Data.Serialize.Get as Cereal
import Data.String (IsString, fromString)
import Data.Text (Text)
import Data.Word (Word64, Word8)
import EVM.Opcode.Internal
import Text.Printf (printf)
import Prelude hiding (EQ, GT, LT)
-- | An 'Opcode' is a plain, parameterless Ethereum VM Opcode.
type Opcode = Opcode' ()
-- | 'jump' is a plain parameterless 'Opcode'.
jump :: Opcode
jump = JUMP ()
-- | 'jumpi' is a plain parameterless 'Opcode'.
jumpi :: Opcode
jumpi = JUMPI ()
-- | 'jumpdest' is a plain parameterless 'Opcode'.
jumpdest :: Opcode
jumpdest = JUMPDEST ()
-- | Determine if a byte represents a 'DUP' opcode ('DUP1' -- 'DUP16').
isDUP :: Word8 -> Bool
isDUP = isJust . readDUP
-- | Determine if a byte represents a 'SWAP' opcode ('SWAP1' -- 'SWAP16').
isSWAP :: Word8 -> Bool
isSWAP = isJust . readSWAP
-- | Determine if a byte represents a 'LOG' opcode ('LOG1' -- 'LOG4').
isLOG :: Word8 -> Bool
isLOG = isJust . readLOG
-- | Determine if a byte represents a 'PUSH' opcode.
isPUSH :: Word8 -> ByteString -> Bool
isPUSH b bs = isJust (readPUSH b bs)
-- | Read a 'DUP' opcode ('DUP1' -- 'DUP16') safely.
readDUP :: Word8 -> Maybe Opcode
readDUP b = do
guard (b >= 0x80 && b <= 0x8f)
pure (DUP (fromWord8 (b - 0x80)))
-- | Read a 'SWAP' opcode ('SWAP1' -- 'SWAP16') safely.
readSWAP :: Word8 -> Maybe Opcode
readSWAP b = do
guard (b >= 0x90 && b <= 0x9f)
pure (SWAP (fromWord8 (b - 0x90)))
-- | Read a 'LOG' opcode ('LOG1' -- 'LOG4') safely.
readLOG :: Word8 -> Maybe Opcode
readLOG b = do
guard (b >= 0xa0 && b <= 0xa4)
pure (LOG (fromWord8 (b - 0xa0)))
-- | Read a 'PUSH' opcode safely.
readPUSH :: Word8 -> ByteString -> Maybe Opcode
readPUSH b bs = do
guard (b >= 0x60 && b <= 0x7f)
let n = fromIntegral (b - 0x60 + 1)
PUSH <$> word256 (BS.take n bs)
-- | Parse an 'Opcode' from a 'Word8'. In case of 'PUSH' instructions, read the
-- constant being pushed from a subsequent 'ByteString'.
readOp :: Word8 -> ByteString -> Maybe Opcode
readOp word bs
= readDUP word
<|> readSWAP word
<|> readLOG word
<|> readPUSH word bs
<|> case word of
-- 0s: Stop and Arithmetic Operations
0x00 -> pure STOP
0x01 -> pure ADD
0x02 -> pure MUL
0x03 -> pure SUB
0x04 -> pure DIV
0x05 -> pure SDIV
0x06 -> pure MOD
0x07 -> pure SMOD
0x08 -> pure ADDMOD
0x09 -> pure MULMOD
0x0a -> pure EXP
0x0b -> pure SIGNEXTEND
-- 10s: Comparison & Bitwise Logic Operations
0x10 -> pure LT
0x11 -> pure GT
0x12 -> pure SLT
0x13 -> pure SGT
0x14 -> pure EQ
0x15 -> pure ISZERO
0x16 -> pure AND
0x17 -> pure OR
0x18 -> pure XOR
0x19 -> pure NOT
0x1a -> pure BYTE
0x1b -> pure SHL
0x1c -> pure SHR
0x1d -> pure SAR
-- 20s: KECCAK256
0x20 -> pure KECCAK256
-- 30s: Environmental Information
0x30 -> pure ADDRESS
0x31 -> pure BALANCE
0x32 -> pure ORIGIN
0x33 -> pure CALLER
0x34 -> pure CALLVALUE
0x35 -> pure CALLDATALOAD
0x36 -> pure CALLDATASIZE
0x37 -> pure CALLDATACOPY
0x38 -> pure CODESIZE
0x39 -> pure CODECOPY
0x3a -> pure GASPRICE
0x3b -> pure EXTCODESIZE
0x3c -> pure EXTCODECOPY
0x3d -> pure RETURNDATASIZE
0x3e -> pure RETURNDATACOPY
0x3f -> pure EXTCODEHASH
-- 40s: Block Information
0x40 -> pure BLOCKHASH
0x41 -> pure COINBASE
0x42 -> pure TIMESTAMP
0x43 -> pure NUMBER
0x44 -> pure PREVRANDAO
0x45 -> pure GASLIMIT
0x46 -> pure CHAINID
0x47 -> pure SELFBALANCE
0x48 -> pure BASEFEE
-- 50s: Stack, Memory, Storage and Flow Operations
0x50 -> pure POP
0x51 -> pure MLOAD
0x52 -> pure MSTORE
0x53 -> pure MSTORE8
0x54 -> pure SLOAD
0x55 -> pure SSTORE
0x56 -> pure jump
0x57 -> pure jumpi
0x58 -> pure PC
0x59 -> pure MSIZE
0x5a -> pure GAS
0x5b -> pure jumpdest
-- f0s: System Operations
0xf0 -> pure CREATE
0xf1 -> pure CALL
0xf2 -> pure CALLCODE
0xf3 -> pure RETURN
0xf4 -> pure DELEGATECALL
0xf5 -> pure CREATE2
0xfa -> pure STATICCALL
0xfd -> pure REVERT
0xfe -> pure INVALID
0xff -> pure SELFDESTRUCT
-- Unknown
_ -> Nothing
-- | Get a 'Word256' from a 'ByteString'
--
-- Pads the ByteString with NULs up to 32 bytes.
word256 :: ByteString -> Maybe Word256
word256 = eitherToMaybe . getWord256 . padLeft 32
where
getWord256 :: ByteString -> Either String Word256
getWord256 = Cereal.runGet $
fromWord64s <$> Cereal.getWord64be
<*> Cereal.getWord64be
<*> Cereal.getWord64be
<*> Cereal.getWord64be
fromWord64s :: Word64 -> Word64 -> Word64 -> Word64 -> Word256
fromWord64s a b c d = fromHiAndLo (fromHiAndLo a b) (fromHiAndLo c d)
padLeft :: Int -> ByteString -> ByteString
padLeft n xs = BS.replicate (n - BS.length xs) 0 <> xs
eitherToMaybe :: Either e a -> Maybe a
eitherToMaybe = either (const Nothing) pure
-- | Show 'Opcode' as 'Text'.
opcodeText :: Opcode -> Text
opcodeText = opcodeName . opcodeSpec
-- | Calculate the size in bytes of an encoded opcode. The only 'Opcode'
-- that uses more than one byte is 'PUSH'. Sizes are trivially determined
-- for only 'Opcode' with unlabelled jumps, since we cannot know e.g. where
-- the label of a 'EVM.Opcode.LabelledOpcode' points to before code generation
-- has completed.
opcodeSize :: Num i => Opcode -> i
opcodeSize (PUSH n) = List.genericLength . uncurry (:) $ push' n
opcodeSize _opcode = 1
-- | Convert a @['Opcode']@ to a string of ASCII hexadecimals.
toHex :: IsString s => [Opcode] -> s
toHex = fromString . List.concatMap (printf "%02x") . List.concatMap toBytes
-- | Convert a @['Opcode']@ to bytecode.
pack :: [Opcode] -> ByteString
pack = BS.pack . List.concatMap toBytes
-- | Convert an @'Opcode'@ to a @['Word8']@.
--
-- To convert many 'Opcode's to bytecode, use 'pack'.
toBytes :: Opcode -> [Word8]
toBytes (PUSH n) = uncurry (:) (push' n)
toBytes opcode = [ opcodeEncoding (opcodeSpec opcode) ]