diff --git a/cli/cli.hs b/cli/cli.hs index d3a86413e..d10ce8d6e 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -53,6 +53,7 @@ import EVM.Types qualified import EVM.UnitTest import EVM.Effects import EVM.Expr (maybeLitWordSimp, maybeLitAddrSimp) +import EVM.Futamura (compileAndRunSpecialized) data AssertionType = DSTest | Forge deriving (Eq, Show, Read) @@ -137,6 +138,7 @@ data CommonExecOptions = CommonExecOptions , gaslimit ::Maybe Word64 , gasprice ::Maybe W256 , maxcodesize ::Maybe W256 + , useFutamura ::Bool , prevRandao ::Maybe W256 , chainid ::Maybe W256 , rpc ::Maybe URL @@ -159,6 +161,7 @@ commonExecOptions = CommonExecOptions <*> (optional $ option auto $ long "gaslimit" <> help "Tx: gas limit") <*> (optional $ option auto $ long "gasprice" <> help "Tx: gas price") <*> (optional $ option auto $ long "maxcodesize" <> help "Block: max code size") + <*> (switch $ long "use-futamura" <> help "Use Futamura specialization for execution") <*> (optional $ option auto $ long "prev-randao" <> help "Block: prevRandao") <*> (optional $ option auto $ long "chainid" <> help "Env: chainId") <*> rpcParser @@ -564,7 +567,11 @@ launchExec cFileOpts execOpts cExecOpts cOpts = do -- TODO: we shouldn't need solvers to execute this code withSolvers Z3 0 1 Nothing $ \solvers -> do - vm' <- EVM.Stepper.interpret (Fetch.oracle solvers rpcinfo) vm EVM.Stepper.runFully + vm' <- case cExecOpts.useFutamura of + True -> do f <- liftIO (compileAndRunSpecialized vm) + return $ f vm + False -> EVM.Stepper.interpret (Fetch.oracle solvers rpcinfo) vm EVM.Stepper.runFully + writeTraceDapp dapp vm' case vm'.result of Just (VMFailure (Revert msg)) -> liftIO $ do diff --git a/hevm.cabal b/hevm.cabal index debd39c4d..e27a8ad99 100644 --- a/hevm.cabal +++ b/hevm.cabal @@ -2,7 +2,7 @@ cabal-version: 3.0 name: hevm version: - 0.54.2 + 0.54.3 synopsis: Symbolic EVM Evaluator description: @@ -104,6 +104,8 @@ library EVM.Format, EVM.Fetch, EVM.FeeSchedule, + EVM.Opcodes, + EVM.Futamura, EVM.Op, EVM.Precompiled, EVM.RLP, @@ -141,6 +143,11 @@ library install-includes: ethjet/tinykeccak.h, ethjet/ethjet.h, ethjet/ethjet-ff.h, ethjet/blake2.h build-depends: + ghc >= 9.6 && < 10, + ghc-paths >= 0.1, + ghc-boot-th >= 9.6 && < 10, + directory >= 1.3, + temporary >= 1.2.0 && < 1.4, system-cxx-std-lib >= 1.0 && < 2.0, QuickCheck >= 2.13.2 && < 2.15, Decimal >= 0.5.1 && < 0.6, @@ -196,7 +203,7 @@ executable hevm cli main-is: cli.hs - ghc-options: -threaded -with-rtsopts=-N + ghc-options: -threaded -with-rtsopts=-N -optl-rdynamic other-modules: Paths_hevm autogen-modules: diff --git a/src/EVM/Futamura.hs b/src/EVM/Futamura.hs new file mode 100644 index 000000000..74d87cd3e --- /dev/null +++ b/src/EVM/Futamura.hs @@ -0,0 +1,499 @@ +{-# LANGUAGE ImpredicativeTypes #-} + +module EVM.Futamura where + +import Control.Monad.State.Strict +import Control.Monad.ST +import System.Directory (getTemporaryDirectory) +import System.IO.Temp (createTempDirectory) +import System.FilePath +import System.Process (readProcess) +import Control.Exception (catch, IOException) +import Data.Word (Word8) +import Data.List (isPrefixOf, dropWhileEnd, intercalate, foldl', find) +import Data.Maybe (catMaybes, listToMaybe) +import Data.Char (isSpace) +import Data.Map qualified as Map +import Data.IntSet qualified as IntSet +import Data.IntMap.Lazy (IntMap, lookup, fromList) +import Data.Bits (shiftL) +import Prelude hiding (lookup) +import Unsafe.Coerce + +import GHC (SuccessFlag(..), compileExpr, mkModuleName, simpleImportDecl, InteractiveImport(..), setContext, LoadHowMuch(..), load, setTargets, guessTarget, setSessionDynFlags, getSessionDynFlags, runGhc) +import GHC.Paths (libdir) +import GHC.LanguageExtensions.Type (Extension(..)) +import GHC.Driver.Session --(PackageFlag(..), PackageArg(..), ModRenaming(..), PackageDBFlag(..), PkgDbRef(..), xopt_set) +import GHC.Driver.Monad (Ghc) +import GHC.Driver.Flags (DumpFlag(..)) + +import EVM.Opcodes (opcodesImpl) +import EVM (currentContract, opslen) +import EVM.Op (getOp, opToWord8) +import EVM.Types (VM, GenericOp(..), ContractCode(..), RuntimeCode(..), contract, code, result, state, pc, VMResult(..), Expr(ConcreteBuf), EvmError(..)) + +import qualified Data.ByteString as BS + +-- Code from Halive + +-- | Extract the sandbox package db directory from the cabal.sandbox.config file. +-- Exception is thrown if the sandbox config file is broken. +extractKey :: String -> String -> Maybe FilePath +extractKey key conf = extractValue <$> parse conf + where + keyLen = length key + + parse = listToMaybe . filter (key `isPrefixOf`) . lines + extractValue = dropWhileEnd isSpace . dropWhile isSpace . drop keyLen + +------------------------ +---------- Stack project +------------------------ + +-- | Get path to the project's snapshot and local package DBs via 'stack path' +getStackDb :: IO (Maybe [FilePath]) +getStackDb = do + pathInfo <- readProcess "stack" ["path"] "" `catch` (\(_e::IOException) -> return []) + return . Just . catMaybes $ map (flip extractKey pathInfo) + ["global-pkg-db:", "local-pkg-db:", "snapshot-pkg-db:"] + +updateDynFlagsWithStackDB :: MonadIO m => DynFlags -> m DynFlags +updateDynFlagsWithStackDB dflags = + liftIO getStackDb >>= \case + Nothing -> error "Failed to get stack package DBs. Ensure you are in a Stack project." + Just stackDBs -> do + liftIO $ putStrLn $ "Using Stack package DBs: " ++ show stackDBs + let pkgs = map (PackageDB . PkgDbPath) stackDBs + return dflags { packageDBFlags = pkgs ++ packageDBFlags dflags } + +-------------------------------------------------------------------------------- +-- | Generate Haskell Code From a List of Opcodes +-------------------------------------------------------------------------------- + +generateHaskellCode :: Map.Map Int BasicBlock -> String +generateHaskellCode cfg = + unlines $ + [ "{-# LANGUAGE ImplicitParams #-}" + , "module Generated where" + , "import Optics.Core" + , "import Optics.State" + , "import Optics.State.Operators" + , "import Optics.Zoom" + , "import Optics.Operators.Unsafe" + , "" + , "import Control.Monad.State.Strict" + , "import Control.Monad.ST" + , "import Data.Word (Word8, Word64)" + , "import Witch.From (From)" + , "import Witch (into, tryInto)" + , "import Data.ByteString qualified as BS" + , "import Data.Vector qualified as V" + , "" + , "import EVM hiding (stackOp2, next)" + , "import EVM.Types" + , "import EVM.Op" + , "import EVM.Expr qualified as Expr" + , "import EVM.Effects (defaultConfig, maxDepth)" + , "import EVM.FeeSchedule (FeeSchedule (..))" + , "" ] + ++ map genOpImpl opcodesImpl + ++ [""] + ++ map (genBasicBlockImpl cfg) (Map.elems cfg) + +-- | Generates a function name for a basic block based on its start PC. +genBasicBlockFuncName :: BasicBlock -> String +genBasicBlockFuncName block = "runBlock_" ++ show (fst block.bbRange) + +-- | Generates the Haskell code for a single BasicBlock. +genBasicBlockImpl :: Map.Map Int BasicBlock -> BasicBlock -> String +genBasicBlockImpl cfg block = + let + funcName = genBasicBlockFuncName block + -- Generate a `runOpcode` call for ALL opcodes in the block. + opCodeStmts = map ((" " ++) . genOp) block.bbOps + -- Generate the final control-flow transfer statement. + successorStmt = " " ++ genSuccessorDispatch cfg block + in + unlines $ + [ --"{-# INLINE " ++ funcName ++ " #-}", + funcName ++ " :: StateT (VM Concrete s) (ST s) ()", + funcName ++ " = do" + ] ++ opCodeStmts ++ [successorStmt] + +-- | Generates the final line of a block function, which dispatches to the next block. +-- This code runs AFTER all opcodes in the block have been executed. +genSuccessorDispatch :: Map.Map Int BasicBlock -> BasicBlock -> String +genSuccessorDispatch cfg block = case block.bbSuccessor of + -- The last opcode was not a terminator. Directly call the next block's function. + -- This overrides any PC change from the last opcode. + FallthroughTo pc -> if Map.member pc cfg then genBasicBlockFuncName (cfg Map.! pc) else "pure ()" + + -- The JUMP opcode was executed (consuming gas & stack), but we ignore its + -- effect on the PC and instead make a direct call to the static destination. + StaticJump pc -> genBasicBlockFuncName (cfg Map.! pc) + + -- The JUMPI opcode was executed (consuming gas & stack). We then check the + -- vm.result to see if it failed (e.g., stack underrun). If not, we know + -- the pc was set to one of two values, but we ignore that and make our own + -- direct call based on the *original* condition, which is now gone from the stack. + -- This is a flaw. The logic must be to check the PC set by the JUMPI. + -- + -- CORRECTED LOGIC: The `runOpcode OpJumpi` has already set the PC to either + -- the destination or pc+1. We now read that PC and dispatch accordingly. + ConditionalJump truePc falsePc -> + unlines + [ "vm <- get" + , " if vm.state.pc == " ++ show truePc + , " then " ++ genBasicBlockFuncName (cfg Map.! truePc) + , " else " ++ genBasicBlockFuncName (cfg Map.! falsePc) + ] + + DynamicJump -> + "error \"JIT Error: Dynamic jumps are not supported.\"" + + Terminal -> + -- The last executed opcode (e.g., runOpcode OpStop) has set vm.result. + -- There is nothing more to do, the machine has halted. + "pure ()" + +-- | Generates a call to the original `runOpcode` interpreter function. +genOpCall :: GenericOp Word8 -> String +genOpCall op = + let opcodeByte = opToWord8 op + in "do { vm <- get; case vm.result of Just _ -> pure (); Nothing -> runOpcode " ++ show opcodeByte ++ " }" + +-- | Data Structures for the Control Flow Graph +type BasicBlockRange = (Int, Int) + +-- A single node in our Control Flow Graph. +data BasicBlock = BasicBlock + { bbRange :: (Int, Int) -- (start_pc, end_pc_exclusive) + , bbOps :: [GenericOp Word8] -- Opcodes within the block + , bbSuccessor :: Successor -- How this block connects to others + } deriving (Show, Eq) + +-- Represents where control flows after a basic block finishes. +data Successor + = FallthroughTo Int -- Statically known PC of the next instruction + | StaticJump Int -- A JUMP to a constant, known destination PC + | ConditionalJump Int Int -- A JUMPI to a constant destination and a fallthrough PC + | DynamicJump -- A JUMP or JUMPI to an unknown (dynamic) destination + | Terminal -- Block ends with STOP, REVERT, RETURN, etc. + deriving (Show, Eq) + +-- A fully decoded instruction with its position and raw data. +data Instruction = Instruction + { instrPC :: Int + , instrOp :: GenericOp Word8 + , instrData :: BS.ByteString -- The raw bytes for a PUSH, otherwise empty + } deriving (Show) + +-- | Core CFG Construction Logic + +-- The main function to build the CFG. +-- Returns a map from a block's starting PC to the BasicBlock itself. +buildCFG :: BS.ByteString -> Map.Map Int BasicBlock +buildCFG bytecode = + let + instrs = disassemble bytecode + leaders = findLeaders instrs + -- 1. Form the complete CFG for the entire bytecode. + fullCfg = formBlocks instrs leaders + -- 2. Filter out the data section at the end. + filteredCfg = filterDataSection fullCfg + in + filteredCfg + +-- ... (The rest of your CFG functions: disassemble, findLeaders, formBlocks, etc., remain the same) + +-- Disassembles bytecode into a stream of instructions. +disassemble :: BS.ByteString -> [Instruction] +disassemble bs = go 0 + where + go pc + | pc >= BS.length bs = [] + | otherwise = + let opcodeByte = BS.index bs pc + op = getOp opcodeByte + in case op of + OpPush n -> + let numBytes = fromIntegral n + start = pc + 1 + end = start + numBytes + in if end > BS.length bs then + -- This PUSH reads past the end of the bytecode. It's an invalid instruction. + let invalidInstr = Instruction pc (OpUnknown opcodeByte) BS.empty + in invalidInstr : go (pc + 1) + else + let pushData = BS.take numBytes (BS.drop start bs) + instr = Instruction pc op pushData + nextPc = pc + 1 + numBytes + in instr : go nextPc + _ -> + let instr = Instruction pc op BS.empty + nextPc = pc + 1 + in instr : go nextPc + +-- Finds all "leaders" - instructions that start a basic block. +findLeaders :: [Instruction] -> IntSet.IntSet +findLeaders instrs = IntSet.insert 0 $ fst $ foldl' go (IntSet.empty, False) instrs + where + go (leaders, wasTerminator) instr = + let currentPc = instr.instrPC + -- If the previous instruction was a terminator, this one is a leader. + leaders' = if wasTerminator then IntSet.insert currentPc leaders else leaders + -- If this instruction is a JUMPDEST, it's a leader. + finalLeaders = if isLeaderOp instr.instrOp then IntSet.insert currentPc leaders' else leaders' + in (finalLeaders, isTerminatorOp instr.instrOp) + +-- Forms a Map of BasicBlocks from the instruction stream and leader set. +formBlocks :: [Instruction] -> IntSet.IntSet -> Map.Map Int BasicBlock +formBlocks [] _ = Map.empty +formBlocks instrs leaders = + let (blockInstrs, restInstrs) = span (not . isLeaderAfterFirst) (tail instrs) + firstInstr = head instrs + -- The first block always starts at PC 0. + currentBlock = firstInstr : blockInstrs + startPc = firstInstr.instrPC + -- The block ends right before the next leader (or at the end of the code). + endPc = case restInstrs of + [] -> let lastI = last currentBlock in lastI.instrPC + instructionByteSize lastI + (nextLeader:_) -> nextLeader.instrPC + block = BasicBlock + { bbRange = (startPc, endPc) + , bbOps = map (.instrOp) currentBlock + , bbSuccessor = analyzeSuccessor currentBlock + } + in Map.insert startPc block (formBlocks restInstrs leaders) + where + isLeaderAfterFirst instr = IntSet.member (instr.instrPC) leaders + +-- Analyzes the last instruction(s) of a block to find its successor type. +analyzeSuccessor :: [Instruction] -> Successor +analyzeSuccessor instrs = + case reverse instrs of + [] -> Terminal -- Should not happen for a non-empty block + + -- JUMP case: Look for a preceding PUSH + (jumpInstr : pushInstr : _) + | jumpInstr.instrOp == OpJump, isPush pushInstr.instrOp -> + let dest = bytesToInteger (BS.unpack pushInstr.instrData) + in StaticJump (fromInteger dest) + + -- JUMPI case: Look for a preceding PUSH + (jumpiInstr : pushInstr : _) + | jumpiInstr.instrOp == OpJumpi, isPush pushInstr.instrOp -> + let dest = bytesToInteger (BS.unpack pushInstr.instrData) + -- CORRECTED: Fallthrough is the PC of the instruction *after* the JUMPI + fallthroughPc = jumpiInstr.instrPC + instructionByteSize jumpiInstr + in ConditionalJump (fromInteger dest) fallthroughPc + + -- Default cases for dynamic jumps and terminators + (lastInstr : _) -> + let op = lastInstr.instrOp + pc = lastInstr.instrPC + size = instructionByteSize lastInstr + in case op of + OpJump -> DynamicJump + OpJumpi -> DynamicJump + _ | isTerminatorOp op -> Terminal + | otherwise -> FallthroughTo (pc + size) + +-- | Helper Functions + +instructionByteSize :: Instruction -> Int +instructionByteSize instr = case instr.instrOp of + OpPush n -> 1 + fromIntegral n + _ -> 1 + +isPush :: GenericOp a -> Bool +isPush (OpPush _) = True +isPush _ = False + +bytesToInteger :: [Word8] -> Integer +bytesToInteger = foldl' (\acc byte -> (acc `shiftL` 8) + fromIntegral byte) 0 + +isLeaderOp :: GenericOp Word8 -> Bool +isLeaderOp OpJumpdest = True +isLeaderOp _ = False + +isTerminatorOp :: GenericOp Word8 -> Bool +isTerminatorOp op = case op of + OpJump -> True + OpJumpi -> True + OpStop -> True + OpRevert -> True + OpReturn -> True + OpSelfdestruct -> True -- FIX: Was missing + OpUnknown _ -> True -- FIX: Was missing + _ -> False + + +-- | Determines if a basic block is the start of the non-executable data section. +-- According to the pattern, this is a block that starts with an unknown/invalid +-- opcode but is not a simple, single invalid instruction. +isDataSectionBlock :: BasicBlock -> Bool +isDataSectionBlock block = + case block.bbOps of + -- An empty block isn't the pattern, but it's good to consider it invalid. + [] -> True + -- The key pattern: starts with OpUnknown but has more content. + (OpUnknown _ : _ : _) -> True + -- Any other case is a valid executable block. + _ -> False + +-- | Filters the CFG to remove blocks that are part of the contract's metadata section. +-- It finds the first block that looks like a data section marker and removes +-- it and all blocks that come after it. +filterDataSection :: Map.Map Int BasicBlock -> Map.Map Int BasicBlock +filterDataSection cfg = + -- Find the first block that matches our data section pattern. + -- `find` on a Map returns the first element in key-order. + case find isDataSectionBlock (Map.elems cfg) of + -- No data section block was found; the entire CFG is valid. + Nothing -> cfg + -- We found a data section block. Its starting PC is the cutoff point. + Just invalidBlock -> + let + (startPc, _) = invalidBlock.bbRange + -- `split` divides the map into elements < separator and >= separator. + -- We just want the part that comes before the invalid block. + (executableBlocks, _) = Map.split startPc cfg + in + executableBlocks + +-- | Generate Haskell code for each opcode implementation. + +genOpImpl :: (String, String, String, String, Bool) -> String +genOpImpl (opName, opParams, typeSig, src, True) = + --"{-# INLINE runOpcode" ++ opName ++ " #-}\n" ++ + "runOpcode" ++ opName ++ " :: " ++ typeSig ++ "\n" ++ + "runOpcode" ++ opName ++ opParams ++ " = " ++ src ++ "\n" + +genOpImpl (opName, opParams, typeSig, src, False) = + --"{-# INLINE " ++ opName ++ " #-}\n" ++ + opName ++ " :: " ++ typeSig ++ "\n" ++ + opName ++ opParams ++ " = " ++ src ++ "\n" + +checkIfVmResulted :: String -> String +checkIfVmResulted op = + " get >>= \\vm ->\n" ++ + " case vm.result of\n" ++ + " Nothing ->" ++ op ++ "\n" ++ + " Just r -> return ()" + +genOp :: GenericOp Word8 -> String +genOp (OpPush0) = "let ?op = opToWord8(OpPush0) in runOpcodePush0" +genOp (OpRevert) = "let ?op = opToWord8(OpRevert) in runOpcodeRevert" +genOp (OpStop) = "let ?op = opToWord8(OpStop) in runOpcodeStop" +genOp (OpAdd) = "let ?op = opToWord8(OpAdd) in runOpcodeAdd" +genOp (OpDup i) = "let ?op = opToWord8(OpDup " ++ show i ++ ") in runOpcodeDup (" ++ show i ++ " :: Int)" +genOp (OpSwap i) = "let ?op = opToWord8(OpSwap " ++ show i ++ ") in runOpcodeSwap (" ++ show i ++ " :: Int)" +genOp (OpMul) = "let ?op = opToWord8(OpMul) in runOpcodeMul" +genOp (OpSub) = "let ?op = opToWord8(OpSub) in runOpcodeSub" +genOp (OpDiv) = "let ?op = opToWord8(OpDiv) in runOpcodeDiv" +genOp (OpMod) = "let ?op = opToWord8(OpMod) in runOpcodeMod" +genOp (OpJumpi) = "let ?op = opToWord8(OpJumpi) in runOpcodeJumpi" +genOp (OpJump) = "let ?op = opToWord8(OpJump) in runOpcodeJump" +genOp (OpJumpdest) = "let ?op = opToWord8(OpJumpdest) in runOpcodeJumpdest" +genOp (OpPush n) = "let ?op = opToWord8(OpPush " ++ show n ++ ") in runOpcodePush (" ++ show n ++ " :: Int)" +genOp (OpPop) = "let ?op = opToWord8(OpPop) in runOpcodePop" +genOp (OpMstore) = "let ?op = opToWord8(OpMstore) in runOpcodeMStore" +genOp (OpMload) = "let ?op = opToWord8(OpMload) in runOpcodeMLoad" +genOp (OpSlt) = "let ?op = opToWord8(OpSlt) in runOpcodeSlt" +genOp (OpIszero) = "let ?op = opToWord8(OpIszero) in runOpcodeIsZero" +genOp (OpEq) = "let ?op = opToWord8(OpEq) in runOpcodeEq" +genOp (OpUnknown _) = "let ?op = 1 in runOpcodeRevert" --"error \"Unknown opcode: " ++ show n ++ "\"" +-- Add more opcodes as needed +genOp other = "pure ()" --error $ "Opcode not supported in specialization: " ++ show other + +-- | Compile and return a function that runs the specialized VM +-- This function will generate Haskell code based on the current contract's opcodes, +-- compile it using GHC API, and return a function that can be used to run +-- the specialized VM. +-- The generated code will be placed in a temporary directory. +compileAndRunSpecialized :: forall t s. VM t s -> IO (VM t s -> VM t s) +compileAndRunSpecialized vm = do + tmpDir <- getTemporaryDirectory >>= \tmp -> createTempDirectory tmp "evmjit" + let contract = currentContract vm + let bs = case contract of + Nothing -> error "No current contract found in VM" + Just c -> extractCode $ c.code + + let cfg = buildCFG bs --filterBasicBlocks $ splitBasicBlocks bs + putStrLn $ "Splitting into basic blocks: " ++ show cfg + let hsPath = tmpDir "Generated.hs" + putStrLn $ "Generating Haskell code for EVM specialization in: " ++ hsPath + writeFile hsPath (generateHaskellCode cfg) + + f <- dynCompileAndRun hsPath "runBlock_0" + return $ \x -> runST (unsafeCoerce $ execStateT f x) + where extractCode (RuntimeCode (ConcreteRuntimeCode ops)) = ops + extractCode _ = error "Expected ConcreteRuntimeCode" + +-------------------------------------------------------------------------------- +-- | Use GHC API to Compile and Run the Generated Code +-------------------------------------------------------------------------------- + +neededExtensionFlags :: [Extension] +neededExtensionFlags = + [ DuplicateRecordFields + , LambdaCase + , OverloadedRecordDot + , OverloadedStrings + , OverloadedLabels + , RecordWildCards + , TypeFamilies + , ViewPatterns + , DataKinds + , ImportQualifiedPost + , TraditionalRecordSyntax + , ImplicitParams + , FlexibleInstances + , ConstraintKinds + , DisambiguateRecordFields + , MonoLocalBinds + ] + +usefulDumpFlags :: [DumpFlag] +usefulDumpFlags = + [ Opt_D_dump_simpl] + +usefulGeneralFlags :: [GeneralFlag] +usefulGeneralFlags = + [ Opt_DumpToFile ] + +dynCompileAndRun :: forall t s. FilePath -> String -> IO (StateT (VM t s) (ST s) ()) +dynCompileAndRun filePath startBlockName = runGhc (Just libdir) $ do + dflags0 <- getSessionDynFlags + dflags1 <- updateDynFlagsWithStackDB dflags0 + + let dflags2 = foldl dopt_set dflags1 usefulDumpFlags + let dflags3 = foldl gopt_set dflags2 usefulGeneralFlags + let dflags = foldl xopt_set dflags3 neededExtensionFlags + _ <- setSessionDynFlags $ updOptLevel 2 $ dflags { + language = Just GHC2021, + verbosity = 1, + debugLevel = 1 + } + + target <- guessTarget filePath Nothing Nothing + setTargets [target] + -- Compile the file with the GHC API. + result <- load LoadAllTargets + case result of + Failed -> liftIO $ error "Failed to load targets" + Succeeded -> return () + + setContext [IIDecl $ simpleImportDecl (mkModuleName "Generated")] + liftIO $ putStrLn "Compilation successful, returning start function." + startBlock <- extractBasicBlockFunction startBlockName + + return startBlock + + where + extractBasicBlockFunction bbName = do + value <- compileExpr ("Generated." ++ bbName) + let specialized :: forall s1. StateT (VM t s) (ST s1) () + specialized = unsafeCoerce value + return specialized \ No newline at end of file diff --git a/src/EVM/Op.hs b/src/EVM/Op.hs index 410bd9e35..d3c6c76c9 100644 --- a/src/EVM/Op.hs +++ b/src/EVM/Op.hs @@ -5,6 +5,7 @@ module EVM.Op , intToOpName , getOp , readOp + , opToWord8 ) where import EVM.Expr qualified as Expr @@ -359,3 +360,90 @@ getOp x = case x of 0xfa -> OpStaticcall 0xff -> OpSelfdestruct _ -> OpUnknown x + +-- | Convert a 'GenericOp' to an Word8 +opToWord8 :: GenericOp Word8 -> Word8 +opToWord8 (OpDup n) = 0x80 + fromIntegral (n - 1) +opToWord8 (OpSwap n) = 0x90 + fromIntegral (n - 1) +opToWord8 (OpLog n) = 0xa0 + fromIntegral n +opToWord8 (OpPush n) = 0x60 + fromIntegral (n - 1) +opToWord8 OpPush0 = 0x5f +opToWord8 OpStop = 0x00 +opToWord8 OpAdd = 0x01 +opToWord8 OpMul = 0x02 +opToWord8 OpSub = 0x03 +opToWord8 OpDiv = 0x04 +opToWord8 OpSdiv = 0x05 +opToWord8 OpMod = 0x06 +opToWord8 OpSmod = 0x07 +opToWord8 OpAddmod = 0x08 +opToWord8 OpMulmod = 0x09 +opToWord8 OpExp = 0x0a +opToWord8 OpSignextend = 0x0b +opToWord8 OpLt = 0x10 +opToWord8 OpGt = 0x11 +opToWord8 OpSlt = 0x12 +opToWord8 OpSgt = 0x13 +opToWord8 OpEq = 0x14 +opToWord8 OpIszero = 0x15 +opToWord8 OpAnd = 0x16 +opToWord8 OpOr = 0x17 +opToWord8 OpXor = 0x18 +opToWord8 OpNot = 0x19 +opToWord8 OpByte = 0x1a +opToWord8 OpShl = 0x1b +opToWord8 OpShr = 0x1c +opToWord8 OpSar = 0x1d +opToWord8 OpSha3 = 0x20 +opToWord8 OpAddress = 0x30 +opToWord8 OpBalance = 0x31 +opToWord8 OpOrigin = 0x32 +opToWord8 OpCaller = 0x33 +opToWord8 OpCallvalue = 0x34 +opToWord8 OpCalldataload = 0x35 +opToWord8 OpCalldatasize = 0x36 +opToWord8 OpCalldatacopy = 0x37 +opToWord8 OpCodesize = 0x38 +opToWord8 OpCodecopy = 0x39 +opToWord8 OpGasprice = 0x3a +opToWord8 OpExtcodesize = 0x3b +opToWord8 OpExtcodecopy = 0x3c +opToWord8 OpReturndatasize = 0x3d +opToWord8 OpReturndatacopy = 0x3e +opToWord8 OpExtcodehash = 0x3f +opToWord8 OpBlockhash = 0x40 +opToWord8 OpCoinbase = 0x41 +opToWord8 OpTimestamp = 0x42 +opToWord8 OpNumber = 0x43 +opToWord8 OpPrevRandao = 0x44 +opToWord8 OpGaslimit = 0x45 +opToWord8 OpChainid = 0x46 +opToWord8 OpSelfbalance = 0x47 +opToWord8 OpBaseFee = 0x48 +opToWord8 OpBlobhash = 0x49 +opToWord8 OpBlobBaseFee = 0x4a +opToWord8 OpPop = 0x50 +opToWord8 OpMcopy = 0x5e +opToWord8 OpMload = 0x51 +opToWord8 OpMstore = 0x52 +opToWord8 OpMstore8 = 0x53 +opToWord8 OpSload = 0x54 +opToWord8 OpSstore = 0x55 +opToWord8 OpTload = 0x5c +opToWord8 OpTstore = 0x5d +opToWord8 OpJump = 0x56 +opToWord8 OpJumpi = 0x57 +opToWord8 OpPc = 0x58 +opToWord8 OpMsize = 0x59 +opToWord8 OpGas = 0x5a +opToWord8 OpJumpdest = 0x5b +opToWord8 OpCreate = 0xf0 +opToWord8 OpCall = 0xf1 +opToWord8 OpStaticcall = 0xfa +opToWord8 OpCallcode = 0xf2 +opToWord8 OpReturn = 0xf3 +opToWord8 OpDelegatecall = 0xf4 +opToWord8 OpCreate2 = 0xf5 +opToWord8 OpSelfdestruct = 0xff +opToWord8 OpRevert = 0xfd +opToWord8 (OpUnknown x) = x diff --git a/src/EVM/Opcodes.hs b/src/EVM/Opcodes.hs new file mode 100644 index 000000000..5ab6c8ea2 --- /dev/null +++ b/src/EVM/Opcodes.hs @@ -0,0 +1,611 @@ +{-# LANGUAGE TemplateHaskell, ImplicitParams #-} + +module EVM.Opcodes where + +import Optics.Core +import Optics.State +import Optics.State.Operators +import Optics.Zoom +import Optics.Operators.Unsafe + + +import Control.Monad.ST (ST) +import Control.Monad.State.Strict (StateT, get, put, gets) +import Witch.From (From) +import Witch (into, tryInto) +import Data.Word (Word8) +import Data.Vector qualified as V +import Data.ByteString qualified as BS + +import EVM +import EVM.Types +import EVM.FeeSchedule (FeeSchedule (..)) +import EVM.Expr qualified as Expr +import EVM.Effects (defaultConfig, maxDepth) + +--{-# INLINE modifyState #-} +--modifyState :: (FrameState t s -> FrameState t s) -> VM t s -> VM t s +--modifyState f vm = vm { state = f (vm.state) } + +runOpcodeAdd :: (VMOps t, ?op::Word8) => StateT (VM t s) (ST s) () +runOpcodeAdd = do + vm <- get + let FeeSchedule {..} = vm.block.schedule + let stk = vm.state.stack + case stk of + x:y:xs -> burn g_verylow $ do + next + vm' <- get + let add (Lit a) (Lit b) = Lit (a + b) + add a b = Expr.add a b + put $ modifyState (vm'.state { stack = add x y : xs }) vm' + _ -> underrun + where modifyState :: FrameState t s -> VM t s -> VM t s + modifyState st vm = vm { state = st } + +runOpcodeAddSrc :: String +runOpcodeAddSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ let stk = vm.state.stack\n\ +\ case stk of\n\ +\ x:y:xs -> burn g_verylow $ do\n\ +\ next\n\ +\ vm' <- get\n\ +\ let add (Lit a) (Lit b) = Lit (a + b)\n\ +\ add a b = Expr.add a b\n\ +\ put $ modifyState (vm'.state { stack = add x y : xs }) vm'\n\ +\ _ -> underrun\n\ +\ where modifyState :: FrameState t s -> VM t s -> VM t s\n\ +\ modifyState st vm = vm { state = st }" + +runOpcodeAddType :: String +runOpcodeAddType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeMul :: (VMOps t, ?op::Word8) => StateT (VM t s) (ST s) () +runOpcodeMul = do + vm <- get + let + FeeSchedule {..} = vm.block.schedule + stackOp2 g_low Expr.mul + +runOpcodeMulSrc :: String +runOpcodeMulSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ let stk = vm.state.stack\n\ +\ case stk of\n\ +\ x:y:xs -> burn g_low $ do\n\ +\ next\n\ +\ vm' <- get\n\ +\ let f (Lit a) (Lit b) = Lit (a * b)\n\ +\ f a b = Expr.mul a b\n\ +\ put $ modifyState (vm'.state { stack = f x y : xs }) vm'\n\ +\ _ -> underrun\n\ +\ where modifyState :: FrameState t s -> VM t s -> VM t s\n\ +\ modifyState st vm = vm { state = st }" + +runOpcodeMulType :: String +runOpcodeMulType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeSub :: (VMOps t, ?op::Word8) => StateT (VM t s) (ST s) () +runOpcodeSub = do + vm <- get + let + FeeSchedule {..} = vm.block.schedule + stackOp2 g_verylow Expr.sub + +runOpcodeSubSrc :: String +runOpcodeSubSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ let stk = vm.state.stack\n\ +\ case stk of\n\ +\ x:y:xs -> burn g_low $ do\n\ +\ next\n\ +\ vm' <- get\n\ +\ let f (Lit a) (Lit b) = Lit (a - b)\n\ +\ f a b = Expr.sub a b\n\ +\ put $ modifyState (vm'.state { stack = f x y : xs }) vm'\n\ +\ _ -> underrun\n\ +\ where modifyState :: FrameState t s -> VM t s -> VM t s\n\ +\ modifyState st vm = vm { state = st }" + +runOpcodeSubType :: String +runOpcodeSubType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeDiv :: (VMOps t, ?op::Word8) => StateT (VM t s) (ST s) () +runOpcodeDiv = do + vm <- get + let + FeeSchedule {..} = vm.block.schedule + stackOp2 g_low Expr.div + +runOpcodeDivSrc :: String +runOpcodeDivSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ stackOp2 g_low Expr.div" + +runOpcodeDivType :: String +runOpcodeDivType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeMod :: (VMOps t, ?op::Word8) => StateT (VM t s) (ST s) () +runOpcodeMod = do + vm <- get + let + FeeSchedule {..} = vm.block.schedule + stackOp2 g_low Expr.mod + +runOpcodeModSrc :: String +runOpcodeModSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ stackOp2 g_low Expr.mod" + +runOpcodeModType :: String +runOpcodeModType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +{-# INLINE runOpcodeDup #-} +runOpcodeDup :: (From source Int, VMOps t, ?op::Word8) => + source -> StateT (VM t s) (ST s) () +runOpcodeDup i = do + vm <- get + let + stk = vm.state.stack + FeeSchedule {..} = vm.block.schedule + let remainingStack = drop (into i - 1) stk + case remainingStack of + [] -> underrun + (x:_) -> limitStack 1 $ + burn g_verylow $ do + next + pushSym x + +runOpcodeDupSrc :: String +runOpcodeDupSrc = "do\n\ +\ vm <- get\n\ +\ let stk = vm.state.stack\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ let remainingStack = drop (into i - 1) stk\n\ +\ case remainingStack of\n\ +\ [] -> underrun\n\ +\ (x:_) -> limitStack 1 $\n\ +\ burn g_verylow $ do\n\ +\ next\n\ +\ pushSym x" + +runOpcodeDupType :: String +runOpcodeDupType = "(From source Int, VMOps t, ?op::Word8) => source -> StateT (VM t s) (ST s) ()" + +runOpcodeSwap :: (?op::Word8, VMOps t, From source Int) => source -> StateT (VM t s) (ST s) () +runOpcodeSwap i = do + vm <- get + let stk = vm.state.stack + let FeeSchedule {..} = vm.block.schedule + case (stk ^? ix_i, stk ^? ix_0) of + (Just ei, Just e0) -> + burn g_verylow $ do + next + zoom (#state % #stack) $ do + ix_i .= e0 + ix_0 .= ei + _ -> underrun + where + (ix_i, ix_0) = (ix (into i), ix 0) + --where modifyState :: FrameState t s -> VM t s -> VM t s + -- modifyState st vm = vm { state = st } + +runOpcodeSwapSrc :: String +runOpcodeSwapSrc = "do\n\ +\ vm <- get\n\ +\ let stk = vm.state.stack\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ case (stk ^? ix_i, stk ^? ix_0) of\n\ +\ (Just ei, Just e0) ->\n\ +\ burn g_verylow $ do\n\ +\ next\n\ +\ zoom (#state % #stack) $ do\n\ +\ ix_i .= e0\n\ +\ ix_0 .= ei\n\ +\ _ -> underrun\n\ +\ where\n\ +\ (ix_i, ix_0) = (ix (into i), ix 0)\n" + + +runOpcodeSwapType :: String +runOpcodeSwapType = "(?op::Word8, VMOps t, From source Int) => source -> StateT (VM t s) (ST s) ()" + +runOpcodePush0 :: (VMOps t, ?op::Word8) => + StateT (VM t s) (ST s) () +runOpcodePush0 = do + vm <- get + let FeeSchedule {..} = vm.block.schedule + limitStack 1 $ + burn g_base $ do + next + pushSym (Lit 0) + +runOpcodePush0Type :: String +runOpcodePush0Type = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodePush0Src :: String +runOpcodePush0Src = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ limitStack 1 $\n\ +\ burn g_base $ do\n\ +\ next\n\ +\ pushSym (Lit 0)" + +runOpcodePush :: (From source Int, VMOps t, ?op::Word8) => source -> StateT (VM t s) (ST s) () +runOpcodePush i = do + vm <- get + let FeeSchedule {..} = vm.block.schedule + let n = into i + !xs = case vm.state.code of + UnknownCode _ -> internalError "Cannot execute unknown code" + InitCode conc _ -> Lit $ word $ padRight n $ BS.take n (BS.drop (1 + vm.state.pc) conc) + RuntimeCode (ConcreteRuntimeCode bs) -> Lit $ word $ BS.take n $ BS.drop (1 + vm.state.pc) bs + RuntimeCode (SymbolicRuntimeCode ops) -> + let bytes = V.take n $ V.drop (1 + vm.state.pc) ops + in Expr.readWord (Lit 0) $ Expr.fromList $ padLeft' 32 bytes + limitStack 1 $ + burn g_verylow $ do + next + pushSym xs + +runOpcodePushSrc :: String +runOpcodePushSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ let n = into i\n\ +\ !xs = case vm.state.code of\n\ +\ UnknownCode _ -> internalError \"Cannot execute unknown code\"\n\ +\ InitCode conc _ -> Lit $ word $ padRight n $ BS.take n (BS.drop (1 + vm.state.pc) conc)\n\ +\ RuntimeCode (ConcreteRuntimeCode bs) -> Lit $ word $ BS.take n $ BS.drop (1 + vm.state.pc) bs\n\ +\ RuntimeCode (SymbolicRuntimeCode ops) ->\n\ +\ let bytes = V.take n $ V.drop (1 + vm.state.pc) ops\n\ +\ in Expr.readWord (Lit 0) $ Expr.fromList $ padLeft' 32 bytes\n\ +\ limitStack 1 $\n\ +\ burn g_verylow $ do\n\ +\ next\n\ +\ pushSym xs" + +runOpcodePushType :: String +runOpcodePushType = "(From source Int, VMOps t, ?op::Word8) => source -> StateT (VM t s) (ST s) ()" + +runOpcodePop :: (VMOps t, ?op::Word8) => + StateT (VM t s) (ST s) () +runOpcodePop = do + vm <- get + let stk = vm.state.stack + FeeSchedule {..} = vm.block.schedule + case stk of + _:xs -> burn g_base (next >> assign (#state % #stack) xs) + _ -> underrun + +runOpcodePopSrc :: String +runOpcodePopSrc = "do\n\ +\ vm <- get\n\ +\ let stk = vm.state.stack\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ case stk of\n\ +\ _:xs -> burn g_base (next >> assign (#state % #stack) xs)\n\ +\ _ -> underrun" + +runOpcodePopType :: String +runOpcodePopType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +{-# INLINE runOpcodeRevert #-} +runOpcodeRevert :: (VMOps t) => + StateT (VM t s) (ST s) () +runOpcodeRevert = do + vm <- get + let stk = vm.state.stack + case stk of + xOffset:xSize:_ -> + accessMemoryRange xOffset xSize $ do + output <- readMemory xOffset xSize + finishFrame (FrameReverted output) + _ -> underrun + +runOpcodeRevertSrc :: String +runOpcodeRevertSrc = "do\n\ +\ vm <- get\n\ +\ let stk = vm.state.stack\n\ +\ case stk of\n\ +\ xOffset:xSize:_ ->\n\ +\ accessMemoryRange xOffset xSize $ do\n\ +\ output <- readMemory xOffset xSize\n\ +\ finishFrame (FrameReverted output)\n\ +\ _ -> underrun" + +runOpcodeRevertType :: String +runOpcodeRevertType = "(VMOps t) => StateT (VM t s) (ST s) ()" + +runOpStop :: (VMOps t) => EVM t s () +runOpStop = finishFrame (FrameReturned mempty) + +runOpcodeStopSrc :: String +runOpcodeStopSrc = "finishFrame (FrameReturned mempty)" + +runOpcodeStopType :: String +runOpcodeStopType = "VMOps t => EVM t s ()" + +runOpcodeJumpi :: (VMOps t, ?op::Word8) => StateT (VM t s) (ST s) () +runOpcodeJumpi = do + vm <- get + let conf = defaultConfig -- TODO + let stk = vm.state.stack + FeeSchedule {..} = vm.block.schedule + case stk of + x:y:xs -> let ?conf = defaultConfig in forceConcreteLimitSz x 2 "JUMPI: symbolic jumpdest" $ \x' -> + burn g_high $ + let jump :: (VMOps t) => Bool -> EVM t s () + jump False = assign (#state % #stack) xs >> next + jump _ = case tryInto x' of + Left _ -> vmError BadJumpDestination + Right i -> checkJump i xs + in branch conf.maxDepth y jump + _ -> underrun + +runOpcodeJumpiSrc :: String +runOpcodeJumpiSrc = "do\n\ +\ vm <- get\n\ +\ let conf = defaultConfig -- TODO\n\ +\ let stk = vm.state.stack\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ case stk of\n\ +\ x:y:xs -> let ?conf = defaultConfig in forceConcreteLimitSz x 2 \"JUMPI: symbolic jumpdest\" $ \\x' ->\n\ +\ burn g_high $\n\ +\ let jump :: (VMOps t) => Bool -> EVM t s ()\n\ +\ jump False = assign (#state % #stack) xs >> next\n\ +\ jump _ = case tryInto x' of\n\ +\ Left _ -> vmError BadJumpDestination\n\ +\ Right i -> checkJump i xs\n\ +\ in branch conf.maxDepth y jump\n\ +\ _ -> underrun" + +runOpcodeJumpiType :: String +runOpcodeJumpiType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeJump :: (VMOps t, ?op::Word8) => + StateT (VM t s) (ST s) () +runOpcodeJump = do + vm <- get + let stk = vm.state.stack + FeeSchedule {..} = vm.block.schedule + case stk of + x:xs -> + burn g_mid $ let ?conf = defaultConfig in forceConcreteLimitSz x 2 "JUMP: symbolic jumpdest" $ \x' -> + case tryInto x' of + Left _ -> vmError BadJumpDestination + Right i -> checkJump i xs + _ -> underrun + +runOpcodeJumpSrc :: String +runOpcodeJumpSrc = "do\n\ +\ vm <- get\n\ +\ let stk = vm.state.stack\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ case stk of\n\ +\ x:xs ->\n\ +\ burn g_mid $ let ?conf = defaultConfig in forceConcreteLimitSz x 2 \"JUMP: symbolic jumpdest\" $ \\x' ->\n\ +\ case tryInto x' of\n\ +\ Left _ -> vmError BadJumpDestination\n\ +\ Right i -> checkJump i xs\n\ +\ _ -> underrun" + +runOpcodeJumpType :: String +runOpcodeJumpType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeJumpdest :: (VMOps t, ?op::Word8) => + StateT (VM t s) (ST s) () +runOpcodeJumpdest = do + vm <- get + let FeeSchedule {..} = vm.block.schedule + burn g_jumpdest next + +runOpcodeJumpdestSrc :: String +runOpcodeJumpdestSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ burn g_jumpdest next" + +runOpcodeJumpdestType :: String +runOpcodeJumpdestType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeMStore :: (VMOps t, ?op::Word8) => + StateT (VM t s) (ST s) () +runOpcodeMStore = do + vm <- get + let stk = vm.state.stack + FeeSchedule {..} = vm.block.schedule + case stk of + x:y:xs -> + burn g_verylow $ + accessMemoryWord x $ do + next + gets (.state.memory) >>= \case + ConcreteMemory mem -> do + case y of + Lit w -> + copyBytesToMemory (ConcreteBuf (word256Bytes w)) (Lit 32) (Lit 0) x + _ -> do + -- copy out and move to symbolic memory + buf <- freezeMemory mem + assign (#state % #memory) (SymbolicMemory $ Expr.writeWord x y buf) + SymbolicMemory mem -> + assign (#state % #memory) (SymbolicMemory $ Expr.writeWord x y mem) + assign (#state % #stack) xs + _ -> underrun + +runOpcodeMStoreSrc :: String +runOpcodeMStoreSrc = "do\n\ +\ vm <- get\n\ +\ let stk = vm.state.stack\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ case stk of\n\ +\ x:y:xs ->\n\ +\ burn g_verylow $\n\ +\ accessMemoryWord x $ do\n\ +\ next\n\ +\ gets (.state.memory) >>= \\case\n\ +\ ConcreteMemory mem -> do\n\ +\ case y of\n\ +\ Lit w ->\n\ +\ copyBytesToMemory (ConcreteBuf (word256Bytes w)) (Lit 32) (Lit 0) x\n\ +\ _ -> do\n\ +\ -- copy out and move to symbolic memory\n\ +\ buf <- freezeMemory mem\n\ +\ assign (#state % #memory) (SymbolicMemory $ Expr.writeWord x y buf)\n\ +\ SymbolicMemory mem ->\n\ +\ assign (#state % #memory) (SymbolicMemory $ Expr.writeWord x y mem)\n\ +\ assign (#state % #stack) xs\n\ +\ _ -> underrun" + +runOpcodeMStoreType :: String +runOpcodeMStoreType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeMLoad :: (VMOps t, ?op::Word8) => + StateT (VM t s) (ST s) () +runOpcodeMLoad = do + vm <- get + let stk = vm.state.stack + FeeSchedule {..} = vm.block.schedule + case stk of + x:xs -> + burn g_verylow $ + accessMemoryWord x $ do + next + buf <- readMemory x (Lit 32) + let w = Expr.readWordFromBytes (Lit 0) buf + assign (#state % #stack) (w : xs) + _ -> underrun + +runOpcodeMLoadSrc :: String +runOpcodeMLoadSrc = "do\n\ +\ vm <- get\n\ +\ let stk = vm.state.stack\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ case stk of\n\ +\ x:xs ->\n\ +\ burn g_verylow $\n\ +\ accessMemoryWord x $ do\n\ +\ next\n\ +\ buf <- readMemory x (Lit 32)\n\ +\ let w = Expr.readWordFromBytes (Lit 0) buf\n\ +\ assign (#state % #stack) (w : xs)\n\ +\ _ -> underrun" + +runOpcodeMLoadType :: String +runOpcodeMLoadType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeSlt :: (VMOps t, ?op::Word8) => + StateT (VM t s) (ST s) () +runOpcodeSlt = do + vm <- get + let + FeeSchedule {..} = vm.block.schedule + stackOp2 g_verylow Expr.slt + +runOpcodeSltSrc :: String +runOpcodeSltSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ stackOp2 g_verylow Expr.slt" + +runOpcodeSltType :: String +runOpcodeSltType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeIsZero :: (VMOps t, ?op::Word8) => + StateT (VM t s) (ST s) () + +runOpcodeIsZero = do + vm <- get + let + FeeSchedule {..} = vm.block.schedule + stackOp1 g_verylow Expr.iszero + +runOpcodeIsZeroSrc :: String +runOpcodeIsZeroSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ stackOp1 g_verylow Expr.iszero" + +runOpcodeIsZeroType :: String +runOpcodeIsZeroType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeEq :: (VMOps t, ?op::Word8) => StateT (VM t s) (ST s) () +runOpcodeEq = do + vm <- get + let + FeeSchedule {..} = vm.block.schedule + stackOp2 g_verylow Expr.eq + +runOpcodeEqSrc :: String +runOpcodeEqSrc = "do\n\ +\ vm <- get\n\ +\ let FeeSchedule {..} = vm.block.schedule\n\ +\ stackOp2 g_verylow Expr.eq" + +runOpcodeEqType :: String +runOpcodeEqType = "(VMOps t, ?op::Word8) => StateT (VM t s) (ST s) ()" + +runOpcodeStackOp2Src :: String +runOpcodeStackOp2Src = + "use (#state % #stack) >>= \\case\n\ +\ x:y:xs ->\n\ +\ burn cost $ do\n\ +\ next\n\ +\ #state % #stack .= f x y : xs\n\ +\ _ ->\n\ +\ underrun" + +runOpcodeStackOp2Type :: String +runOpcodeStackOp2Type = "(?op :: Word8, VMOps t) => Word64 -> (Expr EWord -> Expr EWord -> Expr EWord) -> EVM t s ()" + +nextFast :: (?op :: Word8) => EVM t s () +nextFast = do + vm <- get + put $ modifyState (vm.state { pc = vm.state.pc + fromIntegral (opSize ?op) }) vm + where modifyState :: FrameState t s -> VM t s -> VM t s + modifyState st vm = vm { state = st } + +runOpcodeNextSrc :: String +runOpcodeNextSrc = "do\n\ +\ vm <- get\n\ +\ put $ modifyState (vm.state { pc = vm.state.pc + fromIntegral (opSize ?op) }) vm\n\ +\ where modifyState :: FrameState t s -> VM t s -> VM t s\n\ +\ modifyState st vm = vm { state = st }" + +runOpcodeNextType :: String +runOpcodeNextType = "(?op :: Word8) => EVM t s ()" + +opcodesImpl :: [(String, String, String, String, Bool)] +opcodesImpl = + [ + ("Add", "", runOpcodeAddType, runOpcodeAddSrc, True) + , ("Mul", "", runOpcodeMulType, runOpcodeMulSrc, True) + , ("Sub", "", runOpcodeSubType, runOpcodeSubSrc, True) + , ("Div", "", runOpcodeDivType, runOpcodeDivSrc, True) + , ("Mod", "", runOpcodeModType, runOpcodeModSrc, True) + , ("Push0", "", runOpcodePush0Type, runOpcodePush0Src, True) + , ("Push", " i", runOpcodePushType, runOpcodePushSrc, True) + , ("Pop", "", runOpcodePopType, runOpcodePopSrc, True) + , ("Stop", "", runOpcodeStopType, runOpcodeStopSrc, True) + , ("Revert", "", runOpcodeRevertType, runOpcodeRevertSrc, True) + , ("Dup", " i", runOpcodeDupType, runOpcodeDupSrc, True) + , ("Swap", " i", runOpcodeSwapType, runOpcodeSwapSrc, True) + , ("MStore", "", runOpcodeMStoreType, runOpcodeMStoreSrc, True) + , ("MLoad", "", runOpcodeMLoadType, runOpcodeMLoadSrc, True) + , ("IsZero", "", runOpcodeIsZeroType, runOpcodeIsZeroSrc, True) + , ("Eq", "", runOpcodeEqType, runOpcodeEqSrc, True) + , ("Jumpi", "", runOpcodeJumpiType, runOpcodeJumpiSrc, True) + , ("Jump", "", runOpcodeJumpType, runOpcodeJumpSrc, True) + , ("Jumpdest", "", runOpcodeJumpdestType, runOpcodeJumpdestSrc, True) + , ("Slt", "", runOpcodeSltType, runOpcodeSltSrc, True) + , ("stackOp2", " cost f", runOpcodeStackOp2Type, runOpcodeStackOp2Src, False) + , ("next", "", runOpcodeNextType, runOpcodeNextSrc, False) + ] diff --git a/src/EVM/Stepper.hs b/src/EVM/Stepper.hs index b66b8fda2..439db2181 100644 --- a/src/EVM/Stepper.hs +++ b/src/EVM/Stepper.hs @@ -7,6 +7,7 @@ module EVM.Stepper , execFully , run , runFully + , runFullyNoEffects , wait , fork , evm @@ -95,9 +96,13 @@ runFully = do Just _ -> pure vm +runFullyNoEffects :: VMOps t => Config -> VM t RealWorld -> IO (VM t RealWorld, VM t RealWorld) +runFullyNoEffects conf vm = stToIO $ runStateT (EVM.Exec.run conf) vm + enter :: Text -> Stepper t s () enter t = evm (EVM.pushTrace (EntryTrace t)) + interpret :: forall m a . (App m) => Fetch.Fetcher Concrete m RealWorld