packages feed

hevm-0.53.0: src/EVM/Fuzz.hs

{-# LANGUAGE ScopedTypeVariables #-}

{- |
    Module: EVM.Fuzz
    Description: Concrete Fuzzer of Exprs
-}

module EVM.Fuzz where

import Prelude hiding (LT, GT, lookup)
import Control.Monad.State
import Data.Maybe (fromMaybe)
import Data.Map.Strict as Map (fromList, Map, (!), (!?), insert)
import EVM.Expr qualified as Expr
import EVM.Expr (bytesToW256)
import Data.Set as Set (insert, Set, empty, toList, fromList)
import EVM.Traversals
import Data.ByteString qualified as BS
import Data.Word (Word8)
import Test.QuickCheck.Gen

import EVM.Types (Prop(..), W256, Expr(..), EType(..), internalError, keccak')
import EVM.SMT qualified as SMT (BufModel(..), SMTCex(..))
import Test.QuickCheck (Arbitrary(arbitrary))
import Test.QuickCheck.Random (mkQCGen)

-- TODO: Extract Var X = Lit Z, and set it
tryCexFuzz :: [Prop] -> Integer -> (Maybe (SMT.SMTCex))
tryCexFuzz prePs tries = unGen (testVals tries) (mkQCGen 0) 1337
  where
    ps = Expr.simplifyProps $ Expr.concKeccakProps prePs
    vars = extractVars ps
    bufs = extractBufs ps
    stores = extractStorage ps
    testVals :: Integer -> Gen (Maybe SMT.SMTCex)
    testVals 0 = pure Nothing
    testVals todo = do
      varvals <- getvals vars
      bufVals <- getBufs bufs
      storeVals <- getStores stores
      let
        ret = filterCorrectKeccak $ map (substituteEWord varvals . substituteBuf bufVals . substituteStores storeVals) ps
        retSimp = Expr.simplifyProps $ Expr.concKeccakProps ret
      if null retSimp then pure $ Just (SMT.SMTCex {
                                    vars = varvals
                                    , addrs = mempty
                                    , buffers = bufVals
                                    , store = storeVals
                                    , blockContext = mempty
                                    , txContext = mempty
                                    })
                                    else testVals (todo-1)

-- Filter out PEq (Lit X) (keccak (ConcreteBuf Y)) if it's correct
filterCorrectKeccak :: [Prop] -> [Prop]
filterCorrectKeccak ps = filter checkKecc ps
  where
    checkKecc (PEq (Lit x) (Keccak (ConcreteBuf y))) = keccak' y /= x
    checkKecc _ = True

substituteEWord :: Map (Expr EWord) W256 -> Prop -> Prop
substituteEWord valMap p = mapProp go p
  where
    go :: Expr a -> Expr a
    go orig@(Var _) = Lit (valMap ! orig)
    go orig@(TxValue) = Lit (valMap ! orig)
    go a = a


substituteBuf :: Map (Expr Buf) SMT.BufModel -> Prop -> Prop
substituteBuf valMap p = mapProp go p
  where
    go :: Expr a -> Expr a
    go orig@(AbstractBuf _) = case (valMap !? orig) of
                                Just (SMT.Flat x) -> ConcreteBuf x
                                Just (SMT.Comp _) -> internalError "No compressed allowed in fuzz"
                                Nothing -> orig
    go a = a

substituteStores ::  Map (Expr 'EAddr) (Map W256 W256) -> Prop -> Prop
substituteStores valMap p = mapProp go p
  where
    go :: Expr a -> Expr a
    -- TODO: Is this OK??
    go (AbstractStore a _) = case valMap !? a of
                                  Just m -> ConcreteStore m
                                  Nothing -> ConcreteStore mempty
    go a = a

-- Var extraction
data CollectVars = CollectVars {vars :: Set.Set (Expr EWord)
                               ,vals :: Set.Set W256
                               }
  deriving (Show)

initVarsState :: CollectVars
initVarsState = CollectVars {vars = Set.empty
                            ,vals =  Set.empty
                            }

findVarProp :: Prop -> State CollectVars Prop
findVarProp p = mapPropM go p
  where
    go :: forall a. Expr a -> State CollectVars (Expr a)
    go = \case
      e@(Var a) -> do
        s <- get
        put s {vars = Set.insert (Var a) s.vars}
        pure e
      e@(Lit a) -> do
        s <- get
        put (s {vals=Set.insert a s.vals} ::CollectVars)
        pure e
      e -> pure e


extractVars :: [Prop] -> CollectVars
extractVars ps = execState (mapM_ findVarProp ps) initVarsState


--- Buf extraction
newtype CollectBufs = CollectBufs { bufs :: Set.Set (Expr Buf) }
  deriving (Show)

initBufsState :: CollectBufs
initBufsState = CollectBufs { bufs = Set.empty }

extractBufs :: [Prop] -> [Expr Buf]
extractBufs ps = Set.toList bufs
 where
  CollectBufs bufs = execState (mapM_ findBufProp ps) initBufsState

findBufProp :: Prop -> State CollectBufs Prop
findBufProp p = mapPropM go p
  where
    go :: forall a. Expr a -> State CollectBufs (Expr a)
    go = \case
      e@(AbstractBuf a) -> do
        s <- get
        put s {bufs=Set.insert (AbstractBuf a) s.bufs}
        pure e
      e -> pure e

--- Store extraction
data CollectStorage = CollectStorage { addrs :: Set.Set (Expr EAddr, Maybe W256)
                                     , keys :: Set.Set W256
                                     , vals :: Set.Set W256
                                     }
  deriving (Show)

instance Semigroup (CollectStorage) where
  (CollectStorage a b c) <> (CollectStorage a2 b2 c2) = CollectStorage (a <> a2) (b <> b2) (c <> c2)

initStorageState :: CollectStorage
initStorageState = CollectStorage { addrs = Set.empty, keys = Set.empty, vals = Set.fromList [0x0, 0x1, Expr.maxLit] }

extractStorage :: [Prop] -> CollectStorage
extractStorage ps = execState (mapM_ findStoragePropInner ps) initStorageState <>
    execState (mapM_ findStoragePropComp ps) initStorageState

findStoragePropComp :: Prop -> State CollectStorage Prop
findStoragePropComp p = go2 p
  where
    go2 :: Prop -> State CollectStorage (Prop)
    go2 = \case
      PNeg x -> go2 x
      e@(PEq (Lit val) (SLoad {})) -> do
        s <- get
        put (s {vals=Set.insert val s.vals} :: CollectStorage)
        pure e
      e@(PLT (Lit val) (SLoad {})) -> do
        s <- get
        put (s {vals=Set.insert val s.vals} :: CollectStorage)
        pure e
      (PGT a@(Lit _) b@(SLoad {})) -> go2 (PLT a b)
      (PGEq a@(Lit _) b@(SLoad {})) -> go2 (PLT a b)
      (PLEq a@(Lit _) b@(SLoad {})) -> go2 (PLT a b)
      e -> pure e

findStoragePropInner :: Prop -> State CollectStorage Prop
findStoragePropInner p = mapPropM go p
  where
    go :: forall a. Expr a -> State CollectStorage (Expr a)
    go = \case
      e@(AbstractStore a idx) -> do
        s <- get
        put s {addrs=Set.insert (a, idx) s.addrs}
        pure e
      e@(SLoad (Lit val) _) -> do
        s <- get
        put s {keys=Set.insert val s.keys}
        pure e
      e@(SStore _ (Lit val) _) -> do
        s <- get
        put (s {vals=Set.insert val s.vals} :: CollectStorage)
        pure e
      e -> pure e

-- Var value and TX value generation
getvals :: CollectVars -> Gen (Map (Expr EWord) W256)
getvals vars = do
    bufs <- go (Set.toList vars.vars) mempty
    addTxStuff bufs
  where
    addTxStuff :: Map (Expr EWord) W256 -> Gen (Map (Expr EWord) W256)
    addTxStuff a = do
      val <- frequency [ (20, pure 0)
                       , (1, getRndW256)
                       ]
      pure $ Map.insert TxValue val a
    go :: [Expr EWord] -> Map (Expr EWord) W256 -> Gen (Map (Expr EWord) W256)
    go [] valMap = pure valMap
    go (a:ax) valMap = do
      pickKnown :: Bool <- arbitrary
      val <- if (not pickKnown) || (null vars.vals) then do getRndW256
                                                    else elements $ Set.toList (vars.vals)
      go ax (Map.insert a val valMap)

-- Storage value generation
getStores :: CollectStorage -> Gen (Map (Expr EAddr) (Map W256 W256))
getStores storesLoads = go (Set.toList storesLoads.addrs) mempty
  where
    go :: [(Expr EAddr, Maybe W256)] -> Map (Expr EAddr) (Map W256 W256) -> Gen (Map (Expr EAddr) (Map W256 W256))
    go [] addrToValsMap = pure addrToValsMap
    go ((addr, _):ax) addrToValsMap = do
      -- number of elements inserted into storage
      numElems :: Int <- frequency [(1, pure 0)
                                   ,(10, choose (1, 10))
                                   ,(1, choose (11, 100))
                                   ]
      l <- replicateM numElems oneWrite
      go ax (Map.insert addr (Map.fromList l) addrToValsMap)
        where
          oneWrite :: Gen (W256, W256)
          oneWrite = do
                    a <- getRndElem storesLoads.keys
                    b <- frequency [(1, getRndW256)
                                   ,(3, elements $ Set.toList storesLoads.vals)
                                   ]
                    pure (fromMaybe (0::W256) a, b)
    getRndElem :: Set W256 -> Gen (Maybe W256)
    getRndElem choices = if null choices then pure Nothing
                         else do fmap Just $ elements $ Set.toList choices

-- Buf value generation
getBufs :: [Expr Buf] -> Gen (Map (Expr Buf) SMT.BufModel)
getBufs bufs = go bufs mempty
  where
    go :: [Expr Buf] -> Map (Expr Buf) SMT.BufModel -> Gen (Map (Expr Buf) SMT.BufModel)
    go [] valMap = pure valMap
    go (a:ax) valMap = do
      bytes :: [Word8] <- frequency [
              (1, do
                x :: Int <- choose (1, 100)
                replicateM x arbitrary)
            , (1, replicateM 0 arbitrary)
       ]
      go ax (Map.insert a (SMT.Flat $ BS.pack bytes) valMap)

getRndW256 :: Gen W256
getRndW256 = do
      val <- replicateM 32 arbitrary
      pure $ bytesToW256 val